From 35be723ebff5bfba7606e00c2c496946459c2ef9 Mon Sep 17 00:00:00 2001 From: seavor Date: Sun, 12 Apr 2026 02:27:03 -0500 Subject: [PATCH 01/38] Add near 100% unit test coverage for webclient websocket layer --- webclient/src/websocket/WebClient.spec.ts | 122 +++++ .../websocket/__mocks__/callbackHelpers.ts | 35 ++ webclient/src/websocket/__mocks__/helpers.ts | 74 +++ .../__mocks__/sessionCommandMocks.ts | 132 +++++ .../commands/admin/adminCommands.spec.ts | 104 ++++ .../moderator/moderatorCommands.spec.ts | 239 ++++++++ .../commands/room/roomCommands.spec.ts | 100 ++++ .../session/sessionCommands-complex.spec.ts | 512 ++++++++++++++++++ .../session/sessionCommands-simple.spec.ts | 416 ++++++++++++++ .../events/common/commonEvents.spec.ts | 19 + .../websocket/events/game/gameEvents.spec.ts | 29 + .../websocket/events/room/roomEvents.spec.ts | 63 +++ .../events/session/sessionEvents.spec.ts | 425 +++++++++++++++ .../persistence/AdminPersistence.spec.ts | 37 ++ .../persistence/GamePersistence.spec.ts | 18 + .../persistence/ModeratorPersistence.spec.ts | 84 +++ .../persistence/RoomPersistence.spec.ts | 117 ++++ .../persistence/SessionPersistence.spec.ts | 395 ++++++++++++++ .../websocket/services/BackendService.spec.ts | 119 ++++ .../services/ProtoController.spec.ts | 41 ++ .../services/ProtobufService.spec.ts | 321 +++++++++++ .../services/WebSocketService.spec.ts | 288 ++++++++++ .../websocket/utils/NormalizeService.spec.ts | 110 ++++ .../src/websocket/utils/guid.util.spec.ts | 19 + .../websocket/utils/passwordHasher.spec.ts | 58 ++ .../websocket/utils/sanitizeHtml.util.spec.ts | 55 ++ 26 files changed, 3932 insertions(+) create mode 100644 webclient/src/websocket/WebClient.spec.ts create mode 100644 webclient/src/websocket/__mocks__/callbackHelpers.ts create mode 100644 webclient/src/websocket/__mocks__/helpers.ts create mode 100644 webclient/src/websocket/__mocks__/sessionCommandMocks.ts create mode 100644 webclient/src/websocket/commands/admin/adminCommands.spec.ts create mode 100644 webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts create mode 100644 webclient/src/websocket/commands/room/roomCommands.spec.ts create mode 100644 webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts create mode 100644 webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts create mode 100644 webclient/src/websocket/events/common/commonEvents.spec.ts create mode 100644 webclient/src/websocket/events/game/gameEvents.spec.ts create mode 100644 webclient/src/websocket/events/room/roomEvents.spec.ts create mode 100644 webclient/src/websocket/events/session/sessionEvents.spec.ts create mode 100644 webclient/src/websocket/persistence/AdminPersistence.spec.ts create mode 100644 webclient/src/websocket/persistence/GamePersistence.spec.ts create mode 100644 webclient/src/websocket/persistence/ModeratorPersistence.spec.ts create mode 100644 webclient/src/websocket/persistence/RoomPersistence.spec.ts create mode 100644 webclient/src/websocket/persistence/SessionPersistence.spec.ts create mode 100644 webclient/src/websocket/services/BackendService.spec.ts create mode 100644 webclient/src/websocket/services/ProtoController.spec.ts create mode 100644 webclient/src/websocket/services/ProtobufService.spec.ts create mode 100644 webclient/src/websocket/services/WebSocketService.spec.ts create mode 100644 webclient/src/websocket/utils/NormalizeService.spec.ts create mode 100644 webclient/src/websocket/utils/guid.util.spec.ts create mode 100644 webclient/src/websocket/utils/passwordHasher.spec.ts create mode 100644 webclient/src/websocket/utils/sanitizeHtml.util.spec.ts diff --git a/webclient/src/websocket/WebClient.spec.ts b/webclient/src/websocket/WebClient.spec.ts new file mode 100644 index 000000000..c9fa0298f --- /dev/null +++ b/webclient/src/websocket/WebClient.spec.ts @@ -0,0 +1,122 @@ +jest.mock('./services/WebSocketService', () => ({ + WebSocketService: jest.fn().mockImplementation(() => ({ + message$: { subscribe: jest.fn() }, + connect: jest.fn(), + testConnect: jest.fn(), + disconnect: jest.fn(), + })), +})); + +jest.mock('./services/ProtobufService', () => ({ + ProtobufService: jest.fn().mockImplementation(() => ({ + handleMessageEvent: jest.fn(), + sendKeepAliveCommand: jest.fn(), + resetCommands: jest.fn(), + })), +})); + +jest.mock('./persistence', () => ({ + RoomPersistence: { clearStore: jest.fn() }, + SessionPersistence: { clearStore: jest.fn() }, +})); + +import { WebClient } from './WebClient'; +import { RoomPersistence, SessionPersistence } from './persistence'; +import { StatusEnum } from 'types'; +import { Subject } from 'rxjs'; + +describe('WebClient', () => { + let client: WebClient; + let messageSubject: Subject; + + beforeEach(() => { + jest.clearAllMocks(); + const { ProtobufService } = require('./services/ProtobufService'); + ProtobufService.mockImplementation(() => ({ + handleMessageEvent: jest.fn(), + sendKeepAliveCommand: jest.fn(), + resetCommands: jest.fn(), + })); + messageSubject = new Subject(); + const { WebSocketService } = require('./services/WebSocketService'); + WebSocketService.mockImplementation(() => ({ + message$: messageSubject, + connect: jest.fn(), + testConnect: jest.fn(), + disconnect: jest.fn(), + })); + // suppress console.log from constructor in non-test-env check + jest.spyOn(console, 'log').mockImplementation(() => {}); + client = new WebClient(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe('constructor', () => { + it('subscribes socket.message$ to protobuf.handleMessageEvent', () => { + const event = { data: new ArrayBuffer(0) } as MessageEvent; + messageSubject.next(event); + expect(client.protobuf.handleMessageEvent).toHaveBeenCalledWith(event); + }); + }); + + describe('connect', () => { + it('sets connectionAttemptMade to true', () => { + const opts: any = { host: 'h', port: 1 }; + client.connect(opts); + expect(client.connectionAttemptMade).toBe(true); + }); + + it('stores options and calls socket.connect', () => { + const opts: any = { host: 'h', port: 1 }; + client.connect(opts); + expect(client.options).toBe(opts); + expect(client.socket.connect).toHaveBeenCalledWith(opts); + }); + }); + + describe('testConnect', () => { + it('delegates to socket.testConnect', () => { + const opts: any = { host: 'h', port: 1 }; + client.testConnect(opts); + expect(client.socket.testConnect).toHaveBeenCalledWith(opts); + }); + }); + + describe('disconnect', () => { + it('delegates to socket.disconnect', () => { + client.disconnect(); + expect(client.socket.disconnect).toHaveBeenCalled(); + }); + }); + + describe('keepAlive', () => { + it('delegates to protobuf.sendKeepAliveCommand', () => { + const pingCb = jest.fn(); + client.keepAlive(pingCb); + expect(client.protobuf.sendKeepAliveCommand).toHaveBeenCalledWith(pingCb); + }); + }); + + describe('updateStatus', () => { + it('sets the status', () => { + client.updateStatus(StatusEnum.CONNECTED); + expect(client.status).toBe(StatusEnum.CONNECTED); + }); + + it('calls protobuf.resetCommands and clears stores on DISCONNECTED', () => { + client.updateStatus(StatusEnum.DISCONNECTED); + expect(client.protobuf.resetCommands).toHaveBeenCalled(); + expect(RoomPersistence.clearStore).toHaveBeenCalled(); + expect(SessionPersistence.clearStore).toHaveBeenCalled(); + }); + + it('does not clear stores when status is not DISCONNECTED', () => { + client.updateStatus(StatusEnum.CONNECTED); + expect(client.protobuf.resetCommands).not.toHaveBeenCalled(); + expect(RoomPersistence.clearStore).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/webclient/src/websocket/__mocks__/callbackHelpers.ts b/webclient/src/websocket/__mocks__/callbackHelpers.ts new file mode 100644 index 000000000..ffa4f04b5 --- /dev/null +++ b/webclient/src/websocket/__mocks__/callbackHelpers.ts @@ -0,0 +1,35 @@ +/** + * Factory for invoking BackendService command callbacks in unit tests. + * + * @param mockFn - The jest.Mock for the BackendService send method + * (e.g. BackendService.sendSessionCommand as jest.Mock). + * @param optsArgIndex - Index of the options argument in the mock call. + * Defaults to 2 (commandName, params, options). + * Use 3 for sendRoomCommand (roomId, commandName, params, options). + */ +export function makeCallbackHelpers(mockFn: jest.Mock, optsArgIndex = 2) { + function getLastSendOpts() { + const calls = mockFn.mock.calls; + return calls[calls.length - 1]?.[optsArgIndex]; + } + + function invokeOnSuccess(response: any = {}, raw?: any) { + getLastSendOpts()?.onSuccess?.(response, raw ?? response); + } + + function invokeResponseCode(code: number, raw: any = { responseCode: code }) { + const opts = getLastSendOpts(); + if (opts?.onResponseCode?.[code]) opts.onResponseCode[code](raw); + } + + function invokeOnError(code: number = 99, raw: any = {}) { + getLastSendOpts()?.onError?.(code, raw); + } + + function invokeCallback(callbackName: string, ...args: any[]) { + const opts = getLastSendOpts(); + if (opts?.[callbackName]) opts[callbackName](...args); + } + + return { getLastSendOpts, invokeOnSuccess, invokeResponseCode, invokeOnError, invokeCallback }; +} diff --git a/webclient/src/websocket/__mocks__/helpers.ts b/webclient/src/websocket/__mocks__/helpers.ts new file mode 100644 index 000000000..669792ea7 --- /dev/null +++ b/webclient/src/websocket/__mocks__/helpers.ts @@ -0,0 +1,74 @@ +/** + * Shared mock factories for websocket layer unit tests. + * Import the helpers you need in each spec file via: + * import { makeMockProtoRoot, makeMockWebSocket } from '../__mocks__/helpers'; + */ + +/** Builds a minimal mock of ProtoController.root */ +export function makeMockProtoRoot() { + const encode = { finish: jest.fn().mockReturnValue(new Uint8Array()) }; + return { + CommandContainer: { + create: jest.fn(args => ({ ...args })), + encode: jest.fn().mockReturnValue(encode), + }, + SessionCommand: { create: jest.fn(args => ({ ...args })) }, + RoomCommand: { create: jest.fn(args => ({ ...args })) }, + ModeratorCommand: { create: jest.fn(args => ({ ...args })) }, + AdminCommand: { create: jest.fn(args => ({ ...args })) }, + ServerMessage: { + decode: jest.fn(), + MessageType: { + RESPONSE: 'RESPONSE', + ROOM_EVENT: 'ROOM_EVENT', + SESSION_EVENT: 'SESSION_EVENT', + GAME_EVENT_CONTAINER: 'GAME_EVENT_CONTAINER', + }, + }, + Response: { + ResponseCode: { + RespOk: 0, + RespRegistrationRequired: 1, + }, + }, + Event_ServerIdentification: { + ServerOptions: { SupportsPasswordHash: 2 }, + }, + Event_ConnectionClosed: { + CloseReason: { + USER_LIMIT_REACHED: 1, + TOO_MANY_CONNECTIONS: 2, + BANNED: 3, + DEMOTED: 4, + SERVER_SHUTDOWN: 5, + USERNAMEINVALID: 6, + LOGGEDINELSEWERE: 7, + OTHER: 8, + }, + }, + }; +} + +/** Builds a mock WebSocket instance */ +export function makeMockWebSocketInstance() { + return { + send: jest.fn(), + close: jest.fn(), + readyState: WebSocket.OPEN, + binaryType: '' as BinaryType, + onopen: null as any, + onclose: null as any, + onerror: null as any, + onmessage: null as any, + }; +} + +/** Installs a mock WebSocket constructor on global. Returns the mock instance. */ +export function installMockWebSocket() { + const mockInstance = makeMockWebSocketInstance(); + const MockWS = jest.fn(() => mockInstance) as any; + MockWS.OPEN = 1; + MockWS.CLOSED = 3; + (global as any).WebSocket = MockWS; + return { MockWS, mockInstance }; +} diff --git a/webclient/src/websocket/__mocks__/sessionCommandMocks.ts b/webclient/src/websocket/__mocks__/sessionCommandMocks.ts new file mode 100644 index 000000000..7b28335b7 --- /dev/null +++ b/webclient/src/websocket/__mocks__/sessionCommandMocks.ts @@ -0,0 +1,132 @@ +/** + * Shared mock shape factories for session command specs. + * + * Usage inside jest.mock() factory callbacks (require is used because + * jest.mock() is hoisted above imports): + * + * jest.mock('../../WebClient', () => { + * const { makeWebClientMock } = require('../../__mocks__/sessionCommandMocks'); + * return { __esModule: true, default: makeWebClientMock() }; + * }); + */ + +/** Superset WebClient mock — covers all properties used across both session spec files. */ +export function makeWebClientMock() { + return { + connect: jest.fn(), + testConnect: jest.fn(), + disconnect: jest.fn(), + updateStatus: jest.fn(), + clientConfig: { clientid: 'webatrice', clientver: '1.0', clientfeatures: [] }, + options: {}, + protocolVersion: 14, + status: 0, + connectionAttemptMade: false, + }; +} + +/** Superset ProtoController.root mock — includes all ResponseCode values and Event_ServerIdentification. */ +export function makeProtoControllerRootMock() { + return { + Response: { + ResponseCode: { + RespOk: 0, + RespClientUpdateRequired: 1, + RespWrongPassword: 2, + RespUsernameInvalid: 3, + RespWouldOverwriteOldSession: 4, + RespUserIsBanned: 5, + RespRegistrationRequired: 6, + RespClientIdRequired: 7, + RespContextError: 8, + RespAccountNotActivated: 9, + RespRegistrationAccepted: 10, + RespRegistrationAcceptedNeedsActivation: 11, + RespUserAlreadyExists: 12, + RespPasswordTooShort: 13, + RespEmailRequiredToRegister: 14, + RespEmailBlackListed: 15, + RespTooManyRequests: 16, + RespRegistrationDisabled: 17, + RespActivationAccepted: 18, + }, + }, + Event_ServerIdentification: { + ServerOptions: { SupportsPasswordHash: 2 }, + }, + }; +} + +/** Utils mock with unified return values. */ +export function makeUtilsMock() { + return { + hashPassword: jest.fn().mockReturnValue('hashed_pw'), + generateSalt: jest.fn().mockReturnValue('randSalt'), + passwordSaltSupported: jest.fn().mockReturnValue(0), + }; +} + +/** Superset SessionPersistence mock — covers all methods used across both session spec files. */ +export function makeSessionPersistenceMock() { + return { + loginSuccessful: jest.fn(), + loginFailed: jest.fn(), + updateBuddyList: jest.fn(), + updateIgnoreList: jest.fn(), + updateUser: jest.fn(), + updateUsers: jest.fn(), + accountAwaitingActivation: jest.fn(), + accountActivationSuccess: jest.fn(), + accountActivationFailed: jest.fn(), + updateStatus: jest.fn(), + directMessageSent: jest.fn(), + addToList: jest.fn(), + removeFromList: jest.fn(), + deleteServerDeck: jest.fn(), + deleteServerDeckDir: jest.fn(), + updateServerDecks: jest.fn(), + uploadServerDeck: jest.fn(), + createServerDeckDir: jest.fn(), + getGamesOfUser: jest.fn(), + getUserInfo: jest.fn(), + accountPasswordChange: jest.fn(), + accountEditChanged: jest.fn(), + accountImageChanged: jest.fn(), + replayList: jest.fn(), + replayAdded: jest.fn(), + replayModifyMatch: jest.fn(), + replayDeleteMatch: jest.fn(), + resetPasswordChallenge: jest.fn(), + resetPassword: jest.fn(), + resetPasswordFailed: jest.fn(), + resetPasswordSuccess: jest.fn(), + registrationFailed: jest.fn(), + registrationSuccess: jest.fn(), + registrationUserNameError: jest.fn(), + registrationPasswordError: jest.fn(), + registrationEmailError: jest.fn(), + registrationRequiresEmail: jest.fn(), + }; +} + +/** + * Session barrel mock — pure jest.fn() map for all cross-command calls. + * Used as-is by sessionCommands-complex.spec.ts, or spread over jest.requireActual + * by sessionCommands-simple.spec.ts to preserve real implementations for + * the commands under test. + */ +export function makeSessionBarrelMock() { + return { + login: jest.fn(), + register: jest.fn(), + activate: jest.fn(), + forgotPasswordReset: jest.fn(), + forgotPasswordRequest: jest.fn(), + forgotPasswordChallenge: jest.fn(), + requestPasswordSalt: jest.fn(), + listUsers: jest.fn(), + listRooms: jest.fn(), + updateStatus: jest.fn(), + disconnect: jest.fn(), + }; +} diff --git a/webclient/src/websocket/commands/admin/adminCommands.spec.ts b/webclient/src/websocket/commands/admin/adminCommands.spec.ts new file mode 100644 index 000000000..9cfc4aa81 --- /dev/null +++ b/webclient/src/websocket/commands/admin/adminCommands.spec.ts @@ -0,0 +1,104 @@ +jest.mock('../../services/BackendService', () => ({ + BackendService: { + sendAdminCommand: jest.fn(), + }, +})); + +jest.mock('../../persistence', () => ({ + AdminPersistence: { + adjustMod: jest.fn(), + reloadConfig: jest.fn(), + shutdownServer: jest.fn(), + updateServerMessage: jest.fn(), + }, +})); + +import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; +import { BackendService } from '../../services/BackendService'; +import { AdminPersistence } from '../../persistence'; + +const { getLastSendOpts, invokeOnSuccess } = makeCallbackHelpers( + BackendService.sendAdminCommand as jest.Mock +); + +beforeEach(() => jest.clearAllMocks()); + +// ---------------------------------------------------------------- +// adjustMod +// ---------------------------------------------------------------- +describe('adjustMod', () => { + const { adjustMod } = jest.requireActual('./adjustMod'); + + it('calls sendAdminCommand with Command_AdjustMod', () => { + adjustMod('alice', true, false); + expect(BackendService.sendAdminCommand).toHaveBeenCalledWith( + 'Command_AdjustMod', + expect.objectContaining({ userName: 'alice', shouldBeMod: true, shouldBeJudge: false }), + expect.any(Object) + ); + }); + + it('onSuccess calls AdminPersistence.adjustMod', () => { + adjustMod('alice', true, false); + invokeOnSuccess(); + expect(AdminPersistence.adjustMod).toHaveBeenCalledWith('alice', true, false); + }); +}); + +// ---------------------------------------------------------------- +// reloadConfig +// ---------------------------------------------------------------- +describe('reloadConfig', () => { + const { reloadConfig } = jest.requireActual('./reloadConfig'); + + it('calls sendAdminCommand with Command_ReloadConfig', () => { + reloadConfig(); + expect(BackendService.sendAdminCommand).toHaveBeenCalledWith('Command_ReloadConfig', {}, expect.any(Object)); + }); + + it('onSuccess calls AdminPersistence.reloadConfig', () => { + reloadConfig(); + invokeOnSuccess(); + expect(AdminPersistence.reloadConfig).toHaveBeenCalled(); + }); +}); + +// ---------------------------------------------------------------- +// shutdownServer +// ---------------------------------------------------------------- +describe('shutdownServer', () => { + const { shutdownServer } = jest.requireActual('./shutdownServer'); + + it('calls sendAdminCommand with Command_ShutdownServer', () => { + shutdownServer('maintenance', 10); + expect(BackendService.sendAdminCommand).toHaveBeenCalledWith( + 'Command_ShutdownServer', + { reason: 'maintenance', minutes: 10 }, + expect.any(Object) + ); + }); + + it('onSuccess calls AdminPersistence.shutdownServer', () => { + shutdownServer('maintenance', 10); + invokeOnSuccess(); + expect(AdminPersistence.shutdownServer).toHaveBeenCalled(); + }); +}); + +// ---------------------------------------------------------------- +// updateServerMessage +// ---------------------------------------------------------------- +describe('updateServerMessage', () => { + const { updateServerMessage } = jest.requireActual('./updateServerMessage'); + + it('calls sendAdminCommand with Command_UpdateServerMessage', () => { + updateServerMessage(); + expect(BackendService.sendAdminCommand).toHaveBeenCalledWith('Command_UpdateServerMessage', {}, expect.any(Object)); + }); + + it('onSuccess calls AdminPersistence.updateServerMessage', () => { + updateServerMessage(); + invokeOnSuccess(); + expect(AdminPersistence.updateServerMessage).toHaveBeenCalled(); + }); +}); diff --git a/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts b/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts new file mode 100644 index 000000000..4f8b508b1 --- /dev/null +++ b/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts @@ -0,0 +1,239 @@ +jest.mock('../../services/BackendService', () => ({ + BackendService: { + sendModeratorCommand: jest.fn(), + }, +})); + +jest.mock('../../persistence', () => ({ + ModeratorPersistence: { + banFromServer: jest.fn(), + forceActivateUser: jest.fn(), + getAdminNotes: jest.fn(), + banHistory: jest.fn(), + warnHistory: jest.fn(), + warnListOptions: jest.fn(), + grantReplayAccess: jest.fn(), + updateAdminNotes: jest.fn(), + viewLogs: jest.fn(), + warnUser: jest.fn(), + }, +})); + +import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; +import { BackendService } from '../../services/BackendService'; +import { ModeratorPersistence } from '../../persistence'; + +const { getLastSendOpts, invokeOnSuccess } = makeCallbackHelpers( + BackendService.sendModeratorCommand as jest.Mock +); + +beforeEach(() => jest.clearAllMocks()); + +// ---------------------------------------------------------------- +// banFromServer +// ---------------------------------------------------------------- +describe('banFromServer', () => { + const { banFromServer } = jest.requireActual('./banFromServer'); + + it('calls sendModeratorCommand with Command_BanFromServer', () => { + banFromServer(30, 'alice', '1.2.3.4', 'reason', 'visible', 'cid', 1); + expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + 'Command_BanFromServer', + expect.objectContaining({ minutes: 30, userName: 'alice' }), + expect.any(Object) + ); + }); + + it('onSuccess calls ModeratorPersistence.banFromServer', () => { + banFromServer(30, 'alice'); + invokeOnSuccess(); + expect(ModeratorPersistence.banFromServer).toHaveBeenCalledWith('alice'); + }); +}); + +// ---------------------------------------------------------------- +// forceActivateUser +// ---------------------------------------------------------------- +describe('forceActivateUser', () => { + const { forceActivateUser } = jest.requireActual('./forceActivateUser'); + + it('calls sendModeratorCommand with Command_ForceActivateUser', () => { + forceActivateUser('alice', 'mod1'); + expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith('Command_ForceActivateUser', expect.any(Object), expect.any(Object)); + }); + + it('onSuccess calls ModeratorPersistence.forceActivateUser', () => { + forceActivateUser('alice', 'mod1'); + invokeOnSuccess(); + expect(ModeratorPersistence.forceActivateUser).toHaveBeenCalledWith('alice', 'mod1'); + }); +}); + +// ---------------------------------------------------------------- +// getAdminNotes +// ---------------------------------------------------------------- +describe('getAdminNotes', () => { + const { getAdminNotes } = jest.requireActual('./getAdminNotes'); + + it('calls sendModeratorCommand with Command_GetAdminNotes', () => { + getAdminNotes('alice'); + expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + 'Command_GetAdminNotes', + expect.any(Object), + expect.objectContaining({ responseName: 'Response_GetAdminNotes' }) + ); + }); + + it('onSuccess calls ModeratorPersistence.getAdminNotes with notes', () => { + getAdminNotes('alice'); + const resp = { notes: 'some notes' }; + invokeOnSuccess(resp, { responseCode: 0, '.Response_GetAdminNotes.ext': resp }); + expect(ModeratorPersistence.getAdminNotes).toHaveBeenCalledWith('alice', 'some notes'); + }); +}); + +// ---------------------------------------------------------------- +// getBanHistory +// ---------------------------------------------------------------- +describe('getBanHistory', () => { + const { getBanHistory } = jest.requireActual('./getBanHistory'); + + it('calls sendModeratorCommand with Command_GetBanHistory', () => { + getBanHistory('alice'); + expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + 'Command_GetBanHistory', + expect.any(Object), + expect.objectContaining({ responseName: 'Response_BanHistory' }) + ); + }); + + it('onSuccess calls ModeratorPersistence.banHistory with banList', () => { + getBanHistory('alice'); + const resp = { banList: [{ id: 1 }] }; + invokeOnSuccess(resp, { responseCode: 0, '.Response_BanHistory.ext': resp }); + expect(ModeratorPersistence.banHistory).toHaveBeenCalledWith('alice', [{ id: 1 }]); + }); +}); + +// ---------------------------------------------------------------- +// getWarnHistory +// ---------------------------------------------------------------- +describe('getWarnHistory', () => { + const { getWarnHistory } = jest.requireActual('./getWarnHistory'); + + it('calls sendModeratorCommand with Command_GetWarnHistory', () => { + getWarnHistory('alice'); + expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + 'Command_GetWarnHistory', + expect.any(Object), + expect.objectContaining({ responseName: 'Response_WarnHistory' }) + ); + }); + + it('onSuccess calls ModeratorPersistence.warnHistory with warnList', () => { + getWarnHistory('alice'); + const resp = { warnList: [{ id: 2 }] }; + invokeOnSuccess(resp, { responseCode: 0, '.Response_WarnHistory.ext': resp }); + expect(ModeratorPersistence.warnHistory).toHaveBeenCalledWith('alice', [{ id: 2 }]); + }); +}); + +// ---------------------------------------------------------------- +// getWarnList +// ---------------------------------------------------------------- +describe('getWarnList', () => { + const { getWarnList } = jest.requireActual('./getWarnList'); + + it('calls sendModeratorCommand with Command_GetWarnList', () => { + getWarnList('mod1', 'alice', 'US'); + expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + 'Command_GetWarnList', + expect.any(Object), + expect.objectContaining({ responseName: 'Response_WarnList' }) + ); + }); + + it('onSuccess calls ModeratorPersistence.warnListOptions with warning', () => { + getWarnList('mod1', 'alice', 'US'); + const resp = { warning: ['w1', 'w2'] }; + invokeOnSuccess(resp, { responseCode: 0, '.Response_WarnList.ext': resp }); + expect(ModeratorPersistence.warnListOptions).toHaveBeenCalledWith(['w1', 'w2']); + }); +}); + +// ---------------------------------------------------------------- +// grantReplayAccess +// ---------------------------------------------------------------- +describe('grantReplayAccess', () => { + const { grantReplayAccess } = jest.requireActual('./grantReplayAccess'); + + it('calls sendModeratorCommand with Command_GrantReplayAccess', () => { + grantReplayAccess(10, 'mod1'); + expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith('Command_GrantReplayAccess', expect.any(Object), expect.any(Object)); + }); + + it('onSuccess calls ModeratorPersistence.grantReplayAccess', () => { + grantReplayAccess(10, 'mod1'); + invokeOnSuccess(); + expect(ModeratorPersistence.grantReplayAccess).toHaveBeenCalledWith(10, 'mod1'); + }); +}); + +// ---------------------------------------------------------------- +// updateAdminNotes +// ---------------------------------------------------------------- +describe('updateAdminNotes', () => { + const { updateAdminNotes } = jest.requireActual('./updateAdminNotes'); + + it('calls sendModeratorCommand with Command_UpdateAdminNotes', () => { + updateAdminNotes('alice', 'new notes'); + expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith('Command_UpdateAdminNotes', expect.any(Object), expect.any(Object)); + }); + + it('onSuccess calls ModeratorPersistence.updateAdminNotes', () => { + updateAdminNotes('alice', 'new notes'); + invokeOnSuccess(); + expect(ModeratorPersistence.updateAdminNotes).toHaveBeenCalledWith('alice', 'new notes'); + }); +}); + +// ---------------------------------------------------------------- +// viewLogHistory +// ---------------------------------------------------------------- +describe('viewLogHistory', () => { + const { viewLogHistory } = jest.requireActual('./viewLogHistory'); + + it('calls sendModeratorCommand with Command_ViewLogHistory', () => { + viewLogHistory({ filters: 'all' } as any); + expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + 'Command_ViewLogHistory', + expect.any(Object), + expect.objectContaining({ responseName: 'Response_ViewLogHistory' }) + ); + }); + + it('onSuccess calls ModeratorPersistence.viewLogs with logMessage', () => { + viewLogHistory({ filters: 'all' } as any); + const resp = { logMessage: ['log1'] }; + invokeOnSuccess(resp, { responseCode: 0, '.Response_ViewLogHistory.ext': resp }); + expect(ModeratorPersistence.viewLogs).toHaveBeenCalledWith(['log1']); + }); +}); + +// ---------------------------------------------------------------- +// warnUser +// ---------------------------------------------------------------- +describe('warnUser', () => { + const { warnUser } = jest.requireActual('./warnUser'); + + it('calls sendModeratorCommand with Command_WarnUser', () => { + warnUser('alice', 'bad behavior', 'cid'); + expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith('Command_WarnUser', expect.any(Object), expect.any(Object)); + }); + + it('onSuccess calls ModeratorPersistence.warnUser', () => { + warnUser('alice', 'bad behavior', 'cid'); + invokeOnSuccess(); + expect(ModeratorPersistence.warnUser).toHaveBeenCalledWith('alice'); + }); +}); diff --git a/webclient/src/websocket/commands/room/roomCommands.spec.ts b/webclient/src/websocket/commands/room/roomCommands.spec.ts new file mode 100644 index 000000000..652a5d792 --- /dev/null +++ b/webclient/src/websocket/commands/room/roomCommands.spec.ts @@ -0,0 +1,100 @@ +jest.mock('../../services/BackendService', () => ({ + BackendService: { + sendRoomCommand: jest.fn(), + }, +})); + +jest.mock('../../persistence', () => ({ + RoomPersistence: { + gameCreated: jest.fn(), + joinedGame: jest.fn(), + leaveRoom: jest.fn(), + }, +})); + +import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; +import { BackendService } from '../../services/BackendService'; +import { RoomPersistence } from '../../persistence'; + +const { getLastSendOpts, invokeOnSuccess } = makeCallbackHelpers( + BackendService.sendRoomCommand as jest.Mock, + 3 // sendRoomCommand(roomId, commandName, params, options) — options at index 3 +); + +beforeEach(() => jest.clearAllMocks()); + +// ---------------------------------------------------------------- +// createGame +// ---------------------------------------------------------------- +describe('createGame', () => { + const { createGame } = jest.requireActual('./createGame'); + + it('calls sendRoomCommand with Command_CreateGame', () => { + createGame(5, { maxPlayers: 4 } as any); + expect(BackendService.sendRoomCommand).toHaveBeenCalledWith(5, 'Command_CreateGame', { maxPlayers: 4 }, expect.any(Object)); + }); + + it('onSuccess calls RoomPersistence.gameCreated with roomId', () => { + createGame(5, {} as any); + invokeOnSuccess(); + expect(RoomPersistence.gameCreated).toHaveBeenCalledWith(5); + }); +}); + +// ---------------------------------------------------------------- +// joinGame +// ---------------------------------------------------------------- +describe('joinGame', () => { + const { joinGame } = jest.requireActual('./joinGame'); + + it('calls sendRoomCommand with Command_JoinGame', () => { + joinGame(7, { gameId: 42, password: '' } as any); + expect(BackendService.sendRoomCommand).toHaveBeenCalledWith(7, 'Command_JoinGame', { gameId: 42, password: '' }, expect.any(Object)); + }); + + it('onSuccess calls RoomPersistence.joinedGame with roomId and gameId', () => { + joinGame(7, { gameId: 42 } as any); + invokeOnSuccess(); + expect(RoomPersistence.joinedGame).toHaveBeenCalledWith(7, 42); + }); +}); + +// ---------------------------------------------------------------- +// leaveRoom +// ---------------------------------------------------------------- +describe('leaveRoom', () => { + const { leaveRoom } = jest.requireActual('./leaveRoom'); + + it('calls sendRoomCommand with Command_LeaveRoom', () => { + leaveRoom(3); + expect(BackendService.sendRoomCommand).toHaveBeenCalledWith(3, 'Command_LeaveRoom', {}, expect.any(Object)); + }); + + it('onSuccess calls RoomPersistence.leaveRoom with roomId', () => { + leaveRoom(3); + invokeOnSuccess(); + expect(RoomPersistence.leaveRoom).toHaveBeenCalledWith(3); + }); +}); + +// ---------------------------------------------------------------- +// roomSay +// ---------------------------------------------------------------- +describe('roomSay', () => { + const { roomSay } = jest.requireActual('./roomSay'); + + it('calls sendRoomCommand with trimmed message', () => { + roomSay(2, ' hello '); + expect(BackendService.sendRoomCommand).toHaveBeenCalledWith(2, 'Command_RoomSay', { message: 'hello' }, expect.any(Object)); + }); + + it('does not call sendRoomCommand when message is blank', () => { + roomSay(2, ' '); + expect(BackendService.sendRoomCommand).not.toHaveBeenCalled(); + }); + + it('does not call sendRoomCommand when message is empty string', () => { + roomSay(2, ''); + expect(BackendService.sendRoomCommand).not.toHaveBeenCalled(); + }); +}); diff --git a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts new file mode 100644 index 000000000..c6c11c41b --- /dev/null +++ b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts @@ -0,0 +1,512 @@ +// Tests for complex session commands that call webClient directly +// or have multiple branching callbacks. + +jest.mock('../../services/BackendService', () => ({ + BackendService: { + sendSessionCommand: jest.fn(), + }, +})); + +jest.mock('../../persistence', () => { + const { makeSessionPersistenceMock } = require('../../__mocks__/sessionCommandMocks'); + return { + SessionPersistence: makeSessionPersistenceMock(), + RoomPersistence: {}, + }; +}); + +jest.mock('../../WebClient', () => { + const { makeWebClientMock } = require('../../__mocks__/sessionCommandMocks'); + return { __esModule: true, default: makeWebClientMock() }; +}); + +jest.mock('../../services/ProtoController', () => { + const { makeProtoControllerRootMock } = require('../../__mocks__/sessionCommandMocks'); + return { ProtoController: { root: makeProtoControllerRootMock() } }; +}); + +jest.mock('../../utils', () => { + const { makeUtilsMock } = require('../../__mocks__/sessionCommandMocks'); + return makeUtilsMock(); +}); + +// Intercept all re-exported commands to avoid recursive real invocations +jest.mock('./', () => { + const { makeSessionBarrelMock } = require('../../__mocks__/sessionCommandMocks'); + return makeSessionBarrelMock(); +}); + +import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; +import { BackendService } from '../../services/BackendService'; +import { SessionPersistence } from '../../persistence'; +import webClient from '../../WebClient'; +import * as SessionIndexMocks from './'; +import { StatusEnum, WebSocketConnectReason } from 'types'; +import { hashPassword, generateSalt, passwordSaltSupported } from '../../utils'; + +const { getLastSendOpts, invokeOnSuccess, invokeResponseCode, invokeOnError } = makeCallbackHelpers( + BackendService.sendSessionCommand as jest.Mock +); + +beforeEach(() => { + jest.clearAllMocks(); + (hashPassword as jest.Mock).mockReturnValue('hashed_pw'); + (generateSalt as jest.Mock).mockReturnValue('randSalt'); + (passwordSaltSupported as jest.Mock).mockReturnValue(0); +}); + +// ---------------------------------------------------------------- +// connect.ts +// ---------------------------------------------------------------- +describe('connect', () => { + const { connect } = jest.requireActual('./connect'); + + it('calls updateStatus CONNECTING for LOGIN reason', () => { + connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.LOGIN); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...'); + expect(webClient.connect).toHaveBeenCalled(); + }); + + it('calls updateStatus CONNECTING for REGISTER reason', () => { + connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.REGISTER); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...'); + }); + + it('calls updateStatus CONNECTING for ACTIVATE_ACCOUNT reason', () => { + connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.ACTIVATE_ACCOUNT); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...'); + }); + + it('calls updateStatus CONNECTING for PASSWORD_RESET_REQUEST reason', () => { + connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.PASSWORD_RESET_REQUEST); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...'); + }); + + it('calls updateStatus CONNECTING for PASSWORD_RESET_CHALLENGE reason', () => { + connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.PASSWORD_RESET_CHALLENGE); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...'); + }); + + it('calls updateStatus CONNECTING for PASSWORD_RESET reason', () => { + connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.PASSWORD_RESET); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...'); + }); + + it('calls testConnect for TEST_CONNECTION reason', () => { + connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.TEST_CONNECTION); + expect(webClient.testConnect).toHaveBeenCalled(); + expect(webClient.connect).not.toHaveBeenCalled(); + }); + + it('calls updateStatus DISCONNECTED for unknown reason', () => { + connect({ host: 'h', port: 1 } as any, 999 as WebSocketConnectReason); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, expect.stringContaining('Unknown')); + }); +}); + +// ---------------------------------------------------------------- +// updateStatus.ts +// ---------------------------------------------------------------- +describe('updateStatus', () => { + const { updateStatus } = jest.requireActual('./updateStatus'); + + it('calls SessionPersistence.updateStatus and webClient.updateStatus', () => { + updateStatus(StatusEnum.CONNECTED, 'OK'); + expect(SessionPersistence.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTED, 'OK'); + expect(webClient.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTED); + }); +}); + +// ---------------------------------------------------------------- +// login.ts +// ---------------------------------------------------------------- +describe('login', () => { + const { login } = jest.requireActual('./login'); + + it('sends Command_Login with plain password when no salt', () => { + login({ userName: 'alice', password: 'pw' } as any); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_Login', + expect.objectContaining({ userName: 'alice', password: 'pw' }), + expect.any(Object) + ); + }); + + it('sends Command_Login with hashedPassword when salt is given', () => { + login({ userName: 'alice', password: 'pw' } as any, 'salt'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_Login', + expect.objectContaining({ hashedPassword: 'hashed_pw' }), + expect.any(Object) + ); + }); + + it('uses options.hashedPassword if provided', () => { + login({ userName: 'alice', password: 'pw', hashedPassword: 'pre_hashed' } as any, 'salt'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_Login', + expect.objectContaining({ hashedPassword: 'pre_hashed' }), + expect.any(Object) + ); + }); + + it('onSuccess dispatches buddy/ignore/user and calls listUsers/listRooms', () => { + login({ userName: 'alice', password: 'pw' } as any); + const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } }; + invokeOnSuccess(loginResp, { responseCode: 0, '.Response_Login.ext': loginResp }); + expect(SessionPersistence.updateBuddyList).toHaveBeenCalledWith([]); + expect(SessionPersistence.updateIgnoreList).toHaveBeenCalledWith([]); + expect(SessionPersistence.updateUser).toHaveBeenCalledWith({ name: 'alice' }); + expect(SessionPersistence.loginSuccessful).toHaveBeenCalled(); + expect(SessionIndexMocks.listUsers).toHaveBeenCalled(); + expect(SessionIndexMocks.listRooms).toHaveBeenCalled(); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.LOGGED_IN, 'Logged in.'); + }); + + it('onResponseCode RespClientUpdateRequired calls onLoginError', () => { + login({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(1); + expect(SessionPersistence.loginFailed).toHaveBeenCalled(); + expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); + }); + + it('onResponseCode RespWrongPassword', () => { + login({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(2); + expect(SessionPersistence.loginFailed).toHaveBeenCalled(); + }); + + it('onResponseCode RespUsernameInvalid', () => { + login({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(3); + expect(SessionPersistence.loginFailed).toHaveBeenCalled(); + }); + + it('onResponseCode RespWouldOverwriteOldSession', () => { + login({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(4); + expect(SessionPersistence.loginFailed).toHaveBeenCalled(); + }); + + it('onResponseCode RespUserIsBanned', () => { + login({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(5); + expect(SessionPersistence.loginFailed).toHaveBeenCalled(); + }); + + it('onResponseCode RespRegistrationRequired', () => { + login({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(6); + expect(SessionPersistence.loginFailed).toHaveBeenCalled(); + }); + + it('onResponseCode RespClientIdRequired', () => { + login({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(7); + expect(SessionPersistence.loginFailed).toHaveBeenCalled(); + }); + + it('onResponseCode RespContextError', () => { + login({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(8); + expect(SessionPersistence.loginFailed).toHaveBeenCalled(); + }); + + it('onResponseCode RespAccountNotActivated calls accountAwaitingActivation', () => { + login({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(9); + expect(SessionPersistence.accountAwaitingActivation).toHaveBeenCalled(); + expect(SessionPersistence.loginFailed).toHaveBeenCalled(); + }); + + it('onError calls onLoginError with unknown error message', () => { + login({ userName: 'alice', password: 'pw' } as any); + invokeOnError(999); + expect(SessionPersistence.loginFailed).toHaveBeenCalled(); + }); +}); + +// ---------------------------------------------------------------- +// register.ts +// ---------------------------------------------------------------- +describe('register', () => { + const { register } = jest.requireActual('./register'); + + it('sends Command_Register with plain password when no salt', () => { + register({ userName: 'alice', password: 'pw', email: 'a@b.com', country: 'US', realName: 'Al' } as any); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_Register', + expect.objectContaining({ userName: 'alice', password: 'pw' }), + expect.any(Object) + ); + }); + + it('uses hashedPassword when salt is provided', () => { + register({ userName: 'alice', password: 'pw' } as any, 'salt'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_Register', + expect.objectContaining({ hashedPassword: 'hashed_pw' }), + expect.any(Object) + ); + }); + + it('RespRegistrationAccepted calls login without salt and registrationSuccess', () => { + register({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(10); + expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), undefined); + expect(SessionPersistence.registrationSuccess).toHaveBeenCalled(); + }); + + it('RespRegistrationAccepted forwards salt to login', () => { + register({ userName: 'alice', password: 'pw' } as any, 'mySalt'); + invokeResponseCode(10); + expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'mySalt'); + expect(SessionPersistence.registrationSuccess).toHaveBeenCalled(); + }); + + it('RespRegistrationAcceptedNeedsActivation calls accountAwaitingActivation', () => { + register({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(11); + expect(SessionPersistence.accountAwaitingActivation).toHaveBeenCalled(); + expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); + }); + + it('RespUserAlreadyExists calls registrationUserNameError', () => { + register({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(12); + expect(SessionPersistence.registrationUserNameError).toHaveBeenCalled(); + }); + + it('RespUsernameInvalid calls registrationUserNameError', () => { + register({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(3); + expect(SessionPersistence.registrationUserNameError).toHaveBeenCalled(); + }); + + it('RespPasswordTooShort calls registrationPasswordError', () => { + register({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(13); + expect(SessionPersistence.registrationPasswordError).toHaveBeenCalled(); + }); + + it('RespEmailRequiredToRegister calls registrationRequiresEmail', () => { + register({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(14); + expect(SessionPersistence.registrationRequiresEmail).toHaveBeenCalled(); + }); + + it('RespEmailBlackListed calls registrationEmailError', () => { + register({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(15); + expect(SessionPersistence.registrationEmailError).toHaveBeenCalled(); + }); + + it('RespTooManyRequests calls registrationEmailError', () => { + register({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(16); + expect(SessionPersistence.registrationEmailError).toHaveBeenCalled(); + }); + + it('RespRegistrationDisabled calls registrationFailed', () => { + register({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(17); + expect(SessionPersistence.registrationFailed).toHaveBeenCalled(); + }); + + it('RespUserIsBanned calls registrationFailed with raw.reasonStr and raw.endTime', () => { + register({ userName: 'alice', password: 'pw' } as any); + invokeResponseCode(5, { reasonStr: 'bad user', endTime: 9999 }); + expect(SessionPersistence.registrationFailed).toHaveBeenCalledWith('bad user', 9999); + }); + + it('onError calls registrationFailed', () => { + register({ userName: 'alice', password: 'pw' } as any); + invokeOnError(); + expect(SessionPersistence.registrationFailed).toHaveBeenCalled(); + }); +}); + +// ---------------------------------------------------------------- +// activate.ts +// ---------------------------------------------------------------- +describe('activate', () => { + const { activate } = jest.requireActual('./activate'); + + it('sends Command_Activate', () => { + activate({ userName: 'alice', token: 'tok' } as any); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_Activate', expect.any(Object), expect.any(Object)); + }); + + it('RespActivationAccepted calls accountActivationSuccess and login with salt', () => { + activate({ userName: 'alice', token: 'tok' } as any, 'salt'); + invokeResponseCode(18); + expect(SessionPersistence.accountActivationSuccess).toHaveBeenCalled(); + expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'salt'); + }); + + it('onError calls accountActivationFailed and disconnect', () => { + activate({ userName: 'alice', token: 'tok' } as any); + invokeOnError(); + expect(SessionPersistence.accountActivationFailed).toHaveBeenCalled(); + expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); + }); +}); + +// ---------------------------------------------------------------- +// forgotPasswordChallenge.ts +// ---------------------------------------------------------------- +describe('forgotPasswordChallenge', () => { + const { forgotPasswordChallenge } = jest.requireActual('./forgotPasswordChallenge'); + + it('sends Command_ForgotPasswordChallenge', () => { + forgotPasswordChallenge({ userName: 'alice', email: 'a@b.com' } as any); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ForgotPasswordChallenge', expect.any(Object), expect.any(Object)); + }); + + it('onSuccess calls resetPassword and disconnect', () => { + forgotPasswordChallenge({ userName: 'alice', email: 'a@b.com' } as any); + invokeOnSuccess(); + expect(SessionPersistence.resetPassword).toHaveBeenCalled(); + expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); + }); + + it('onError calls resetPasswordFailed and disconnect', () => { + forgotPasswordChallenge({ userName: 'alice', email: 'a@b.com' } as any); + invokeOnError(); + expect(SessionPersistence.resetPasswordFailed).toHaveBeenCalled(); + expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); + }); +}); + +// ---------------------------------------------------------------- +// forgotPasswordRequest.ts +// ---------------------------------------------------------------- +describe('forgotPasswordRequest', () => { + const { forgotPasswordRequest } = jest.requireActual('./forgotPasswordRequest'); + + it('sends Command_ForgotPasswordRequest', () => { + forgotPasswordRequest({ userName: 'alice' } as any); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ForgotPasswordRequest', expect.any(Object), expect.any(Object)); + }); + + it('onSuccess with challengeEmail calls resetPasswordChallenge', () => { + forgotPasswordRequest({ userName: 'alice' } as any); + const resp = { challengeEmail: true }; + invokeOnSuccess(resp, { responseCode: 0, '.Response_ForgotPasswordRequest.ext': resp }); + expect(SessionPersistence.resetPasswordChallenge).toHaveBeenCalled(); + expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); + }); + + it('onSuccess without challengeEmail calls resetPassword', () => { + forgotPasswordRequest({ userName: 'alice' } as any); + const resp = { challengeEmail: false }; + invokeOnSuccess(resp, { responseCode: 0, '.Response_ForgotPasswordRequest.ext': resp }); + expect(SessionPersistence.resetPassword).toHaveBeenCalled(); + expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); + }); + + it('onError calls resetPasswordFailed and disconnect', () => { + forgotPasswordRequest({ userName: 'alice' } as any); + invokeOnError(); + expect(SessionPersistence.resetPasswordFailed).toHaveBeenCalled(); + expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); + }); +}); + +// ---------------------------------------------------------------- +// forgotPasswordReset.ts +// ---------------------------------------------------------------- +describe('forgotPasswordReset', () => { + const { forgotPasswordReset } = jest.requireActual('./forgotPasswordReset'); + + it('sends Command_ForgotPasswordReset with plain newPassword when no salt', () => { + forgotPasswordReset({ userName: 'alice', token: 'tok', newPassword: 'newpw' } as any); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_ForgotPasswordReset', + expect.objectContaining({ newPassword: 'newpw' }), + expect.any(Object) + ); + }); + + it('sends hashed new password when salt provided', () => { + forgotPasswordReset({ userName: 'alice', token: 'tok', newPassword: 'newpw' } as any, 'salt'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_ForgotPasswordReset', + expect.objectContaining({ hashedNewPassword: 'hashed_pw' }), + expect.any(Object) + ); + }); + + it('onSuccess calls resetPasswordSuccess and disconnect', () => { + forgotPasswordReset({ userName: 'alice', token: 'tok', newPassword: 'newpw' } as any); + invokeOnSuccess(); + expect(SessionPersistence.resetPasswordSuccess).toHaveBeenCalled(); + expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); + }); + + it('onError calls resetPasswordFailed and disconnect', () => { + forgotPasswordReset({ userName: 'alice', token: 'tok', newPassword: 'newpw' } as any); + invokeOnError(); + expect(SessionPersistence.resetPasswordFailed).toHaveBeenCalled(); + expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); + }); +}); + +// ---------------------------------------------------------------- +// requestPasswordSalt.ts +// ---------------------------------------------------------------- +describe('requestPasswordSalt', () => { + const { requestPasswordSalt } = jest.requireActual('./requestPasswordSalt'); + + it('sends Command_RequestPasswordSalt', () => { + requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_RequestPasswordSalt', expect.any(Object), expect.any(Object)); + }); + + it('onSuccess with LOGIN reason calls login', () => { + requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any); + const resp = { passwordSalt: 'salt123' }; + invokeOnSuccess(resp, { responseCode: 0, '.Response_PasswordSalt.ext': resp }); + expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'salt123'); + }); + + it('onSuccess with ACTIVATE_ACCOUNT reason calls activate', () => { + requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.ACTIVATE_ACCOUNT } as any); + const resp = { passwordSalt: 'salt123' }; + invokeOnSuccess(resp, { responseCode: 0, '.Response_PasswordSalt.ext': resp }); + expect(SessionIndexMocks.activate).toHaveBeenCalledWith(expect.any(Object), 'salt123'); + }); + + it('onSuccess with PASSWORD_RESET reason calls forgotPasswordReset', () => { + requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.PASSWORD_RESET } as any); + const resp = { passwordSalt: 'salt123' }; + invokeOnSuccess(resp, { responseCode: 0, '.Response_PasswordSalt.ext': resp }); + expect(SessionIndexMocks.forgotPasswordReset).toHaveBeenCalled(); + }); + + it('onResponseCode RespRegistrationRequired calls updateStatus and disconnect', () => { + requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any); + invokeResponseCode(6); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, expect.any(String)); + expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); + }); + + it('onResponseCode RespRegistrationRequired with ACTIVATE_ACCOUNT calls accountActivationFailed', () => { + requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.ACTIVATE_ACCOUNT } as any); + invokeResponseCode(6); + expect(SessionPersistence.accountActivationFailed).toHaveBeenCalled(); + }); + + it('onError calls updateStatus DISCONNECTED and disconnect', () => { + requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any); + invokeOnError(); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalled(); + expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); + }); + + it('onError with PASSWORD_RESET reason calls resetPasswordFailed', () => { + requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.PASSWORD_RESET } as any); + invokeOnError(); + expect(SessionPersistence.resetPasswordFailed).toHaveBeenCalled(); + }); +}); diff --git a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts new file mode 100644 index 000000000..1bd86f568 --- /dev/null +++ b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts @@ -0,0 +1,416 @@ +// Shared mock setup for session command tests + +jest.mock('../../services/BackendService', () => ({ + BackendService: { + sendSessionCommand: jest.fn(), + }, +})); + +jest.mock('../../persistence', () => { + const { makeSessionPersistenceMock } = require('../../__mocks__/sessionCommandMocks'); + return { + SessionPersistence: makeSessionPersistenceMock(), + RoomPersistence: { joinRoom: jest.fn() }, + }; +}); + +jest.mock('../../WebClient', () => { + const { makeWebClientMock } = require('../../__mocks__/sessionCommandMocks'); + return { __esModule: true, default: makeWebClientMock() }; +}); + +jest.mock('../../services/ProtoController', () => { + const { makeProtoControllerRootMock } = require('../../__mocks__/sessionCommandMocks'); + return { ProtoController: { root: makeProtoControllerRootMock() } }; +}); + +jest.mock('../../utils', () => { + const { makeUtilsMock } = require('../../__mocks__/sessionCommandMocks'); + return makeUtilsMock(); +}); + +// Mock session commands barrel to allow cross-command calls while keeping real implementations +jest.mock('./', () => { + const actual = jest.requireActual('./'); + const { makeSessionBarrelMock } = require('../../__mocks__/sessionCommandMocks'); + return { ...actual, ...makeSessionBarrelMock() }; +}); + +import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; +import { BackendService } from '../../services/BackendService'; +import { SessionPersistence } from '../../persistence'; +import { RoomPersistence } from '../../persistence'; +import webClient from '../../WebClient'; +import * as SessionCommands from './'; +import { hashPassword, generateSalt, passwordSaltSupported } from '../../utils'; + +const { invokeOnSuccess, invokeCallback } = makeCallbackHelpers( + BackendService.sendSessionCommand as jest.Mock +); + +beforeEach(() => { + jest.clearAllMocks(); + (hashPassword as jest.Mock).mockReturnValue('hashed_pw'); + (generateSalt as jest.Mock).mockReturnValue('randSalt'); + (passwordSaltSupported as jest.Mock).mockReturnValue(0); +}); + +// ---------------------------------------------------------------- + +describe('accountEdit', () => { + const { accountEdit } = jest.requireActual('./accountEdit'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_AccountEdit with correct params', () => { + accountEdit('pw', 'Alice', 'a@b.com', 'US'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_AccountEdit', + { passwordCheck: 'pw', realName: 'Alice', email: 'a@b.com', country: 'US' }, + expect.any(Object) + ); + }); + + it('calls SessionPersistence.accountEditChanged on success', () => { + accountEdit('pw', 'Alice', 'a@b.com', 'US'); + invokeOnSuccess(); + expect(SessionPersistence.accountEditChanged).toHaveBeenCalledWith('Alice', 'a@b.com', 'US'); + }); +}); + +describe('accountImage', () => { + const { accountImage } = jest.requireActual('./accountImage'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_AccountImage', () => { + const img = new Uint8Array([1, 2]); + accountImage(img); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_AccountImage', { image: img }, expect.any(Object)); + }); + + it('calls SessionPersistence.accountImageChanged on success', () => { + const img = new Uint8Array([1, 2]); + accountImage(img); + invokeOnSuccess(); + expect(SessionPersistence.accountImageChanged).toHaveBeenCalledWith(img); + }); +}); + +describe('accountPassword', () => { + const { accountPassword } = jest.requireActual('./accountPassword'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_AccountPassword', () => { + accountPassword('old', 'new', 'hashed'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_AccountPassword', + { oldPassword: 'old', newPassword: 'new', hashedNewPassword: 'hashed' }, + expect.any(Object) + ); + }); + + it('calls SessionPersistence.accountPasswordChange on success', () => { + accountPassword('old', 'new', 'hashed'); + invokeOnSuccess(); + expect(SessionPersistence.accountPasswordChange).toHaveBeenCalled(); + }); +}); + +describe('deckDel', () => { + const { deckDel } = jest.requireActual('./deckDel'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_DeckDel', () => { + deckDel(42); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_DeckDel', { deckId: 42 }, expect.any(Object)); + }); + + it('calls deleteServerDeck on success', () => { + deckDel(42); + invokeOnSuccess(); + expect(SessionPersistence.deleteServerDeck).toHaveBeenCalledWith(42); + }); +}); + +describe('deckDelDir', () => { + const { deckDelDir } = jest.requireActual('./deckDelDir'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_DeckDelDir', () => { + deckDelDir('/path'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_DeckDelDir', { path: '/path' }, expect.any(Object)); + }); + + it('calls deleteServerDeckDir on success', () => { + deckDelDir('/path'); + invokeOnSuccess(); + expect(SessionPersistence.deleteServerDeckDir).toHaveBeenCalledWith('/path'); + }); +}); + +describe('deckList', () => { + const { deckList } = jest.requireActual('./deckList'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_DeckList', () => { + deckList(); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_DeckList', {}, expect.any(Object)); + }); + + it('calls updateServerDecks on success', () => { + deckList(); + const resp = { folders: [] }; + invokeOnSuccess(resp, { responseCode: 0, '.Response_DeckList.ext': resp }); + expect(SessionPersistence.updateServerDecks).toHaveBeenCalledWith(resp); + }); +}); + +describe('deckNewDir', () => { + const { deckNewDir } = jest.requireActual('./deckNewDir'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_DeckNewDir', () => { + deckNewDir('/path', 'dir'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_DeckNewDir', { path: '/path', dirName: 'dir' }, expect.any(Object)); + }); + + it('calls createServerDeckDir on success', () => { + deckNewDir('/path', 'dir'); + invokeOnSuccess(); + expect(SessionPersistence.createServerDeckDir).toHaveBeenCalledWith('/path', 'dir'); + }); +}); + +describe('deckUpload', () => { + const { deckUpload } = jest.requireActual('./deckUpload'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_DeckUpload', () => { + deckUpload('/path', 1, 'content'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_DeckUpload', + { path: '/path', deckId: 1, deckList: 'content' }, + expect.any(Object) + ); + }); + + it('calls uploadServerDeck on success', () => { + deckUpload('/path', 1, 'content'); + const resp = { newFile: { id: 1 } }; + invokeOnSuccess(resp, { responseCode: 0, '.Response_DeckUpload.ext': resp }); + expect(SessionPersistence.uploadServerDeck).toHaveBeenCalledWith('/path', resp.newFile); + }); +}); + +describe('disconnect', () => { + const { disconnect } = jest.requireActual('./disconnect'); + beforeEach(() => jest.clearAllMocks()); + + it('calls webClient.disconnect', () => { + disconnect(); + expect(webClient.disconnect).toHaveBeenCalled(); + }); +}); + +describe('getGamesOfUser', () => { + const { getGamesOfUser } = jest.requireActual('./getGamesOfUser'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_GetGamesOfUser', () => { + getGamesOfUser('alice'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_GetGamesOfUser', { userName: 'alice' }, expect.any(Object)); + }); + + it('calls getGamesOfUser on success', () => { + getGamesOfUser('alice'); + const resp = { gameList: [] }; + invokeOnSuccess(resp, { responseCode: 0, '.Response_GetGamesOfUser.ext': resp }); + expect(SessionPersistence.getGamesOfUser).toHaveBeenCalledWith('alice', resp); + }); +}); + +describe('getUserInfo', () => { + const { getUserInfo } = jest.requireActual('./getUserInfo'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_GetUserInfo', () => { + getUserInfo('alice'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_GetUserInfo', { userName: 'alice' }, expect.any(Object)); + }); + + it('calls getUserInfo on success', () => { + getUserInfo('alice'); + const resp = { userInfo: { name: 'alice' } }; + invokeOnSuccess(resp, { responseCode: 0, '.Response_GetUserInfo.ext': resp }); + expect(SessionPersistence.getUserInfo).toHaveBeenCalledWith(resp.userInfo); + }); +}); + +describe('joinRoom', () => { + const { joinRoom } = jest.requireActual('./joinRoom'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_JoinRoom', () => { + joinRoom(5); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_JoinRoom', { roomId: 5 }, expect.any(Object)); + }); + + it('calls RoomPersistence.joinRoom on success', () => { + joinRoom(5); + const resp = { roomInfo: { roomId: 5 } }; + invokeOnSuccess(resp, { responseCode: 0, '.Response_JoinRoom.ext': resp }); + expect(RoomPersistence.joinRoom).toHaveBeenCalledWith(resp.roomInfo); + }); +}); + +describe('listRooms (command)', () => { + const { listRooms } = jest.requireActual('./listRooms'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_ListRooms', () => { + listRooms(); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ListRooms', {}, {}); + }); +}); + +describe('listUsers', () => { + const { listUsers } = jest.requireActual('./listUsers'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_ListUsers', () => { + listUsers(); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ListUsers', {}, expect.any(Object)); + }); + + it('calls SessionPersistence.updateUsers with the user list on success', () => { + listUsers(); + const resp = { userList: [{ name: 'Alice' }] }; + invokeOnSuccess(resp, { responseCode: 0, '.Response_ListUsers.ext': resp }); + expect(SessionPersistence.updateUsers).toHaveBeenCalledWith([{ name: 'Alice' }]); + }); +}); + +describe('message', () => { + const { message } = jest.requireActual('./message'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_Message', () => { + message('bob', 'hi'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_Message', { userName: 'bob', message: 'hi' }, expect.any(Object)); + }); + + it('calls directMessageSent on success', () => { + message('bob', 'hi'); + invokeOnSuccess(); + expect(SessionPersistence.directMessageSent).toHaveBeenCalledWith('bob', 'hi'); + }); +}); + +describe('ping', () => { + const { ping } = jest.requireActual('./ping'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_Ping', () => { + const pingReceived = jest.fn(); + ping(pingReceived); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_Ping', {}, expect.any(Object)); + }); + + it('calls pingReceived via onResponse', () => { + const pingReceived = jest.fn(); + ping(pingReceived); + const raw = {}; + invokeCallback('onResponse', raw); + expect(pingReceived).toHaveBeenCalledWith(raw); + }); +}); + +describe('replayDeleteMatch', () => { + const { replayDeleteMatch } = jest.requireActual('./replayDeleteMatch'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_ReplayDeleteMatch', () => { + replayDeleteMatch(7); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ReplayDeleteMatch', { gameId: 7 }, expect.any(Object)); + }); + + it('calls replayDeleteMatch on success', () => { + replayDeleteMatch(7); + invokeOnSuccess(); + expect(SessionPersistence.replayDeleteMatch).toHaveBeenCalledWith(7); + }); +}); + +describe('replayList', () => { + const { replayList } = jest.requireActual('./replayList'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_ReplayList', () => { + replayList(); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ReplayList', {}, expect.any(Object)); + }); + + it('calls replayList on success', () => { + replayList(); + const resp = { matchList: [] }; + invokeOnSuccess(resp, { responseCode: 0, '.Response_ReplayList.ext': resp }); + expect(SessionPersistence.replayList).toHaveBeenCalledWith([]); + }); +}); + +describe('replayModifyMatch', () => { + const { replayModifyMatch } = jest.requireActual('./replayModifyMatch'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_ReplayModifyMatch', () => { + replayModifyMatch(7, true); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ReplayModifyMatch', { gameId: 7, doNotHide: true }, expect.any(Object)); + }); + + it('calls replayModifyMatch on success', () => { + replayModifyMatch(7, true); + invokeOnSuccess(); + expect(SessionPersistence.replayModifyMatch).toHaveBeenCalledWith(7, true); + }); +}); + +describe('addToList / addToBuddyList / addToIgnoreList', () => { + const { addToList, addToBuddyList, addToIgnoreList } = jest.requireActual('./addToList'); + beforeEach(() => jest.clearAllMocks()); + + it('addToBuddyList sends Command_AddToList with list=buddy', () => { + addToBuddyList('alice'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_AddToList', { list: 'buddy', userName: 'alice' }, expect.any(Object)); + }); + + it('addToIgnoreList sends Command_AddToList with list=ignore', () => { + addToIgnoreList('bob'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_AddToList', { list: 'ignore', userName: 'bob' }, expect.any(Object)); + }); + + it('onSuccess calls SessionPersistence.addToList', () => { + addToList('buddy', 'alice'); + invokeOnSuccess(); + expect(SessionPersistence.addToList).toHaveBeenCalledWith('buddy', 'alice'); + }); +}); + +describe('removeFromList / removeFromBuddyList / removeFromIgnoreList', () => { + const { removeFromList, removeFromBuddyList, removeFromIgnoreList } = jest.requireActual('./removeFromList'); + beforeEach(() => jest.clearAllMocks()); + + it('removeFromBuddyList sends Command_RemoveFromList with list=buddy', () => { + removeFromBuddyList('alice'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_RemoveFromList', { list: 'buddy', userName: 'alice' }, expect.any(Object)); + }); + + it('removeFromIgnoreList sends Command_RemoveFromList with list=ignore', () => { + removeFromIgnoreList('bob'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_RemoveFromList', { list: 'ignore', userName: 'bob' }, expect.any(Object)); + }); + + it('onSuccess calls SessionPersistence.removeFromList', () => { + removeFromList('buddy', 'alice'); + invokeOnSuccess(); + expect(SessionPersistence.removeFromList).toHaveBeenCalledWith('buddy', 'alice'); + }); +}); diff --git a/webclient/src/websocket/events/common/commonEvents.spec.ts b/webclient/src/websocket/events/common/commonEvents.spec.ts new file mode 100644 index 000000000..a080dc1e4 --- /dev/null +++ b/webclient/src/websocket/events/common/commonEvents.spec.ts @@ -0,0 +1,19 @@ +jest.mock('../../persistence', () => ({ + SessionPersistence: { + playerPropertiesChanged: jest.fn(), + }, +})); + +import { SessionPersistence } from '../../persistence'; + +beforeEach(() => jest.clearAllMocks()); + +describe('playerPropertiesChanged', () => { + const { playerPropertiesChanged } = jest.requireActual('./playerPropertiesChanged'); + + it('delegates to SessionPersistence.playerPropertiesChanged', () => { + const payload = { gameId: 1, player: { playerId: 2 } } as any; + playerPropertiesChanged(payload); + expect(SessionPersistence.playerPropertiesChanged).toHaveBeenCalledWith(payload); + }); +}); diff --git a/webclient/src/websocket/events/game/gameEvents.spec.ts b/webclient/src/websocket/events/game/gameEvents.spec.ts new file mode 100644 index 000000000..34154fa7e --- /dev/null +++ b/webclient/src/websocket/events/game/gameEvents.spec.ts @@ -0,0 +1,29 @@ +jest.mock('../../persistence', () => ({ + GamePersistence: { + joinGame: jest.fn(), + leaveGame: jest.fn(), + }, +})); + +import { GamePersistence } from '../../persistence'; + +beforeEach(() => jest.clearAllMocks()); + +describe('joinGame event', () => { + const { joinGame } = jest.requireActual('./joinGame'); + + it('delegates to GamePersistence.joinGame', () => { + const data = { gameId: 5, player: { playerId: 1 } } as any; + joinGame(data); + expect(GamePersistence.joinGame).toHaveBeenCalledWith(data); + }); +}); + +describe('leaveGame event', () => { + const { leaveGame } = jest.requireActual('./leaveGame'); + + it('delegates to GamePersistence.leaveGame', () => { + leaveGame(42 as any); + expect(GamePersistence.leaveGame).toHaveBeenCalledWith(42); + }); +}); diff --git a/webclient/src/websocket/events/room/roomEvents.spec.ts b/webclient/src/websocket/events/room/roomEvents.spec.ts new file mode 100644 index 000000000..79b28aada --- /dev/null +++ b/webclient/src/websocket/events/room/roomEvents.spec.ts @@ -0,0 +1,63 @@ +jest.mock('../../persistence', () => ({ + RoomPersistence: { + userJoined: jest.fn(), + userLeft: jest.fn(), + updateGames: jest.fn(), + removeMessages: jest.fn(), + addMessage: jest.fn(), + }, +})); + +import { RoomPersistence } from '../../persistence'; + +const makeRoomEvent = (roomId: number) => ({ roomEvent: { roomId } }); + +beforeEach(() => jest.clearAllMocks()); + +describe('joinRoom room event', () => { + const { joinRoom } = jest.requireActual('./joinRoom'); + + it('calls RoomPersistence.userJoined with roomId and userInfo', () => { + const userInfo = { name: 'alice' } as any; + joinRoom({ userInfo }, makeRoomEvent(3)); + expect(RoomPersistence.userJoined).toHaveBeenCalledWith(3, userInfo); + }); +}); + +describe('leaveRoom room event', () => { + const { leaveRoom } = jest.requireActual('./leaveRoom'); + + it('calls RoomPersistence.userLeft with roomId and name', () => { + leaveRoom({ name: 'alice' }, makeRoomEvent(4)); + expect(RoomPersistence.userLeft).toHaveBeenCalledWith(4, 'alice'); + }); +}); + +describe('listGames room event', () => { + const { listGames } = jest.requireActual('./listGames'); + + it('calls RoomPersistence.updateGames with roomId and gameList', () => { + const gameList = [{ gameId: 1 }] as any; + listGames({ gameList }, makeRoomEvent(5)); + expect(RoomPersistence.updateGames).toHaveBeenCalledWith(5, gameList); + }); +}); + +describe('removeMessages room event', () => { + const { removeMessages } = jest.requireActual('./removeMessages'); + + it('calls RoomPersistence.removeMessages with roomId, name, amount', () => { + removeMessages({ name: 'bob', amount: 10 }, makeRoomEvent(6)); + expect(RoomPersistence.removeMessages).toHaveBeenCalledWith(6, 'bob', 10); + }); +}); + +describe('roomSay room event', () => { + const { roomSay } = jest.requireActual('./roomSay'); + + it('calls RoomPersistence.addMessage with roomId and message', () => { + const msg = { text: 'hello' } as any; + roomSay(msg, makeRoomEvent(7)); + expect(RoomPersistence.addMessage).toHaveBeenCalledWith(7, msg); + }); +}); diff --git a/webclient/src/websocket/events/session/sessionEvents.spec.ts b/webclient/src/websocket/events/session/sessionEvents.spec.ts new file mode 100644 index 000000000..35ad9bcaa --- /dev/null +++ b/webclient/src/websocket/events/session/sessionEvents.spec.ts @@ -0,0 +1,425 @@ +// Tests for simple session events that delegate 1:1 to SessionPersistence +// or RoomPersistence with minimal logic. + +jest.mock('../../persistence', () => ({ + SessionPersistence: { + gameJoined: jest.fn(), + notifyUser: jest.fn(), + replayAdded: jest.fn(), + serverMessage: jest.fn(), + serverShutdown: jest.fn(), + updateUsers: jest.fn(), + updateInfo: jest.fn(), + userJoined: jest.fn(), + userLeft: jest.fn(), + userMessage: jest.fn(), + addToBuddyList: jest.fn(), + addToIgnoreList: jest.fn(), + removeFromBuddyList: jest.fn(), + removeFromIgnoreList: jest.fn(), + playerPropertiesChanged: jest.fn(), + }, + RoomPersistence: { + updateRooms: jest.fn(), + }, +})); + +jest.mock('../../WebClient', () => ({ + __esModule: true, + default: { + clientOptions: { autojoinrooms: false }, + options: {}, + protocolVersion: 14, + }, +})); + +jest.mock('../../commands/session', () => ({ + joinRoom: jest.fn(), + updateStatus: jest.fn(), + disconnect: jest.fn(), + login: jest.fn(), + register: jest.fn(), + activate: jest.fn(), + requestPasswordSalt: jest.fn(), + forgotPasswordRequest: jest.fn(), + forgotPasswordChallenge: jest.fn(), + forgotPasswordReset: jest.fn(), +})); + +jest.mock('../../utils', () => ({ + generateSalt: jest.fn().mockReturnValue('newSalt'), + passwordSaltSupported: jest.fn().mockReturnValue(0), +})); + +jest.mock('../../services/ProtoController', () => ({ + ProtoController: { + root: { + Event_ConnectionClosed: { + CloseReason: { + USER_LIMIT_REACHED: 0, + TOO_MANY_CONNECTIONS: 1, + BANNED: 2, + DEMOTED: 3, + SERVER_SHUTDOWN: 4, + USERNAMEINVALID: 5, + LOGGEDINELSEWERE: 6, + OTHER: 7, + }, + }, + }, + }, +})); + +import { WebSocketConnectReason } from 'types'; + +import { SessionPersistence, RoomPersistence } from '../../persistence'; +import webClient from '../../WebClient'; +import * as SessionCmds from '../../commands/session'; +import * as Utils from '../../utils'; + +beforeEach(() => { + jest.clearAllMocks(); + (Utils.generateSalt as jest.Mock).mockReturnValue('newSalt'); + (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0); +}); + +// ---------------------------------------------------------------- +// gameJoined +// ---------------------------------------------------------------- +describe('gameJoined', () => { + const { gameJoined } = jest.requireActual('./gameJoined'); + + it('calls SessionPersistence.gameJoined', () => { + const data = { gameId: 1 } as any; + gameJoined(data); + expect(SessionPersistence.gameJoined).toHaveBeenCalledWith(data); + }); +}); + +// ---------------------------------------------------------------- +// notifyUser +// ---------------------------------------------------------------- +describe('notifyUser', () => { + const { notifyUser } = jest.requireActual('./notifyUser'); + + it('calls SessionPersistence.notifyUser', () => { + const data = { message: 'yo' } as any; + notifyUser(data); + expect(SessionPersistence.notifyUser).toHaveBeenCalledWith(data); + }); +}); + +// ---------------------------------------------------------------- +// replayAdded +// ---------------------------------------------------------------- +describe('replayAdded', () => { + const { replayAdded } = jest.requireActual('./replayAdded'); + + it('calls SessionPersistence.replayAdded with matchInfo', () => { + replayAdded({ matchInfo: { id: 42 } } as any); + expect(SessionPersistence.replayAdded).toHaveBeenCalledWith({ id: 42 }); + }); +}); + +// ---------------------------------------------------------------- +// serverCompleteList +// ---------------------------------------------------------------- +describe('serverCompleteList', () => { + const { serverCompleteList } = jest.requireActual('./serverCompleteList'); + + it('calls SessionPersistence.updateUsers and RoomPersistence.updateRooms', () => { + serverCompleteList({ userList: ['u'], roomList: ['r'] } as any); + expect(SessionPersistence.updateUsers).toHaveBeenCalledWith(['u']); + expect(RoomPersistence.updateRooms).toHaveBeenCalledWith(['r']); + }); +}); + +// ---------------------------------------------------------------- +// serverMessage +// ---------------------------------------------------------------- +describe('serverMessage', () => { + const { serverMessage } = jest.requireActual('./serverMessage'); + + it('calls SessionPersistence.serverMessage with message', () => { + serverMessage({ message: 'hello server' }); + expect(SessionPersistence.serverMessage).toHaveBeenCalledWith('hello server'); + }); +}); + +// ---------------------------------------------------------------- +// serverShutdown +// ---------------------------------------------------------------- +describe('serverShutdown', () => { + const { serverShutdown } = jest.requireActual('./serverShutdown'); + + it('calls SessionPersistence.serverShutdown', () => { + const payload = { reason: 'maintenance' } as any; + serverShutdown(payload); + expect(SessionPersistence.serverShutdown).toHaveBeenCalledWith(payload); + }); +}); + +// ---------------------------------------------------------------- +// userJoined +// ---------------------------------------------------------------- +describe('userJoined', () => { + const { userJoined } = jest.requireActual('./userJoined'); + + it('calls SessionPersistence.userJoined with userInfo', () => { + userJoined({ userInfo: { name: 'alice' } } as any); + expect(SessionPersistence.userJoined).toHaveBeenCalledWith({ name: 'alice' }); + }); +}); + +// ---------------------------------------------------------------- +// userLeft +// ---------------------------------------------------------------- +describe('userLeft', () => { + const { userLeft } = jest.requireActual('./userLeft'); + + it('calls SessionPersistence.userLeft with name', () => { + userLeft({ name: 'bob' }); + expect(SessionPersistence.userLeft).toHaveBeenCalledWith('bob'); + }); +}); + +// ---------------------------------------------------------------- +// userMessage +// ---------------------------------------------------------------- +describe('userMessage', () => { + const { userMessage } = jest.requireActual('./userMessage'); + + it('calls SessionPersistence.userMessage', () => { + const payload = { userName: 'alice', message: 'hi' } as any; + userMessage(payload); + expect(SessionPersistence.userMessage).toHaveBeenCalledWith(payload); + }); +}); + +// ---------------------------------------------------------------- +// addToList +// ---------------------------------------------------------------- +describe('addToList', () => { + const { addToList } = jest.requireActual('./addToList'); + const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + afterAll(() => logSpy.mockRestore()); + + it('buddy list → addToBuddyList', () => { + addToList({ listName: 'buddy', userInfo: { name: 'alice' } } as any); + expect(SessionPersistence.addToBuddyList).toHaveBeenCalledWith({ name: 'alice' }); + }); + + it('ignore list → addToIgnoreList', () => { + addToList({ listName: 'ignore', userInfo: { name: 'bob' } } as any); + expect(SessionPersistence.addToIgnoreList).toHaveBeenCalledWith({ name: 'bob' }); + }); + + it('unknown list → console.log', () => { + addToList({ listName: 'unknown', userInfo: {} } as any); + expect(logSpy).toHaveBeenCalled(); + }); +}); + +// ---------------------------------------------------------------- +// removeFromList +// ---------------------------------------------------------------- +describe('removeFromList', () => { + const { removeFromList } = jest.requireActual('./removeFromList'); + + it('buddy list → removeFromBuddyList', () => { + removeFromList({ listName: 'buddy', userName: 'alice' } as any); + expect(SessionPersistence.removeFromBuddyList).toHaveBeenCalledWith('alice'); + }); + + it('ignore list → removeFromIgnoreList', () => { + removeFromList({ listName: 'ignore', userName: 'bob' } as any); + expect(SessionPersistence.removeFromIgnoreList).toHaveBeenCalledWith('bob'); + }); + + it('unknown list → console.log', () => { + const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + removeFromList({ listName: 'other', userName: 'x' } as any); + expect(logSpy).toHaveBeenCalled(); + logSpy.mockRestore(); + }); +}); + +// ---------------------------------------------------------------- +// listRooms +// ---------------------------------------------------------------- +describe('listRooms', () => { + const { listRooms } = jest.requireActual('./listRooms'); + + it('calls RoomPersistence.updateRooms', () => { + listRooms({ roomList: [] }); + expect(RoomPersistence.updateRooms).toHaveBeenCalledWith([]); + }); + + it('does not call joinRoom when autojoinrooms is false', () => { + (webClient as any).clientOptions = { autojoinrooms: false }; + listRooms({ roomList: [{ autoJoin: true, roomId: 1 }] } as any); + expect(SessionCmds.joinRoom).not.toHaveBeenCalled(); + }); + + it('calls joinRoom for autoJoin rooms when autojoinrooms is true', () => { + (webClient as any).clientOptions = { autojoinrooms: true }; + listRooms({ roomList: [{ autoJoin: true, roomId: 2 }, { autoJoin: false, roomId: 3 }] } as any); + expect(SessionCmds.joinRoom).toHaveBeenCalledTimes(1); + expect(SessionCmds.joinRoom).toHaveBeenCalledWith(2); + }); +}); + +// ---------------------------------------------------------------- +// connectionClosed +// ---------------------------------------------------------------- +describe('connectionClosed', () => { + const { connectionClosed } = jest.requireActual('./connectionClosed'); + + it('uses reasonStr when provided', () => { + connectionClosed({ reason: 0, reasonStr: 'custom' } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'custom'); + }); + + it('USER_LIMIT_REACHED → specific message', () => { + connectionClosed({ reason: 0 } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalledWith( + expect.anything(), + expect.stringContaining('maximum user capacity') + ); + }); + + it('TOO_MANY_CONNECTIONS → specific message', () => { + connectionClosed({ reason: 1 } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('too many concurrent')); + }); + + it('BANNED → specific message', () => { + connectionClosed({ reason: 2 } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('banned')); + }); + + it('DEMOTED → specific message', () => { + connectionClosed({ reason: 3 } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('demoted')); + }); + + it('SERVER_SHUTDOWN → specific message', () => { + connectionClosed({ reason: 4 } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('shutdown')); + }); + + it('USERNAMEINVALID → specific message', () => { + connectionClosed({ reason: 5 } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('username')); + }); + + it('LOGGEDINELSEWERE → specific message', () => { + connectionClosed({ reason: 6 } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('logged out')); + }); + + it('OTHER → "Unknown reason"', () => { + connectionClosed({ reason: 7 } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'Unknown reason'); + }); +}); + +// ---------------------------------------------------------------- +// serverIdentification +// ---------------------------------------------------------------- +describe('serverIdentification', () => { + const { serverIdentification } = jest.requireActual('./serverIdentification'); + + beforeEach(() => { + (webClient as any).protocolVersion = 14; + (webClient as any).options = {}; + }); + + it('disconnects when protocolVersion mismatches', () => { + serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 99, serverOptions: 0 } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalled(); + expect(SessionCmds.disconnect).toHaveBeenCalled(); + }); + + it('LOGIN reason without salt → calls login', () => { + (webClient as any).options = { reason: WebSocketConnectReason.LOGIN }; + (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0); + serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); + expect(SessionCmds.login).toHaveBeenCalled(); + }); + + it('LOGIN reason with salt → calls requestPasswordSalt', () => { + (webClient as any).options = { reason: WebSocketConnectReason.LOGIN }; + (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(1); + serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any); + expect(SessionCmds.requestPasswordSalt).toHaveBeenCalled(); + }); + + it('REGISTER reason without salt → calls register with null salt', () => { + (webClient as any).options = { reason: WebSocketConnectReason.REGISTER }; + (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0); + serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); + expect(SessionCmds.register).toHaveBeenCalledWith(expect.any(Object), null); + }); + + it('REGISTER reason with salt → calls register with generated salt', () => { + (webClient as any).options = { reason: WebSocketConnectReason.REGISTER }; + (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(1); + serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any); + expect(SessionCmds.register).toHaveBeenCalledWith(expect.any(Object), 'newSalt'); + }); + + it('ACTIVATE_ACCOUNT reason without salt → calls activate', () => { + (webClient as any).options = { reason: WebSocketConnectReason.ACTIVATE_ACCOUNT }; + (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0); + serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); + expect(SessionCmds.activate).toHaveBeenCalled(); + }); + + it('ACTIVATE_ACCOUNT reason with salt → calls requestPasswordSalt', () => { + (webClient as any).options = { reason: WebSocketConnectReason.ACTIVATE_ACCOUNT }; + (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(1); + serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any); + expect(SessionCmds.requestPasswordSalt).toHaveBeenCalled(); + }); + + it('PASSWORD_RESET_REQUEST reason → calls forgotPasswordRequest', () => { + (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET_REQUEST }; + serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); + expect(SessionCmds.forgotPasswordRequest).toHaveBeenCalled(); + }); + + it('PASSWORD_RESET_CHALLENGE reason → calls forgotPasswordChallenge', () => { + (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET_CHALLENGE }; + serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); + expect(SessionCmds.forgotPasswordChallenge).toHaveBeenCalled(); + }); + + it('PASSWORD_RESET reason without salt → calls forgotPasswordReset', () => { + (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET }; + (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0); + serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); + expect(SessionCmds.forgotPasswordReset).toHaveBeenCalled(); + }); + + it('PASSWORD_RESET reason with salt → calls requestPasswordSalt', () => { + (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET }; + (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(1); + serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any); + expect(SessionCmds.requestPasswordSalt).toHaveBeenCalled(); + }); + + it('unknown reason → updateStatus DISCONNECTED and disconnect', () => { + (webClient as any).options = { reason: 999 }; + serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalled(); + expect(SessionCmds.disconnect).toHaveBeenCalled(); + }); + + it('updates webClient.options to empty and calls SessionPersistence.updateInfo', () => { + (webClient as any).options = { reason: WebSocketConnectReason.LOGIN }; + serverIdentification({ serverName: 'myServer', serverVersion: '2.0', protocolVersion: 14, serverOptions: 0 } as any); + expect(SessionPersistence.updateInfo).toHaveBeenCalledWith('myServer', '2.0'); + expect((webClient as any).options).toEqual({}); + }); +}); diff --git a/webclient/src/websocket/persistence/AdminPersistence.spec.ts b/webclient/src/websocket/persistence/AdminPersistence.spec.ts new file mode 100644 index 000000000..8e34e2baf --- /dev/null +++ b/webclient/src/websocket/persistence/AdminPersistence.spec.ts @@ -0,0 +1,37 @@ +jest.mock('store', () => ({ + ServerDispatch: { + adjustMod: jest.fn(), + reloadConfig: jest.fn(), + shutdownServer: jest.fn(), + updateServerMessage: jest.fn(), + }, +})); + +import { AdminPersistence } from './AdminPersistence'; +import { ServerDispatch } from 'store'; + +beforeEach(() => { + jest.clearAllMocks(); +}); + +describe('AdminPersistence', () => { + it('adjustMod passes userName, shouldBeMod, shouldBeJudge', () => { + AdminPersistence.adjustMod('alice', true, false); + expect(ServerDispatch.adjustMod).toHaveBeenCalledWith('alice', true, false); + }); + + it('reloadConfig -> ServerDispatch.reloadConfig', () => { + AdminPersistence.reloadConfig(); + expect(ServerDispatch.reloadConfig).toHaveBeenCalled(); + }); + + it('shutdownServer -> ServerDispatch.shutdownServer', () => { + AdminPersistence.shutdownServer(); + expect(ServerDispatch.shutdownServer).toHaveBeenCalled(); + }); + + it('updateServerMessage -> ServerDispatch.updateServerMessage', () => { + AdminPersistence.updateServerMessage(); + expect(ServerDispatch.updateServerMessage).toHaveBeenCalled(); + }); +}); diff --git a/webclient/src/websocket/persistence/GamePersistence.spec.ts b/webclient/src/websocket/persistence/GamePersistence.spec.ts new file mode 100644 index 000000000..2720b58ca --- /dev/null +++ b/webclient/src/websocket/persistence/GamePersistence.spec.ts @@ -0,0 +1,18 @@ +import { GamePersistence } from './GamePersistence'; + +describe('GamePersistence', () => { + it('joinGame logs to console', () => { + const spy = jest.spyOn(console, 'log').mockImplementation(() => {}); + const data = { playerId: 1 } as any; + GamePersistence.joinGame(data); + expect(spy).toHaveBeenCalledWith('joinGame', data); + spy.mockRestore(); + }); + + it('leaveGame logs to console', () => { + const spy = jest.spyOn(console, 'log').mockImplementation(() => {}); + GamePersistence.leaveGame(0 as any); + expect(spy).toHaveBeenCalledWith('leaveGame', 0); + spy.mockRestore(); + }); +}); diff --git a/webclient/src/websocket/persistence/ModeratorPersistence.spec.ts b/webclient/src/websocket/persistence/ModeratorPersistence.spec.ts new file mode 100644 index 000000000..317fa422c --- /dev/null +++ b/webclient/src/websocket/persistence/ModeratorPersistence.spec.ts @@ -0,0 +1,84 @@ +jest.mock('store', () => ({ + ServerDispatch: { + banFromServer: jest.fn(), + banHistory: jest.fn(), + viewLogs: jest.fn(), + warnHistory: jest.fn(), + warnListOptions: jest.fn(), + warnUser: jest.fn(), + grantReplayAccess: jest.fn(), + forceActivateUser: jest.fn(), + getAdminNotes: jest.fn(), + updateAdminNotes: jest.fn(), + }, +})); + +jest.mock('../utils/NormalizeService', () => ({ + __esModule: true, + default: { + normalizeLogs: jest.fn((logs: any) => ({ normalized: logs })), + }, +})); + +import { ModeratorPersistence } from './ModeratorPersistence'; +import { ServerDispatch } from 'store'; +import NormalizeService from '../utils/NormalizeService'; + +beforeEach(() => { + jest.clearAllMocks(); + (NormalizeService.normalizeLogs as jest.Mock).mockImplementation((logs: any) => ({ normalized: logs })); +}); + +describe('ModeratorPersistence', () => { + it('banFromServer passes userName', () => { + ModeratorPersistence.banFromServer('alice'); + expect(ServerDispatch.banFromServer).toHaveBeenCalledWith('alice'); + }); + + it('banHistory passes userName and banHistory', () => { + ModeratorPersistence.banHistory('alice', []); + expect(ServerDispatch.banHistory).toHaveBeenCalledWith('alice', []); + }); + + it('viewLogs normalizes logs and dispatches', () => { + const logs = [{ targetType: 'room' }] as any; + ModeratorPersistence.viewLogs(logs); + expect(NormalizeService.normalizeLogs).toHaveBeenCalledWith(logs); + expect(ServerDispatch.viewLogs).toHaveBeenCalledWith({ normalized: logs }); + }); + + it('warnHistory passes userName and warnHistory', () => { + ModeratorPersistence.warnHistory('bob', []); + expect(ServerDispatch.warnHistory).toHaveBeenCalledWith('bob', []); + }); + + it('warnListOptions passes warnList', () => { + ModeratorPersistence.warnListOptions([]); + expect(ServerDispatch.warnListOptions).toHaveBeenCalledWith([]); + }); + + it('warnUser passes userName', () => { + ModeratorPersistence.warnUser('carol'); + expect(ServerDispatch.warnUser).toHaveBeenCalledWith('carol'); + }); + + it('grantReplayAccess passes replayId and moderatorName', () => { + ModeratorPersistence.grantReplayAccess(10, 'mod1'); + expect(ServerDispatch.grantReplayAccess).toHaveBeenCalledWith(10, 'mod1'); + }); + + it('forceActivateUser passes usernameToActivate and moderatorName', () => { + ModeratorPersistence.forceActivateUser('user1', 'mod1'); + expect(ServerDispatch.forceActivateUser).toHaveBeenCalledWith('user1', 'mod1'); + }); + + it('getAdminNotes passes userName and notes', () => { + ModeratorPersistence.getAdminNotes('alice', 'some notes'); + expect(ServerDispatch.getAdminNotes).toHaveBeenCalledWith('alice', 'some notes'); + }); + + it('updateAdminNotes passes userName and notes', () => { + ModeratorPersistence.updateAdminNotes('alice', 'new notes'); + expect(ServerDispatch.updateAdminNotes).toHaveBeenCalledWith('alice', 'new notes'); + }); +}); diff --git a/webclient/src/websocket/persistence/RoomPersistence.spec.ts b/webclient/src/websocket/persistence/RoomPersistence.spec.ts new file mode 100644 index 000000000..40874928b --- /dev/null +++ b/webclient/src/websocket/persistence/RoomPersistence.spec.ts @@ -0,0 +1,117 @@ +jest.mock('store', () => ({ + store: { getState: jest.fn().mockReturnValue({}) }, + RoomsDispatch: { + clearStore: jest.fn(), + joinRoom: jest.fn(), + leaveRoom: jest.fn(), + updateRooms: jest.fn(), + updateGames: jest.fn(), + addMessage: jest.fn(), + userJoined: jest.fn(), + userLeft: jest.fn(), + removeMessages: jest.fn(), + gameCreated: jest.fn(), + joinedGame: jest.fn(), + }, + RoomsSelectors: { + getRoom: jest.fn(), + }, +})); + +jest.mock('../utils/NormalizeService', () => ({ + __esModule: true, + default: { + normalizeRoomInfo: jest.fn(), + normalizeGameObject: jest.fn(), + normalizeUserMessage: jest.fn(), + }, +})); + +import { RoomPersistence } from './RoomPersistence'; +import { store, RoomsDispatch, RoomsSelectors } from 'store'; +import NormalizeService from '../utils/NormalizeService'; + +beforeEach(() => { + jest.clearAllMocks(); +}); + +describe('RoomPersistence', () => { + it('clearStore -> RoomsDispatch.clearStore', () => { + RoomPersistence.clearStore(); + expect(RoomsDispatch.clearStore).toHaveBeenCalled(); + }); + + it('joinRoom normalizes and dispatches', () => { + const room = { roomId: 1 } as any; + RoomPersistence.joinRoom(room); + expect(NormalizeService.normalizeRoomInfo).toHaveBeenCalledWith(room); + expect(RoomsDispatch.joinRoom).toHaveBeenCalledWith(room); + }); + + it('leaveRoom -> RoomsDispatch.leaveRoom', () => { + RoomPersistence.leaveRoom(5); + expect(RoomsDispatch.leaveRoom).toHaveBeenCalledWith(5); + }); + + it('updateRooms -> RoomsDispatch.updateRooms', () => { + RoomPersistence.updateRooms([]); + expect(RoomsDispatch.updateRooms).toHaveBeenCalledWith([]); + }); + + describe('updateGames', () => { + it('normalizes game when gameType is missing and room exists', () => { + const game = { gameType: null, gameTypes: [1] } as any; + const room = { gametypeMap: { 1: 'Standard' } } as any; + (RoomsSelectors.getRoom as jest.Mock).mockReturnValue(room); + RoomPersistence.updateGames(1, [game]); + expect(NormalizeService.normalizeGameObject).toHaveBeenCalledWith(game, room.gametypeMap); + expect(RoomsDispatch.updateGames).toHaveBeenCalledWith(1, [game]); + }); + + it('does not normalize when game already has gameType', () => { + const game = { gameType: 'Standard' } as any; + RoomPersistence.updateGames(1, [game]); + expect(NormalizeService.normalizeGameObject).not.toHaveBeenCalled(); + }); + + it('does not normalize when room is not found', () => { + const game = { gameType: null } as any; + (RoomsSelectors.getRoom as jest.Mock).mockReturnValue(null); + RoomPersistence.updateGames(1, [game]); + expect(NormalizeService.normalizeGameObject).not.toHaveBeenCalled(); + }); + }); + + it('addMessage normalizes message and dispatches', () => { + const msg = { name: 'alice', message: 'hi' } as any; + RoomPersistence.addMessage(1, msg); + expect(NormalizeService.normalizeUserMessage).toHaveBeenCalledWith(msg); + expect(RoomsDispatch.addMessage).toHaveBeenCalledWith(1, msg); + }); + + it('userJoined -> RoomsDispatch.userJoined', () => { + const user = { name: 'bob' } as any; + RoomPersistence.userJoined(1, user); + expect(RoomsDispatch.userJoined).toHaveBeenCalledWith(1, user); + }); + + it('userLeft -> RoomsDispatch.userLeft', () => { + RoomPersistence.userLeft(1, 'bob'); + expect(RoomsDispatch.userLeft).toHaveBeenCalledWith(1, 'bob'); + }); + + it('removeMessages -> RoomsDispatch.removeMessages', () => { + RoomPersistence.removeMessages(1, 'bob', 5); + expect(RoomsDispatch.removeMessages).toHaveBeenCalledWith(1, 'bob', 5); + }); + + it('gameCreated -> RoomsDispatch.gameCreated', () => { + RoomPersistence.gameCreated(1); + expect(RoomsDispatch.gameCreated).toHaveBeenCalledWith(1); + }); + + it('joinedGame -> RoomsDispatch.joinedGame', () => { + RoomPersistence.joinedGame(1, 99); + expect(RoomsDispatch.joinedGame).toHaveBeenCalledWith(1, 99); + }); +}); diff --git a/webclient/src/websocket/persistence/SessionPersistence.spec.ts b/webclient/src/websocket/persistence/SessionPersistence.spec.ts new file mode 100644 index 000000000..d5dd8e1b5 --- /dev/null +++ b/webclient/src/websocket/persistence/SessionPersistence.spec.ts @@ -0,0 +1,395 @@ +jest.mock('store', () => ({ + ServerDispatch: { + initialized: jest.fn(), + clearStore: jest.fn(), + loginSuccessful: jest.fn(), + loginFailed: jest.fn(), + connectionClosed: jest.fn(), + connectionFailed: jest.fn(), + testConnectionSuccessful: jest.fn(), + testConnectionFailed: jest.fn(), + updateBuddyList: jest.fn(), + addToBuddyList: jest.fn(), + removeFromBuddyList: jest.fn(), + updateIgnoreList: jest.fn(), + addToIgnoreList: jest.fn(), + removeFromIgnoreList: jest.fn(), + updateInfo: jest.fn(), + updateStatus: jest.fn(), + updateUser: jest.fn(), + updateUsers: jest.fn(), + userJoined: jest.fn(), + userLeft: jest.fn(), + serverMessage: jest.fn(), + accountAwaitingActivation: jest.fn(), + accountActivationSuccess: jest.fn(), + accountActivationFailed: jest.fn(), + registrationRequiresEmail: jest.fn(), + registrationSuccess: jest.fn(), + registrationFailed: jest.fn(), + registrationEmailError: jest.fn(), + registrationPasswordError: jest.fn(), + registrationUserNameError: jest.fn(), + resetPasswordChallenge: jest.fn(), + resetPassword: jest.fn(), + resetPasswordSuccess: jest.fn(), + resetPasswordFailed: jest.fn(), + accountPasswordChange: jest.fn(), + accountEditChanged: jest.fn(), + accountImageChanged: jest.fn(), + directMessageSent: jest.fn(), + getUserInfo: jest.fn(), + notifyUser: jest.fn(), + serverShutdown: jest.fn(), + userMessage: jest.fn(), + addToList: jest.fn(), + removeFromList: jest.fn(), + deckDelete: jest.fn(), + backendDecks: jest.fn(), + deckUpload: jest.fn(), + deckNewDir: jest.fn(), + deckDelDir: jest.fn(), + replayList: jest.fn(), + replayAdded: jest.fn(), + replayModifyMatch: jest.fn(), + replayDeleteMatch: jest.fn(), + }, +})); + +jest.mock('websocket/utils', () => ({ + sanitizeHtml: jest.fn((msg: string) => `sanitized:${msg}`), +})); + +jest.mock('../utils/NormalizeService', () => ({ + __esModule: true, + default: { + normalizeBannedUserError: jest.fn((r: string, t: number) => `banned:${r}:${t}`), + }, +})); + +import { SessionPersistence } from './SessionPersistence'; +import { ServerDispatch } from 'store'; +import { sanitizeHtml } from 'websocket/utils'; +import NormalizeService from '../utils/NormalizeService'; +import { StatusEnum } from 'types'; + +beforeEach(() => { + jest.clearAllMocks(); + (sanitizeHtml as jest.Mock).mockImplementation((msg: string) => `sanitized:${msg}`); + (NormalizeService.normalizeBannedUserError as jest.Mock).mockImplementation( + (r: string, t: number) => `banned:${r}:${t}` + ); +}); + +describe('SessionPersistence', () => { + it('initialized -> ServerDispatch.initialized', () => { + SessionPersistence.initialized(); + expect(ServerDispatch.initialized).toHaveBeenCalled(); + }); + + it('clearStore -> ServerDispatch.clearStore', () => { + SessionPersistence.clearStore(); + expect(ServerDispatch.clearStore).toHaveBeenCalled(); + }); + + it('loginSuccessful passes options', () => { + const opts = { userName: 'alice' } as any; + SessionPersistence.loginSuccessful(opts); + expect(ServerDispatch.loginSuccessful).toHaveBeenCalledWith(opts); + }); + + it('loginFailed -> ServerDispatch.loginFailed', () => { + SessionPersistence.loginFailed(); + expect(ServerDispatch.loginFailed).toHaveBeenCalled(); + }); + + it('connectionClosed passes reason', () => { + SessionPersistence.connectionClosed(3); + expect(ServerDispatch.connectionClosed).toHaveBeenCalledWith(3); + }); + + it('connectionFailed -> ServerDispatch.connectionFailed', () => { + SessionPersistence.connectionFailed(); + expect(ServerDispatch.connectionFailed).toHaveBeenCalled(); + }); + + it('testConnectionSuccessful -> ServerDispatch.testConnectionSuccessful', () => { + SessionPersistence.testConnectionSuccessful(); + expect(ServerDispatch.testConnectionSuccessful).toHaveBeenCalled(); + }); + + it('testConnectionFailed -> ServerDispatch.testConnectionFailed', () => { + SessionPersistence.testConnectionFailed(); + expect(ServerDispatch.testConnectionFailed).toHaveBeenCalled(); + }); + + it('updateBuddyList passes list', () => { + SessionPersistence.updateBuddyList(['user']); + expect(ServerDispatch.updateBuddyList).toHaveBeenCalledWith(['user']); + }); + + it('addToBuddyList passes user', () => { + const user = { name: 'bob' } as any; + SessionPersistence.addToBuddyList(user); + expect(ServerDispatch.addToBuddyList).toHaveBeenCalledWith(user); + }); + + it('removeFromBuddyList passes userName', () => { + SessionPersistence.removeFromBuddyList('bob'); + expect(ServerDispatch.removeFromBuddyList).toHaveBeenCalledWith('bob'); + }); + + it('updateIgnoreList passes list', () => { + SessionPersistence.updateIgnoreList(['user']); + expect(ServerDispatch.updateIgnoreList).toHaveBeenCalledWith(['user']); + }); + + it('addToIgnoreList passes user', () => { + const user = { name: 'bob' } as any; + SessionPersistence.addToIgnoreList(user); + expect(ServerDispatch.addToIgnoreList).toHaveBeenCalledWith(user); + }); + + it('removeFromIgnoreList passes userName', () => { + SessionPersistence.removeFromIgnoreList('bob'); + expect(ServerDispatch.removeFromIgnoreList).toHaveBeenCalledWith('bob'); + }); + + it('updateInfo passes name and version', () => { + SessionPersistence.updateInfo('Server', '1.0'); + expect(ServerDispatch.updateInfo).toHaveBeenCalledWith('Server', '1.0'); + }); + + it('updateStatus dispatches status and calls connectionClosed when DISCONNECTED', () => { + SessionPersistence.updateStatus(StatusEnum.DISCONNECTED, 'bye'); + expect(ServerDispatch.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'bye'); + expect(ServerDispatch.connectionClosed).toHaveBeenCalledWith(StatusEnum.DISCONNECTED); + }); + + it('updateStatus does not call connectionClosed when not DISCONNECTED', () => { + SessionPersistence.updateStatus(StatusEnum.CONNECTED, 'hi'); + expect(ServerDispatch.connectionClosed).not.toHaveBeenCalled(); + }); + + it('updateUser passes user', () => { + const user = { name: 'alice' } as any; + SessionPersistence.updateUser(user); + expect(ServerDispatch.updateUser).toHaveBeenCalledWith(user); + }); + + it('updateUsers passes users array', () => { + SessionPersistence.updateUsers([]); + expect(ServerDispatch.updateUsers).toHaveBeenCalledWith([]); + }); + + it('userJoined passes user', () => { + const user = { name: 'carol' } as any; + SessionPersistence.userJoined(user); + expect(ServerDispatch.userJoined).toHaveBeenCalledWith(user); + }); + + it('userLeft passes userName', () => { + SessionPersistence.userLeft('carol'); + expect(ServerDispatch.userLeft).toHaveBeenCalledWith('carol'); + }); + + it('serverMessage sanitizes message', () => { + SessionPersistence.serverMessage('hello'); + expect(sanitizeHtml).toHaveBeenCalledWith('hello'); + expect(ServerDispatch.serverMessage).toHaveBeenCalledWith('sanitized:hello'); + }); + + it('accountAwaitingActivation passes options', () => { + const opts = { userName: 'u' } as any; + SessionPersistence.accountAwaitingActivation(opts); + expect(ServerDispatch.accountAwaitingActivation).toHaveBeenCalledWith(opts); + }); + + it('accountActivationSuccess -> ServerDispatch.accountActivationSuccess', () => { + SessionPersistence.accountActivationSuccess(); + expect(ServerDispatch.accountActivationSuccess).toHaveBeenCalled(); + }); + + it('accountActivationFailed -> ServerDispatch.accountActivationFailed', () => { + SessionPersistence.accountActivationFailed(); + expect(ServerDispatch.accountActivationFailed).toHaveBeenCalled(); + }); + + it('registrationRequiresEmail -> ServerDispatch.registrationRequiresEmail', () => { + SessionPersistence.registrationRequiresEmail(); + expect(ServerDispatch.registrationRequiresEmail).toHaveBeenCalled(); + }); + + it('registrationSuccess -> ServerDispatch.registrationSuccess', () => { + SessionPersistence.registrationSuccess(); + expect(ServerDispatch.registrationSuccess).toHaveBeenCalled(); + }); + + it('registrationFailed normalizes ban error when endTime is given', () => { + SessionPersistence.registrationFailed('reason', 999); + expect(NormalizeService.normalizeBannedUserError).toHaveBeenCalledWith('reason', 999); + expect(ServerDispatch.registrationFailed).toHaveBeenCalledWith('banned:reason:999'); + }); + + it('registrationFailed uses reason directly when no endTime', () => { + SessionPersistence.registrationFailed('plain reason'); + expect(ServerDispatch.registrationFailed).toHaveBeenCalledWith('plain reason'); + }); + + it('registrationEmailError passes error', () => { + SessionPersistence.registrationEmailError('bad email'); + expect(ServerDispatch.registrationEmailError).toHaveBeenCalledWith('bad email'); + }); + + it('registrationPasswordError passes error', () => { + SessionPersistence.registrationPasswordError('short password'); + expect(ServerDispatch.registrationPasswordError).toHaveBeenCalledWith('short password'); + }); + + it('registrationUserNameError passes error', () => { + SessionPersistence.registrationUserNameError('taken'); + expect(ServerDispatch.registrationUserNameError).toHaveBeenCalledWith('taken'); + }); + + it('resetPasswordChallenge -> ServerDispatch.resetPasswordChallenge', () => { + SessionPersistence.resetPasswordChallenge(); + expect(ServerDispatch.resetPasswordChallenge).toHaveBeenCalled(); + }); + + it('resetPassword -> ServerDispatch.resetPassword', () => { + SessionPersistence.resetPassword(); + expect(ServerDispatch.resetPassword).toHaveBeenCalled(); + }); + + it('resetPasswordSuccess -> ServerDispatch.resetPasswordSuccess', () => { + SessionPersistence.resetPasswordSuccess(); + expect(ServerDispatch.resetPasswordSuccess).toHaveBeenCalled(); + }); + + it('resetPasswordFailed -> ServerDispatch.resetPasswordFailed', () => { + SessionPersistence.resetPasswordFailed(); + expect(ServerDispatch.resetPasswordFailed).toHaveBeenCalled(); + }); + + it('accountPasswordChange -> ServerDispatch.accountPasswordChange', () => { + SessionPersistence.accountPasswordChange(); + expect(ServerDispatch.accountPasswordChange).toHaveBeenCalled(); + }); + + it('accountEditChanged passes fields', () => { + SessionPersistence.accountEditChanged('Alice', 'a@b.com', 'US'); + expect(ServerDispatch.accountEditChanged).toHaveBeenCalledWith({ realName: 'Alice', email: 'a@b.com', country: 'US' }); + }); + + it('accountImageChanged passes avatarBmp', () => { + const buf = new Uint8Array([1, 2, 3]); + SessionPersistence.accountImageChanged(buf); + expect(ServerDispatch.accountImageChanged).toHaveBeenCalledWith({ avatarBmp: buf }); + }); + + it('directMessageSent passes userName and message', () => { + SessionPersistence.directMessageSent('bob', 'hi'); + expect(ServerDispatch.directMessageSent).toHaveBeenCalledWith('bob', 'hi'); + }); + + it('getUserInfo passes userInfo', () => { + const user = { name: 'u' } as any; + SessionPersistence.getUserInfo(user); + expect(ServerDispatch.getUserInfo).toHaveBeenCalledWith(user); + }); + + it('getGamesOfUser logs to console', () => { + const spy = jest.spyOn(console, 'log').mockImplementation(() => {}); + SessionPersistence.getGamesOfUser('user1', {}); + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('gameJoined logs to console', () => { + const spy = jest.spyOn(console, 'log').mockImplementation(() => {}); + SessionPersistence.gameJoined({ gameInfo: {} } as any); + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('notifyUser passes notification', () => { + const notif = { type: 1 } as any; + SessionPersistence.notifyUser(notif); + expect(ServerDispatch.notifyUser).toHaveBeenCalledWith(notif); + }); + + it('playerPropertiesChanged logs to console', () => { + const spy = jest.spyOn(console, 'log').mockImplementation(() => {}); + SessionPersistence.playerPropertiesChanged({} as any); + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('serverShutdown passes data', () => { + const data = { gracePeriod: 5 } as any; + SessionPersistence.serverShutdown(data); + expect(ServerDispatch.serverShutdown).toHaveBeenCalledWith(data); + }); + + it('userMessage passes messageData', () => { + const msg = { message: 'hello' } as any; + SessionPersistence.userMessage(msg); + expect(ServerDispatch.userMessage).toHaveBeenCalledWith(msg); + }); + + it('addToList passes list and userName', () => { + SessionPersistence.addToList('buddy', 'alice'); + expect(ServerDispatch.addToList).toHaveBeenCalledWith('buddy', 'alice'); + }); + + it('removeFromList passes list and userName', () => { + SessionPersistence.removeFromList('ignore', 'bob'); + expect(ServerDispatch.removeFromList).toHaveBeenCalledWith('ignore', 'bob'); + }); + + it('deleteServerDeck passes deckId', () => { + SessionPersistence.deleteServerDeck(42); + expect(ServerDispatch.deckDelete).toHaveBeenCalledWith(42); + }); + + it('updateServerDecks passes deckList', () => { + SessionPersistence.updateServerDecks({ folders: [] } as any); + expect(ServerDispatch.backendDecks).toHaveBeenCalled(); + }); + + it('uploadServerDeck passes path and treeItem', () => { + SessionPersistence.uploadServerDeck('/path', { id: 1 } as any); + expect(ServerDispatch.deckUpload).toHaveBeenCalledWith('/path', { id: 1 }); + }); + + it('createServerDeckDir passes path and dirName', () => { + SessionPersistence.createServerDeckDir('/path', 'newdir'); + expect(ServerDispatch.deckNewDir).toHaveBeenCalledWith('/path', 'newdir'); + }); + + it('deleteServerDeckDir passes path', () => { + SessionPersistence.deleteServerDeckDir('/path'); + expect(ServerDispatch.deckDelDir).toHaveBeenCalledWith('/path'); + }); + + it('replayList passes matchList', () => { + SessionPersistence.replayList([]); + expect(ServerDispatch.replayList).toHaveBeenCalledWith([]); + }); + + it('replayAdded passes matchInfo', () => { + const match = { gameId: 1 } as any; + SessionPersistence.replayAdded(match); + expect(ServerDispatch.replayAdded).toHaveBeenCalledWith(match); + }); + + it('replayModifyMatch passes gameId and doNotHide', () => { + SessionPersistence.replayModifyMatch(7, true); + expect(ServerDispatch.replayModifyMatch).toHaveBeenCalledWith(7, true); + }); + + it('replayDeleteMatch passes gameId', () => { + SessionPersistence.replayDeleteMatch(7); + expect(ServerDispatch.replayDeleteMatch).toHaveBeenCalledWith(7); + }); +}); diff --git a/webclient/src/websocket/services/BackendService.spec.ts b/webclient/src/websocket/services/BackendService.spec.ts new file mode 100644 index 000000000..e203a3dd6 --- /dev/null +++ b/webclient/src/websocket/services/BackendService.spec.ts @@ -0,0 +1,119 @@ +import { makeMockProtoRoot } from '../__mocks__/helpers'; + +jest.mock('./ProtoController', () => ({ + ProtoController: { root: null }, +})); + +jest.mock('../WebClient', () => { + const mockProtobuf = { + sendSessionCommand: jest.fn(), + sendRoomCommand: jest.fn(), + sendModeratorCommand: jest.fn(), + sendAdminCommand: jest.fn(), + }; + return { __esModule: true, default: { protobuf: mockProtobuf } }; +}); + +import { BackendService } from './BackendService'; +import { ProtoController } from './ProtoController'; +import webClient from '../WebClient'; + +beforeEach(() => { + jest.clearAllMocks(); + ProtoController.root = makeMockProtoRoot(); + ProtoController.root['Command_Test'] = { create: jest.fn(p => ({ ...p })) }; + ProtoController.root['Command_Room'] = { create: jest.fn(p => ({ ...p })) }; + ProtoController.root['Command_Mod'] = { create: jest.fn(p => ({ ...p })) }; + ProtoController.root['Command_Admin'] = { create: jest.fn(p => ({ ...p })) }; + ProtoController.root['Response_Test'] = {}; +}); + +function captureCallback(sendFn: jest.Mock) { + return sendFn.mock.calls[0][sendFn === (webClient.protobuf as any).sendRoomCommand ? 2 : 1]; +} + +describe('BackendService', () => { + describe('send commands', () => { + it.each<[string, () => void]>([ + ['sendSessionCommand', () => BackendService.sendSessionCommand('Command_Test', { x: 1 }, {})], + ['sendRoomCommand', () => BackendService.sendRoomCommand(5, 'Command_Room', { y: 2 }, {})], + ['sendModeratorCommand', () => BackendService.sendModeratorCommand('Command_Mod', { z: 3 }, {})], + ['sendAdminCommand', () => BackendService.sendAdminCommand('Command_Admin', {}, {})], + ])('%s creates the command and delegates to protobuf', (methodName, invoke) => { + invoke(); + expect((webClient.protobuf as any)[methodName]).toHaveBeenCalled(); + }); + }); + + describe('handleResponse via non-session command callbacks', () => { + it('sendRoomCommand callback invokes handleResponse', () => { + const onSuccess = jest.fn(); + BackendService.sendRoomCommand(5, 'Command_Room', {}, { onSuccess }); + captureCallback((webClient.protobuf as any).sendRoomCommand)({ responseCode: 0 }); + expect(onSuccess).toHaveBeenCalled(); + }); + + it('sendModeratorCommand callback invokes handleResponse', () => { + const onSuccess = jest.fn(); + BackendService.sendModeratorCommand('Command_Mod', {}, { onSuccess }); + captureCallback((webClient.protobuf as any).sendModeratorCommand)({ responseCode: 0 }); + expect(onSuccess).toHaveBeenCalled(); + }); + + it('sendAdminCommand callback invokes handleResponse', () => { + const onSuccess = jest.fn(); + BackendService.sendAdminCommand('Command_Admin', {}, { onSuccess }); + captureCallback((webClient.protobuf as any).sendAdminCommand)({ responseCode: 0 }); + expect(onSuccess).toHaveBeenCalled(); + }); + }); + + describe('handleResponse (via sendSessionCommand callback)', () => { + function invokeCallback(options: any, raw: any) { + BackendService.sendSessionCommand('Command_Test', {}, options); + const cb = (webClient.protobuf as any).sendSessionCommand.mock.calls[0][1]; + cb(raw); + } + + it('calls onResponse and returns early when provided', () => { + const onResponse = jest.fn(); + const onSuccess = jest.fn(); + invokeCallback({ onResponse, onSuccess }, { responseCode: 99 }); + expect(onResponse).toHaveBeenCalled(); + expect(onSuccess).not.toHaveBeenCalled(); + }); + + it('calls onSuccess with raw when responseCode is RespOk and no responseName', () => { + const onSuccess = jest.fn(); + const raw = { responseCode: 0 }; + invokeCallback({ onSuccess }, raw); + expect(onSuccess).toHaveBeenCalledWith(raw, raw); + }); + + it('calls onSuccess with nested response when responseName is set', () => { + const onSuccess = jest.fn(); + const raw = { responseCode: 0, '.Response_Test.ext': { nested: true } }; + invokeCallback({ onSuccess, responseName: 'Response_Test' }, raw); + expect(onSuccess).toHaveBeenCalledWith({ nested: true }, raw); + }); + + it('calls onResponseCode handler when code matches', () => { + const specificHandler = jest.fn(); + invokeCallback({ onResponseCode: { 5: specificHandler } }, { responseCode: 5 }); + expect(specificHandler).toHaveBeenCalled(); + }); + + it('calls onError when responseCode is not RespOk and no specific handler', () => { + const onError = jest.fn(); + invokeCallback({ onError }, { responseCode: 99 }); + expect(onError).toHaveBeenCalledWith(99, { responseCode: 99 }); + }); + + it('logs error to console when no callbacks for non-RespOk response', () => { + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + invokeCallback({}, { responseCode: 42 }); + expect(consoleSpy).toHaveBeenCalled(); + consoleSpy.mockRestore(); + }); + }); +}); diff --git a/webclient/src/websocket/services/ProtoController.spec.ts b/webclient/src/websocket/services/ProtoController.spec.ts new file mode 100644 index 000000000..32a15a46f --- /dev/null +++ b/webclient/src/websocket/services/ProtoController.spec.ts @@ -0,0 +1,41 @@ +jest.mock('../persistence', () => ({ + SessionPersistence: { initialized: jest.fn() }, +})); + +jest.mock('../../proto-files.json', () => ['test.proto'], { virtual: true }); + +import { ProtoController } from './ProtoController'; +import { SessionPersistence } from '../persistence'; +import protobuf from 'protobufjs'; + +beforeEach(() => { + jest.clearAllMocks(); + ProtoController.root = null; + (process.env as any).PUBLIC_URL = ''; +}); + +describe('ProtoController', () => { + describe('load', () => { + it('creates a new protobuf.Root', () => { + ProtoController.load(); + expect(ProtoController.root).toBeDefined(); + }); + + it('calls initialized when callback succeeds', () => { + const loadSpy = jest.spyOn(protobuf.Root.prototype, 'load').mockImplementation( + (_files: any, _opts: any, cb: any) => cb(null) + ); + ProtoController.load(); + expect(SessionPersistence.initialized).toHaveBeenCalled(); + loadSpy.mockRestore(); + }); + + it('throws when callback receives an error', () => { + const loadSpy = jest.spyOn(protobuf.Root.prototype, 'load').mockImplementation( + (_files: any, _opts: any, cb: any) => cb(new Error('load failed')) + ); + expect(() => ProtoController.load()).toThrow('load failed'); + loadSpy.mockRestore(); + }); + }); +}); diff --git a/webclient/src/websocket/services/ProtobufService.spec.ts b/webclient/src/websocket/services/ProtobufService.spec.ts new file mode 100644 index 000000000..d0cfab31a --- /dev/null +++ b/webclient/src/websocket/services/ProtobufService.spec.ts @@ -0,0 +1,321 @@ +import { makeMockProtoRoot } from '../__mocks__/helpers'; + +jest.mock('./ProtoController', () => ({ + ProtoController: { root: null, load: jest.fn() }, +})); + +jest.mock('../commands/session', () => ({ + SessionCommands: { ping: jest.fn() }, + ping: jest.fn(), +})); + +jest.mock('../events', () => ({ + CommonEvents: {}, + GameEvents: { '.Event_Game.ext': jest.fn() }, + RoomEvents: { '.Event_Room.ext': jest.fn() }, + SessionEvents: { '.Event_Session.ext': jest.fn() }, +})); + +jest.mock('../WebClient'); + +import { ProtobufService } from './ProtobufService'; +import { ProtoController } from './ProtoController'; +import { ping as sessionPing } from '../commands/session'; + +let mockSocket: any; +let mockWebClient: any; + +beforeEach(() => { + jest.clearAllMocks(); + + ProtoController.root = makeMockProtoRoot(); + const encodeResult = { finish: jest.fn().mockReturnValue(new Uint8Array([1, 2])) }; + ProtoController.root.CommandContainer.encode = jest.fn().mockReturnValue(encodeResult); + + mockSocket = { + checkReadyState: jest.fn().mockReturnValue(true), + send: jest.fn(), + }; + + mockWebClient = { + socket: mockSocket, + }; +}); + +describe('ProtobufService', () => { + it('calls ProtoController.load on construction', () => { + new ProtobufService(mockWebClient); + expect(ProtoController.load).toHaveBeenCalled(); + }); + + describe('resetCommands', () => { + it('resets cmdId and pendingCommands', () => { + const service = new ProtobufService(mockWebClient); + // add a pending command + service.sendSessionCommand({}, jest.fn()); + expect((service as any).cmdId).toBe(1); + service.resetCommands(); + expect((service as any).cmdId).toBe(0); + expect((service as any).pendingCommands).toEqual({}); + }); + }); + + describe('sendCommand', () => { + it('increments cmdId and stores callback', () => { + const service = new ProtobufService(mockWebClient); + const cb = jest.fn(); + service.sendCommand({}, cb); + expect((service as any).cmdId).toBe(1); + expect((service as any).pendingCommands[1]).toBe(cb); + }); + + it('sends encoded data when socket is OPEN', () => { + const service = new ProtobufService(mockWebClient); + mockSocket.checkReadyState.mockReturnValue(true); + service.sendCommand({}, jest.fn()); + expect(mockSocket.send).toHaveBeenCalled(); + }); + + it('does not send when socket is not OPEN', () => { + const service = new ProtobufService(mockWebClient); + mockSocket.checkReadyState.mockReturnValue(false); + service.sendCommand({}, jest.fn()); + expect(mockSocket.send).not.toHaveBeenCalled(); + }); + }); + + describe('sendSessionCommand', () => { + it('creates a CommandContainer and calls sendCommand', () => { + const service = new ProtobufService(mockWebClient); + const cb = jest.fn(); + service.sendSessionCommand({ cmdType: 'test' }, cb); + expect(ProtoController.root.CommandContainer.create).toHaveBeenCalledWith( + expect.objectContaining({ sessionCommand: expect.anything() }) + ); + }); + + it('invokes callback with raw response when the pending command is triggered', () => { + const service = new ProtobufService(mockWebClient); + const cb = jest.fn(); + service.sendSessionCommand({ cmdType: 'test' }, cb); + + const storedCb = (service as any).pendingCommands[1]; + storedCb({ responseData: true }); + + expect(cb).toHaveBeenCalledWith({ responseData: true }); + }); + + it('does not throw when no callback is provided and pending command is triggered', () => { + const service = new ProtobufService(mockWebClient); + service.sendSessionCommand({ cmdType: 'test' }); + + const storedCb = (service as any).pendingCommands[1]; + expect(() => storedCb({ responseData: true })).not.toThrow(); + }); + }); + + describe('sendRoomCommand', () => { + it('creates a CommandContainer with roomId and calls sendCommand', () => { + const service = new ProtobufService(mockWebClient); + service.sendRoomCommand(42, { roomCmdType: 'test' }, jest.fn()); + expect(ProtoController.root.CommandContainer.create).toHaveBeenCalledWith( + expect.objectContaining({ roomId: 42 }) + ); + }); + + it('invokes callback with raw response when the pending command is triggered', () => { + const service = new ProtobufService(mockWebClient); + const cb = jest.fn(); + service.sendRoomCommand(42, { roomCmdType: 'test' }, cb); + + const storedCb = (service as any).pendingCommands[1]; + storedCb({ responseData: true }); + + expect(cb).toHaveBeenCalledWith({ responseData: true }); + }); + + it('does not throw when no callback is provided and pending command is triggered', () => { + const service = new ProtobufService(mockWebClient); + service.sendRoomCommand(42, { roomCmdType: 'test' }); + + const storedCb = (service as any).pendingCommands[1]; + expect(() => storedCb({ responseData: true })).not.toThrow(); + }); + }); + + describe('sendModeratorCommand', () => { + it('creates a CommandContainer with moderatorCommand', () => { + const service = new ProtobufService(mockWebClient); + service.sendModeratorCommand({ modCmdType: 'test' }, jest.fn()); + expect(ProtoController.root.CommandContainer.create).toHaveBeenCalledWith( + expect.objectContaining({ moderatorCommand: expect.anything() }) + ); + }); + + it('invokes callback with raw response when the pending command is triggered', () => { + const service = new ProtobufService(mockWebClient); + const cb = jest.fn(); + service.sendModeratorCommand({ modCmdType: 'test' }, cb); + + const storedCb = (service as any).pendingCommands[1]; + storedCb({ responseData: true }); + + expect(cb).toHaveBeenCalledWith({ responseData: true }); + }); + + it('does not throw when no callback is provided and pending command is triggered', () => { + const service = new ProtobufService(mockWebClient); + service.sendModeratorCommand({ modCmdType: 'test' }); + + const storedCb = (service as any).pendingCommands[1]; + expect(() => storedCb({ responseData: true })).not.toThrow(); + }); + }); + + describe('sendAdminCommand', () => { + it('creates a CommandContainer with adminCommand', () => { + const service = new ProtobufService(mockWebClient); + service.sendAdminCommand({ adminCmdType: 'test' }, jest.fn()); + expect(ProtoController.root.CommandContainer.create).toHaveBeenCalledWith( + expect.objectContaining({ adminCommand: expect.anything() }) + ); + }); + + it('invokes callback with raw response when the pending command is triggered', () => { + const service = new ProtobufService(mockWebClient); + const cb = jest.fn(); + service.sendAdminCommand({ adminCmdType: 'test' }, cb); + + const storedCb = (service as any).pendingCommands[1]; + storedCb({ responseData: true }); + + expect(cb).toHaveBeenCalledWith({ responseData: true }); + }); + + it('does not throw when no callback is provided and pending command is triggered', () => { + const service = new ProtobufService(mockWebClient); + service.sendAdminCommand({ adminCmdType: 'test' }); + + const storedCb = (service as any).pendingCommands[1]; + expect(() => storedCb({ responseData: true })).not.toThrow(); + }); + }); + + describe('sendKeepAliveCommand', () => { + it('delegates to SessionCommands.ping', () => { + const service = new ProtobufService(mockWebClient); + const pingReceived = jest.fn(); + service.sendKeepAliveCommand(pingReceived); + expect(sessionPing).toHaveBeenCalledWith(pingReceived); + }); + }); + + describe('handleMessageEvent', () => { + it('routes RESPONSE message to processServerResponse', () => { + const service = new ProtobufService(mockWebClient); + const cb = jest.fn(); + // store a callback for cmdId 1 + (service as any).cmdId = 1; + (service as any).pendingCommands[1] = cb; + + const response = { cmdId: 1 }; + ProtoController.root.ServerMessage.decode = jest.fn().mockReturnValue({ + messageType: ProtoController.root.ServerMessage.MessageType.RESPONSE, + response, + }); + + service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); + expect(cb).toHaveBeenCalledWith(response); + expect((service as any).pendingCommands[1]).toBeUndefined(); + }); + + it('routes ROOM_EVENT message', () => { + const service = new ProtobufService(mockWebClient); + const processRoomEvent = jest.spyOn(service as any, 'processRoomEvent'); + ProtoController.root.ServerMessage.decode = jest.fn().mockReturnValue({ + messageType: ProtoController.root.ServerMessage.MessageType.ROOM_EVENT, + roomEvent: { '.Event_Room.ext': {} }, + }); + service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); + expect(processRoomEvent).toHaveBeenCalled(); + }); + + it('routes SESSION_EVENT message', () => { + const service = new ProtobufService(mockWebClient); + const processSessionEvent = jest.spyOn(service as any, 'processSessionEvent'); + ProtoController.root.ServerMessage.decode = jest.fn().mockReturnValue({ + messageType: ProtoController.root.ServerMessage.MessageType.SESSION_EVENT, + sessionEvent: { '.Event_Session.ext': {} }, + }); + service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); + expect(processSessionEvent).toHaveBeenCalled(); + }); + + it('routes GAME_EVENT_CONTAINER message', () => { + const service = new ProtobufService(mockWebClient); + const processGameEvent = jest.spyOn(service as any, 'processGameEvent'); + ProtoController.root.ServerMessage.decode = jest.fn().mockReturnValue({ + messageType: ProtoController.root.ServerMessage.MessageType.GAME_EVENT_CONTAINER, + gameEvent: { '.Event_Game.ext': {} }, + }); + service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); + expect(processGameEvent).toHaveBeenCalled(); + }); + + it('logs unknown message types (default case)', () => { + const service = new ProtobufService(mockWebClient); + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + ProtoController.root.ServerMessage.decode = jest.fn().mockReturnValue({ + messageType: 'UNKNOWN_TYPE', + }); + service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); + expect(consoleSpy).toHaveBeenCalled(); + consoleSpy.mockRestore(); + }); + + it('does nothing when decoded message is null', () => { + const service = new ProtobufService(mockWebClient); + ProtoController.root.ServerMessage.decode = jest.fn().mockReturnValue(null); + expect(() => service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent)).not.toThrow(); + }); + + it('catches and logs decode errors', () => { + const service = new ProtobufService(mockWebClient); + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + ProtoController.root.ServerMessage.decode = jest.fn().mockImplementation(() => { + throw new Error('decode error'); + }); + expect(() => service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent)).not.toThrow(); + expect(consoleSpy).toHaveBeenCalled(); + consoleSpy.mockRestore(); + }); + }); + + describe('processEvent', () => { + it('calls matching event handler with payload and raw', () => { + const service = new ProtobufService(mockWebClient); + const handler = jest.fn(); + const events = { '.Event_Test.ext': handler }; + const payload = { someData: 1 }; + const response = { '.Event_Test.ext': payload }; + const raw = { extra: true }; + + (service as any).processEvent(response, events, raw); + + expect(handler).toHaveBeenCalledWith(payload, raw); + }); + + it('stops after first matching event', () => { + const service = new ProtobufService(mockWebClient); + const handler1 = jest.fn(); + const handler2 = jest.fn(); + const events = { '.Event_A.ext': handler1, '.Event_B.ext': handler2 }; + const response = { '.Event_A.ext': { x: 1 } }; + + (service as any).processEvent(response, events, {}); + + expect(handler1).toHaveBeenCalled(); + expect(handler2).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/webclient/src/websocket/services/WebSocketService.spec.ts b/webclient/src/websocket/services/WebSocketService.spec.ts new file mode 100644 index 000000000..275a88a66 --- /dev/null +++ b/webclient/src/websocket/services/WebSocketService.spec.ts @@ -0,0 +1,288 @@ +import { installMockWebSocket } from '../__mocks__/helpers'; + +jest.mock('../commands/session', () => ({ + updateStatus: jest.fn(), +})); + +jest.mock('../persistence', () => ({ + SessionPersistence: { + connectionFailed: jest.fn(), + testConnectionSuccessful: jest.fn(), + testConnectionFailed: jest.fn(), + }, +})); + +import { WebSocketService } from './WebSocketService'; +import { SessionPersistence } from '../persistence'; +import { updateStatus } from '../commands/session'; +import { StatusEnum } from 'types'; + +let MockWS: jest.Mock; +let mockInstance: ReturnType['mockInstance']; +let mockWebClient: any; + +beforeEach(() => { + jest.useFakeTimers(); + jest.clearAllMocks(); + + const installed = installMockWebSocket(); + MockWS = installed.MockWS; + mockInstance = installed.mockInstance; + + mockWebClient = { + status: StatusEnum.CONNECTED, + clientOptions: { keepalive: 1000 }, + keepAlive: jest.fn(), + }; +}); + +afterEach(() => { + jest.useRealTimers(); +}); + +describe('WebSocketService', () => { + function createConnectedService() { + const service = new WebSocketService(mockWebClient); + service.connect({ host: 'h', port: 1 } as any, 'ws'); + return service; + } + + function createTestConnectedService() { + const service = new WebSocketService(mockWebClient); + service.testConnect({ host: 'h', port: 1 } as any, 'ws'); + return service; + } + + describe('constructor', () => { + it('subscribes disconnected$ from KeepAliveService', () => { + const service = new WebSocketService(mockWebClient); + expect(service).toBeDefined(); + }); + + it('calls disconnect and updateStatus when keepAlive disconnected$ fires', () => { + const service = new WebSocketService(mockWebClient); + service.connect({ host: 'localhost', port: 8080 } as any, 'ws'); + // trigger keepAliveService.disconnected$ + (service as any).keepAliveService.disconnected$.next(); + expect(mockInstance.close).toHaveBeenCalled(); + expect(updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Connection timeout'); + }); + }); + + describe('connect', () => { + it('creates a WebSocket with wss protocol by default', () => { + const service = new WebSocketService(mockWebClient); + Object.defineProperty(window, 'location', { + value: { hostname: 'example.com' }, + writable: true, + configurable: true, + }); + service.connect({ host: 'example.com', port: 8080 } as any); + expect(MockWS).toHaveBeenCalledWith('wss://example.com:8080'); + }); + + it('switches to ws protocol when hostname is localhost', () => { + const service = new WebSocketService(mockWebClient); + Object.defineProperty(window, 'location', { + value: { hostname: 'localhost' }, + writable: true, + configurable: true, + }); + service.connect({ host: 'somehost', port: 1234 } as any); + expect(MockWS).toHaveBeenCalledWith('ws://somehost:1234'); + }); + + it('sets binaryType to arraybuffer', () => { + createConnectedService(); + expect(mockInstance.binaryType).toBe('arraybuffer'); + }); + + it('fires socket.close after keepalive timeout', () => { + createConnectedService(); + jest.advanceTimersByTime(1000); + expect(mockInstance.close).toHaveBeenCalled(); + }); + }); + + describe('socket event handlers (onopen)', () => { + it('clears the connection timeout when socket opens', () => { + const clearSpy = jest.spyOn(global, 'clearTimeout'); + createConnectedService(); + mockInstance.onopen(); + expect(clearSpy).toHaveBeenCalled(); + }); + + it('calls updateStatus CONNECTED on open', () => { + createConnectedService(); + mockInstance.onopen(); + expect(updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTED, 'Connected'); + }); + + it('starts the ping loop with the keepalive interval', () => { + const service = new WebSocketService(mockWebClient); + const startSpy = jest.spyOn((service as any).keepAliveService, 'startPingLoop'); + service.connect({ host: 'h', port: 1 } as any, 'ws'); + mockInstance.onopen(); + expect(startSpy).toHaveBeenCalledWith(1000, expect.any(Function)); + }); + + it('ping loop callback calls webClient.keepAlive', () => { + const service = new WebSocketService(mockWebClient); + const startSpy = jest.spyOn((service as any).keepAliveService, 'startPingLoop'); + service.connect({ host: 'h', port: 1 } as any, 'ws'); + mockInstance.onopen(); + const pingCb = startSpy.mock.calls[0][1]; + const done = jest.fn(); + pingCb(done); + expect(mockWebClient.keepAlive).toHaveBeenCalledWith(done); + }); + }); + + describe('socket event handlers (onclose)', () => { + it('calls updateStatus DISCONNECTED on close when not already DISCONNECTED', () => { + createConnectedService(); + mockInstance.onclose(); + expect(updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Connection Closed'); + }); + + it('does not overwrite status if already DISCONNECTED', () => { + createConnectedService(); + mockWebClient.status = StatusEnum.DISCONNECTED; + mockInstance.onclose(); + expect(updateStatus).not.toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Connection Closed'); + }); + + it('ends the ping loop on close', () => { + const service = new WebSocketService(mockWebClient); + const endSpy = jest.spyOn((service as any).keepAliveService, 'endPingLoop'); + service.connect({ host: 'h', port: 1 } as any, 'ws'); + mockInstance.onclose(); + expect(endSpy).toHaveBeenCalled(); + }); + }); + + describe('socket event handlers (onerror)', () => { + it('calls updateStatus DISCONNECTED on error', () => { + createConnectedService(); + mockInstance.onerror(); + expect(updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Connection Failed'); + }); + + it('calls SessionPersistence.connectionFailed on error', () => { + createConnectedService(); + mockInstance.onerror(); + expect(SessionPersistence.connectionFailed).toHaveBeenCalled(); + }); + }); + + describe('socket event handlers (onmessage)', () => { + it('emits on message$ subject', () => { + const service = createConnectedService(); + const handler = jest.fn(); + service.message$.subscribe(handler); + const event = { data: new ArrayBuffer(4) } as MessageEvent; + mockInstance.onmessage(event); + expect(handler).toHaveBeenCalledWith(event); + }); + }); + + describe('disconnect', () => { + it('closes the socket', () => { + const service = createConnectedService(); + service.disconnect(); + expect(mockInstance.close).toHaveBeenCalled(); + }); + }); + + describe('send', () => { + it('delegates to socket.send', () => { + const service = createConnectedService(); + const data = new Uint8Array([1, 2, 3]); + service.send(data); + expect(mockInstance.send).toHaveBeenCalledWith(data); + }); + }); + + describe('checkReadyState', () => { + it('returns true when readyState matches', () => { + const service = createConnectedService(); + mockInstance.readyState = WebSocket.OPEN; + expect(service.checkReadyState(WebSocket.OPEN)).toBe(true); + }); + + it('returns false when readyState does not match', () => { + const service = createConnectedService(); + mockInstance.readyState = 3; // CLOSED + expect(service.checkReadyState(WebSocket.OPEN)).toBe(false); + }); + + it('returns false when socket is null', () => { + const service = new WebSocketService(mockWebClient); + // no connect called, socket is undefined + expect(service.checkReadyState(WebSocket.OPEN)).toBe(false); + }); + }); + + describe('testConnect', () => { + it('creates a test WebSocket with correct URL', () => { + const service = new WebSocketService(mockWebClient); + Object.defineProperty(window, 'location', { + value: { hostname: 'example.com' }, + writable: true, + configurable: true, + }); + service.testConnect({ host: 'example.com', port: 9000 } as any); + expect(MockWS).toHaveBeenCalledWith('wss://example.com:9000'); + }); + + it('uses ws protocol on localhost', () => { + const service = new WebSocketService(mockWebClient); + Object.defineProperty(window, 'location', { + value: { hostname: 'localhost' }, + writable: true, + configurable: true, + }); + service.testConnect({ host: 'h', port: 1 } as any); + expect(MockWS).toHaveBeenCalledWith('ws://h:1'); + }); + + it('closes previous testSocket when connecting again', () => { + const service = new WebSocketService(mockWebClient); + service.testConnect({ host: 'h', port: 1 } as any, 'ws'); + const firstInstance = mockInstance; + // install second mock instance and restore after test + const installed2 = installMockWebSocket(); + service.testConnect({ host: 'h', port: 2 } as any, 'ws'); + expect(firstInstance.close).toHaveBeenCalled(); + // restore original mock so subsequent tests see a clean global + mockInstance = installed2.mockInstance; + MockWS = installed2.MockWS; + }); + + it('calls SessionPersistence.testConnectionSuccessful on open', () => { + createTestConnectedService(); + const timer = jest.spyOn(global, 'clearTimeout'); + mockInstance.onopen(); + expect(SessionPersistence.testConnectionSuccessful).toHaveBeenCalled(); + expect(mockInstance.close).toHaveBeenCalled(); + }); + + it('fires socket.close after keepalive timeout for testConnect', () => { + createTestConnectedService(); + jest.advanceTimersByTime(1000); + expect(mockInstance.close).toHaveBeenCalled(); + }); + + it('calls SessionPersistence.testConnectionFailed on error', () => { + createTestConnectedService(); + mockInstance.onerror(); + expect(SessionPersistence.testConnectionFailed).toHaveBeenCalled(); + }); + + it('nulls out testSocket on close', () => { + const service = createTestConnectedService(); + mockInstance.onclose(); + expect((service as any).testSocket).toBeNull(); + }); + }); +}); diff --git a/webclient/src/websocket/utils/NormalizeService.spec.ts b/webclient/src/websocket/utils/NormalizeService.spec.ts new file mode 100644 index 000000000..c0c20adc9 --- /dev/null +++ b/webclient/src/websocket/utils/NormalizeService.spec.ts @@ -0,0 +1,110 @@ +import NormalizeService from './NormalizeService'; + +describe('NormalizeService', () => { + describe('normalizeRoomInfo', () => { + it('builds gametypeMap from gametypeList', () => { + const roomInfo: any = { + gametypeList: [ + { gameTypeId: 1, description: 'Standard' }, + { gameTypeId: 2, description: 'Draft' }, + ], + gametypeMap: {}, + gameList: [], + }; + NormalizeService.normalizeRoomInfo(roomInfo); + expect(roomInfo.gametypeMap).toEqual({ 1: 'Standard', 2: 'Draft' }); + }); + + it('normalizes each game in gameList', () => { + const roomInfo: any = { + gametypeList: [{ gameTypeId: 5, description: 'Modern' }], + gametypeMap: {}, + gameList: [{ gameTypes: [5], description: 'My Game' }], + }; + NormalizeService.normalizeRoomInfo(roomInfo); + expect(roomInfo.gameList[0].gameType).toBe('Modern'); + }); + }); + + describe('normalizeGameObject', () => { + it('sets gameType from first element of gameTypes', () => { + const game: any = { gameTypes: [3], description: 'Test' }; + const map: any = { 3: 'Legacy' }; + NormalizeService.normalizeGameObject(game, map); + expect(game.gameType).toBe('Legacy'); + }); + + it('sets gameType to empty string when gameTypes is empty', () => { + const game: any = { gameTypes: [], description: 'Test' }; + NormalizeService.normalizeGameObject(game, {}); + expect(game.gameType).toBe(''); + }); + + it('sets gameType to empty string when gameTypes is null', () => { + const game: any = { gameTypes: null, description: 'Test' }; + NormalizeService.normalizeGameObject(game, {}); + expect(game.gameType).toBe(''); + }); + + it('sets description to empty string when description is falsy', () => { + const game: any = { gameTypes: [], description: null }; + NormalizeService.normalizeGameObject(game, {}); + expect(game.description).toBe(''); + }); + }); + + describe('normalizeLogs', () => { + it('groups logs by targetType', () => { + const logs: any[] = [ + { targetType: 'room', msg: 'a' }, + { targetType: 'chat', msg: 'b' }, + { targetType: 'room', msg: 'c' }, + ]; + const result = NormalizeService.normalizeLogs(logs); + expect(result['room']).toHaveLength(2); + expect(result['chat']).toHaveLength(1); + }); + + it('returns empty object for empty array', () => { + expect(NormalizeService.normalizeLogs([])).toEqual({}); + }); + }); + + describe('normalizeUserMessage', () => { + it('prepends username when name is present', () => { + const message: any = { name: 'Alice', message: 'hello' }; + NormalizeService.normalizeUserMessage(message); + expect(message.message).toBe('Alice: hello'); + }); + + it('does not modify message when name is absent', () => { + const message: any = { name: '', message: 'hello' }; + NormalizeService.normalizeUserMessage(message); + expect(message.message).toBe('hello'); + }); + }); + + describe('normalizeBannedUserError', () => { + it('returns permanently banned message when endTime is 0', () => { + const result = NormalizeService.normalizeBannedUserError('', 0); + expect(result).toBe('You are permanently banned'); + }); + + it('returns banned until date when endTime is given', () => { + const endTime = new Date('2030-01-01').getTime(); + const result = NormalizeService.normalizeBannedUserError('', endTime); + expect(result).toContain('You are banned until'); + expect(result).toContain(new Date(endTime).toString()); + }); + + it('appends reasonStr when provided', () => { + const result = NormalizeService.normalizeBannedUserError('bad behavior', 0); + expect(result).toContain('\n\nbad behavior'); + }); + + it('does not append when reasonStr is empty', () => { + const result = NormalizeService.normalizeBannedUserError('', 0); + expect(result).not.toContain('\n\n'); + }); + }); +}); diff --git a/webclient/src/websocket/utils/guid.util.spec.ts b/webclient/src/websocket/utils/guid.util.spec.ts new file mode 100644 index 000000000..4ed8d4955 --- /dev/null +++ b/webclient/src/websocket/utils/guid.util.spec.ts @@ -0,0 +1,19 @@ +import { guid } from './guid.util'; + +describe('guid', () => { + it('returns a string', () => { + expect(typeof guid()).toBe('string'); + }); + + it('matches UUID v4 pattern', () => { + const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; + expect(guid()).toMatch(uuidPattern); + }); + + it('returns deterministic value when Math.random is mocked', () => { + const spy = jest.spyOn(Math, 'random').mockReturnValue(0.5); + const result = guid(); + expect(result).toBe(guid()); + spy.mockRestore(); + }); +}); diff --git a/webclient/src/websocket/utils/passwordHasher.spec.ts b/webclient/src/websocket/utils/passwordHasher.spec.ts new file mode 100644 index 000000000..d4275fa43 --- /dev/null +++ b/webclient/src/websocket/utils/passwordHasher.spec.ts @@ -0,0 +1,58 @@ +import { makeMockProtoRoot } from '../__mocks__/helpers'; + +jest.mock('../services/ProtoController', () => ({ + ProtoController: { root: null }, +})); + +import { ProtoController } from '../services/ProtoController'; +import { hashPassword, generateSalt, passwordSaltSupported } from './passwordHasher'; + +beforeEach(() => { + ProtoController.root = makeMockProtoRoot(); +}); + +describe('hashPassword', () => { + it('returns a string starting with the salt', () => { + const result = hashPassword('mysalt', 'mypassword'); + expect(result.startsWith('mysalt')).toBe(true); + }); + + it('returns the same value for the same inputs (deterministic)', () => { + expect(hashPassword('salt', 'pass')).toBe(hashPassword('salt', 'pass')); + }); + + it('returns different values for different salts', () => { + expect(hashPassword('salt1', 'pass')).not.toBe(hashPassword('salt2', 'pass')); + }); + + it('returns different values for different passwords', () => { + expect(hashPassword('salt', 'pass1')).not.toBe(hashPassword('salt', 'pass2')); + }); +}); + +describe('generateSalt', () => { + it('returns a string of 16 characters', () => { + expect(generateSalt()).toHaveLength(16); + }); + + it('only contains alphanumeric characters', () => { + expect(generateSalt()).toMatch(/^[A-Za-z0-9]{16}$/); + }); + + it('returns different values on successive calls (not constant)', () => { + const salts = new Set(Array.from({ length: 10 }, () => generateSalt())); + expect(salts.size).toBeGreaterThan(1); + }); +}); + +describe('passwordSaltSupported', () => { + it('returns non-zero when SupportsPasswordHash bit is set', () => { + // SupportsPasswordHash = 2 from mock; 2 & 2 = 2 + expect(passwordSaltSupported(2)).toBeTruthy(); + }); + + it('returns zero when SupportsPasswordHash bit is not set', () => { + // 1 & 2 = 0 + expect(passwordSaltSupported(1)).toBeFalsy(); + }); +}); diff --git a/webclient/src/websocket/utils/sanitizeHtml.util.spec.ts b/webclient/src/websocket/utils/sanitizeHtml.util.spec.ts new file mode 100644 index 000000000..cea755d98 --- /dev/null +++ b/webclient/src/websocket/utils/sanitizeHtml.util.spec.ts @@ -0,0 +1,55 @@ +import { sanitizeHtml } from './sanitizeHtml.util'; + +describe('sanitizeHtml', () => { + it('passes through plain text unchanged', () => { + expect(sanitizeHtml('hello world')).toBe('hello world'); + }); + + it('allows
tag', () => { + expect(sanitizeHtml('line1
line2')).toBe('line1
line2'); + }); + + it('allows tag', () => { + expect(sanitizeHtml('bold')).toBe('bold'); + }); + + it('allows tag', () => { + expect(sanitizeHtml('')).toBe(''); + }); + + it('allows
tag', () => { + expect(sanitizeHtml('
x
')).toBe('
x
'); + }); + + it('allows tag with color attribute', () => { + expect(sanitizeHtml('text')).toBe('text'); + }); + + it('strips disallowed tag ')).toBe(''); + }); + + it('strips disallowed tag
', () => { + expect(sanitizeHtml('
content
')).toBe('content'); + }); + + it('strips disallowed attribute onclick from ', () => { + expect(sanitizeHtml('hi')).toBe('hi'); + }); + + it('adds target=_blank and rel=noopener noreferrer to tags', () => { + const result = sanitizeHtml('link'); + expect(result).toContain('target="_blank"'); + expect(result).toContain('rel="noopener noreferrer"'); + }); + + it('allows href attribute on ', () => { + const result = sanitizeHtml('link'); + expect(result).toContain('href="https://example.com"'); + }); + + it('strips disallowed schemes like javascript:', () => { + const result = sanitizeHtml('xss'); + expect(result).not.toContain('javascript:'); + }); +}); From d96d5e15898a07bb6d3ebb6471811829a3e683f6 Mon Sep 17 00:00:00 2001 From: seavor Date: Sun, 12 Apr 2026 03:03:01 -0500 Subject: [PATCH 02/38] fix build and lint --- .../websocket/__mocks__/callbackHelpers.ts | 8 ++++-- webclient/src/websocket/__mocks__/helpers.ts | 4 +-- .../commands/room/roomCommands.spec.ts | 3 +- .../session/sessionCommands-complex.spec.ts | 4 ++- .../session/sessionCommands-simple.spec.ts | 28 ++++++++++++++----- .../websocket/services/BackendService.spec.ts | 6 ++-- .../services/ProtoController.spec.ts | 4 +-- .../services/WebSocketService.spec.ts | 5 ++-- 8 files changed, 42 insertions(+), 20 deletions(-) diff --git a/webclient/src/websocket/__mocks__/callbackHelpers.ts b/webclient/src/websocket/__mocks__/callbackHelpers.ts index ffa4f04b5..964e7df6e 100644 --- a/webclient/src/websocket/__mocks__/callbackHelpers.ts +++ b/webclient/src/websocket/__mocks__/callbackHelpers.ts @@ -19,7 +19,9 @@ export function makeCallbackHelpers(mockFn: jest.Mock, optsArgIndex = 2) { function invokeResponseCode(code: number, raw: any = { responseCode: code }) { const opts = getLastSendOpts(); - if (opts?.onResponseCode?.[code]) opts.onResponseCode[code](raw); + if (opts?.onResponseCode?.[code]) { + opts.onResponseCode[code](raw); + } } function invokeOnError(code: number = 99, raw: any = {}) { @@ -28,7 +30,9 @@ export function makeCallbackHelpers(mockFn: jest.Mock, optsArgIndex = 2) { function invokeCallback(callbackName: string, ...args: any[]) { const opts = getLastSendOpts(); - if (opts?.[callbackName]) opts[callbackName](...args); + if (opts?.[callbackName]) { + opts[callbackName](...args); + } } return { getLastSendOpts, invokeOnSuccess, invokeResponseCode, invokeOnError, invokeCallback }; diff --git a/webclient/src/websocket/__mocks__/helpers.ts b/webclient/src/websocket/__mocks__/helpers.ts index 669792ea7..a511125a8 100644 --- a/webclient/src/websocket/__mocks__/helpers.ts +++ b/webclient/src/websocket/__mocks__/helpers.ts @@ -13,9 +13,9 @@ export function makeMockProtoRoot() { encode: jest.fn().mockReturnValue(encode), }, SessionCommand: { create: jest.fn(args => ({ ...args })) }, - RoomCommand: { create: jest.fn(args => ({ ...args })) }, + RoomCommand: { create: jest.fn(args => ({ ...args })) }, ModeratorCommand: { create: jest.fn(args => ({ ...args })) }, - AdminCommand: { create: jest.fn(args => ({ ...args })) }, + AdminCommand: { create: jest.fn(args => ({ ...args })) }, ServerMessage: { decode: jest.fn(), MessageType: { diff --git a/webclient/src/websocket/commands/room/roomCommands.spec.ts b/webclient/src/websocket/commands/room/roomCommands.spec.ts index 652a5d792..c2842bd19 100644 --- a/webclient/src/websocket/commands/room/roomCommands.spec.ts +++ b/webclient/src/websocket/commands/room/roomCommands.spec.ts @@ -18,7 +18,8 @@ import { RoomPersistence } from '../../persistence'; const { getLastSendOpts, invokeOnSuccess } = makeCallbackHelpers( BackendService.sendRoomCommand as jest.Mock, - 3 // sendRoomCommand(roomId, commandName, params, options) — options at index 3 + // sendRoomCommand(roomId, commandName, params, options) — options at index 3 + 3 ); beforeEach(() => jest.clearAllMocks()); diff --git a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts index c6c11c41b..edcbf6983 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts @@ -360,7 +360,9 @@ describe('forgotPasswordChallenge', () => { it('sends Command_ForgotPasswordChallenge', () => { forgotPasswordChallenge({ userName: 'alice', email: 'a@b.com' } as any); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ForgotPasswordChallenge', expect.any(Object), expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_ForgotPasswordChallenge', expect.any(Object), expect.any(Object) + ); }); it('onSuccess calls resetPassword and disconnect', () => { diff --git a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts index 1bd86f568..f6af910db 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts @@ -170,7 +170,9 @@ describe('deckNewDir', () => { it('sends Command_DeckNewDir', () => { deckNewDir('/path', 'dir'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_DeckNewDir', { path: '/path', dirName: 'dir' }, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_DeckNewDir', { path: '/path', dirName: 'dir' }, expect.any(Object) + ); }); it('calls createServerDeckDir on success', () => { @@ -295,7 +297,9 @@ describe('message', () => { it('sends Command_Message', () => { message('bob', 'hi'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_Message', { userName: 'bob', message: 'hi' }, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_Message', { userName: 'bob', message: 'hi' }, expect.any(Object) + ); }); it('calls directMessageSent on success', () => { @@ -363,7 +367,9 @@ describe('replayModifyMatch', () => { it('sends Command_ReplayModifyMatch', () => { replayModifyMatch(7, true); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ReplayModifyMatch', { gameId: 7, doNotHide: true }, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_ReplayModifyMatch', { gameId: 7, doNotHide: true }, expect.any(Object) + ); }); it('calls replayModifyMatch on success', () => { @@ -379,12 +385,16 @@ describe('addToList / addToBuddyList / addToIgnoreList', () => { it('addToBuddyList sends Command_AddToList with list=buddy', () => { addToBuddyList('alice'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_AddToList', { list: 'buddy', userName: 'alice' }, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_AddToList', { list: 'buddy', userName: 'alice' }, expect.any(Object) + ); }); it('addToIgnoreList sends Command_AddToList with list=ignore', () => { addToIgnoreList('bob'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_AddToList', { list: 'ignore', userName: 'bob' }, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_AddToList', { list: 'ignore', userName: 'bob' }, expect.any(Object) + ); }); it('onSuccess calls SessionPersistence.addToList', () => { @@ -400,12 +410,16 @@ describe('removeFromList / removeFromBuddyList / removeFromIgnoreList', () => { it('removeFromBuddyList sends Command_RemoveFromList with list=buddy', () => { removeFromBuddyList('alice'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_RemoveFromList', { list: 'buddy', userName: 'alice' }, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_RemoveFromList', { list: 'buddy', userName: 'alice' }, expect.any(Object) + ); }); it('removeFromIgnoreList sends Command_RemoveFromList with list=ignore', () => { removeFromIgnoreList('bob'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_RemoveFromList', { list: 'ignore', userName: 'bob' }, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_RemoveFromList', { list: 'ignore', userName: 'bob' }, expect.any(Object) + ); }); it('onSuccess calls SessionPersistence.removeFromList', () => { diff --git a/webclient/src/websocket/services/BackendService.spec.ts b/webclient/src/websocket/services/BackendService.spec.ts index e203a3dd6..bdc92c2a1 100644 --- a/webclient/src/websocket/services/BackendService.spec.ts +++ b/webclient/src/websocket/services/BackendService.spec.ts @@ -34,11 +34,11 @@ function captureCallback(sendFn: jest.Mock) { describe('BackendService', () => { describe('send commands', () => { - it.each<[string, () => void]>([ + it.each([ ['sendSessionCommand', () => BackendService.sendSessionCommand('Command_Test', { x: 1 }, {})], - ['sendRoomCommand', () => BackendService.sendRoomCommand(5, 'Command_Room', { y: 2 }, {})], + ['sendRoomCommand', () => BackendService.sendRoomCommand(5, 'Command_Room', { y: 2 }, {})], ['sendModeratorCommand', () => BackendService.sendModeratorCommand('Command_Mod', { z: 3 }, {})], - ['sendAdminCommand', () => BackendService.sendAdminCommand('Command_Admin', {}, {})], + ['sendAdminCommand', () => BackendService.sendAdminCommand('Command_Admin', {}, {})], ])('%s creates the command and delegates to protobuf', (methodName, invoke) => { invoke(); expect((webClient.protobuf as any)[methodName]).toHaveBeenCalled(); diff --git a/webclient/src/websocket/services/ProtoController.spec.ts b/webclient/src/websocket/services/ProtoController.spec.ts index 32a15a46f..b2460d8e4 100644 --- a/webclient/src/websocket/services/ProtoController.spec.ts +++ b/webclient/src/websocket/services/ProtoController.spec.ts @@ -23,7 +23,7 @@ describe('ProtoController', () => { it('calls initialized when callback succeeds', () => { const loadSpy = jest.spyOn(protobuf.Root.prototype, 'load').mockImplementation( - (_files: any, _opts: any, cb: any) => cb(null) + ((_files: any, _opts: any, cb: any) => cb(null)) as any ); ProtoController.load(); expect(SessionPersistence.initialized).toHaveBeenCalled(); @@ -32,7 +32,7 @@ describe('ProtoController', () => { it('throws when callback receives an error', () => { const loadSpy = jest.spyOn(protobuf.Root.prototype, 'load').mockImplementation( - (_files: any, _opts: any, cb: any) => cb(new Error('load failed')) + ((_files: any, _opts: any, cb: any) => cb(new Error('load failed'))) as any ); expect(() => ProtoController.load()).toThrow('load failed'); loadSpy.mockRestore(); diff --git a/webclient/src/websocket/services/WebSocketService.spec.ts b/webclient/src/websocket/services/WebSocketService.spec.ts index 275a88a66..828b60213 100644 --- a/webclient/src/websocket/services/WebSocketService.spec.ts +++ b/webclient/src/websocket/services/WebSocketService.spec.ts @@ -131,7 +131,7 @@ describe('WebSocketService', () => { const startSpy = jest.spyOn((service as any).keepAliveService, 'startPingLoop'); service.connect({ host: 'h', port: 1 } as any, 'ws'); mockInstance.onopen(); - const pingCb = startSpy.mock.calls[0][1]; + const pingCb = startSpy.mock.calls[0][1] as (done: Function) => void; const done = jest.fn(); pingCb(done); expect(mockWebClient.keepAlive).toHaveBeenCalledWith(done); @@ -212,7 +212,8 @@ describe('WebSocketService', () => { it('returns false when readyState does not match', () => { const service = createConnectedService(); - mockInstance.readyState = 3; // CLOSED + // CLOSED + mockInstance.readyState = 3; expect(service.checkReadyState(WebSocket.OPEN)).toBe(false); }); From 74803442d2fa9b1ae43f302629154ed8751b6552 Mon Sep 17 00:00:00 2001 From: seavor Date: Sun, 12 Apr 2026 05:05:16 -0500 Subject: [PATCH 03/38] Implement game layer from protobuf to redux --- webclient/src/store/game/game.actions.ts | 234 ++++++ webclient/src/store/game/game.dispatch.ts | 155 ++++ webclient/src/store/game/game.interfaces.ts | 59 ++ webclient/src/store/game/game.reducer.ts | 729 ++++++++++++++++++ webclient/src/store/game/game.selectors.ts | 72 ++ webclient/src/store/game/game.types.ts | 34 + webclient/src/store/game/index.ts | 6 + webclient/src/store/index.ts | 9 +- webclient/src/store/rootReducer.ts | 2 + webclient/src/types/game.ts | 458 +++++++++++ .../src/websocket/commands/game/attachCard.ts | 6 + .../commands/game/changeZoneProperties.ts | 6 + .../src/websocket/commands/game/concede.ts | 5 + .../websocket/commands/game/createArrow.ts | 6 + .../websocket/commands/game/createCounter.ts | 6 + .../websocket/commands/game/createToken.ts | 6 + .../src/websocket/commands/game/deckSelect.ts | 6 + .../src/websocket/commands/game/delCounter.ts | 6 + .../websocket/commands/game/deleteArrow.ts | 6 + .../src/websocket/commands/game/drawCards.ts | 6 + .../src/websocket/commands/game/dumpZone.ts | 6 + .../src/websocket/commands/game/flipCard.ts | 6 + .../src/websocket/commands/game/gameSay.ts | 6 + .../websocket/commands/game/incCardCounter.ts | 6 + .../src/websocket/commands/game/incCounter.ts | 6 + .../src/websocket/commands/game/index.ts | 31 + .../websocket/commands/game/kickFromGame.ts | 6 + .../src/websocket/commands/game/leaveGame.ts | 5 + .../src/websocket/commands/game/moveCard.ts | 6 + .../src/websocket/commands/game/mulligan.ts | 6 + .../src/websocket/commands/game/nextTurn.ts | 5 + .../src/websocket/commands/game/readyStart.ts | 6 + .../websocket/commands/game/revealCards.ts | 6 + .../websocket/commands/game/reverseTurn.ts | 5 + .../websocket/commands/game/setActivePhase.ts | 6 + .../websocket/commands/game/setCardAttr.ts | 6 + .../websocket/commands/game/setCardCounter.ts | 6 + .../src/websocket/commands/game/setCounter.ts | 6 + .../commands/game/setSideboardLock.ts | 6 + .../commands/game/setSideboardPlan.ts | 6 + .../src/websocket/commands/game/shuffle.ts | 6 + .../src/websocket/commands/game/undoDraw.ts | 5 + webclient/src/websocket/commands/index.ts | 1 + .../src/websocket/events/common/index.ts | 5 +- .../events/common/playerPropertiesChanged.ts | 9 +- .../src/websocket/events/game/attachCard.ts | 6 + .../events/game/changeZoneProperties.ts | 6 + .../src/websocket/events/game/createArrow.ts | 6 + .../websocket/events/game/createCounter.ts | 6 + .../src/websocket/events/game/createToken.ts | 6 + .../src/websocket/events/game/delCounter.ts | 6 + .../src/websocket/events/game/deleteArrow.ts | 6 + .../src/websocket/events/game/destroyCard.ts | 6 + .../src/websocket/events/game/drawCards.ts | 6 + .../src/websocket/events/game/dumpZone.ts | 6 + .../src/websocket/events/game/flipCard.ts | 6 + .../src/websocket/events/game/gameClosed.ts | 6 + .../websocket/events/game/gameEvents.spec.ts | 28 +- .../websocket/events/game/gameHostChanged.ts | 10 + .../src/websocket/events/game/gameSay.ts | 6 + .../websocket/events/game/gameStateChanged.ts | 6 + webclient/src/websocket/events/game/index.ts | 82 +- .../src/websocket/events/game/joinGame.ts | 6 +- webclient/src/websocket/events/game/kicked.ts | 6 + .../src/websocket/events/game/leaveGame.ts | 7 +- .../src/websocket/events/game/moveCard.ts | 6 + .../events/game/playerPropertiesChanged.ts | 6 + .../src/websocket/events/game/revealCards.ts | 6 + .../src/websocket/events/game/reverseTurn.ts | 6 + .../src/websocket/events/game/rollDie.ts | 6 + .../websocket/events/game/setActivePhase.ts | 6 + .../websocket/events/game/setActivePlayer.ts | 6 + .../src/websocket/events/game/setCardAttr.ts | 6 + .../websocket/events/game/setCardCounter.ts | 6 + .../src/websocket/events/game/setCounter.ts | 6 + .../src/websocket/events/game/shuffle.ts | 6 + .../persistence/GamePersistence.spec.ts | 27 +- .../websocket/persistence/GamePersistence.ts | 142 +++- .../persistence/SessionPersistence.spec.ts | 14 +- .../persistence/SessionPersistence.ts | 26 +- .../src/websocket/services/BackendService.ts | 10 + .../src/websocket/services/ProtobufService.ts | 50 +- 82 files changed, 2455 insertions(+), 88 deletions(-) create mode 100644 webclient/src/store/game/game.actions.ts create mode 100644 webclient/src/store/game/game.dispatch.ts create mode 100644 webclient/src/store/game/game.interfaces.ts create mode 100644 webclient/src/store/game/game.reducer.ts create mode 100644 webclient/src/store/game/game.selectors.ts create mode 100644 webclient/src/store/game/game.types.ts create mode 100644 webclient/src/store/game/index.ts create mode 100644 webclient/src/websocket/commands/game/attachCard.ts create mode 100644 webclient/src/websocket/commands/game/changeZoneProperties.ts create mode 100644 webclient/src/websocket/commands/game/concede.ts create mode 100644 webclient/src/websocket/commands/game/createArrow.ts create mode 100644 webclient/src/websocket/commands/game/createCounter.ts create mode 100644 webclient/src/websocket/commands/game/createToken.ts create mode 100644 webclient/src/websocket/commands/game/deckSelect.ts create mode 100644 webclient/src/websocket/commands/game/delCounter.ts create mode 100644 webclient/src/websocket/commands/game/deleteArrow.ts create mode 100644 webclient/src/websocket/commands/game/drawCards.ts create mode 100644 webclient/src/websocket/commands/game/dumpZone.ts create mode 100644 webclient/src/websocket/commands/game/flipCard.ts create mode 100644 webclient/src/websocket/commands/game/gameSay.ts create mode 100644 webclient/src/websocket/commands/game/incCardCounter.ts create mode 100644 webclient/src/websocket/commands/game/incCounter.ts create mode 100644 webclient/src/websocket/commands/game/index.ts create mode 100644 webclient/src/websocket/commands/game/kickFromGame.ts create mode 100644 webclient/src/websocket/commands/game/leaveGame.ts create mode 100644 webclient/src/websocket/commands/game/moveCard.ts create mode 100644 webclient/src/websocket/commands/game/mulligan.ts create mode 100644 webclient/src/websocket/commands/game/nextTurn.ts create mode 100644 webclient/src/websocket/commands/game/readyStart.ts create mode 100644 webclient/src/websocket/commands/game/revealCards.ts create mode 100644 webclient/src/websocket/commands/game/reverseTurn.ts create mode 100644 webclient/src/websocket/commands/game/setActivePhase.ts create mode 100644 webclient/src/websocket/commands/game/setCardAttr.ts create mode 100644 webclient/src/websocket/commands/game/setCardCounter.ts create mode 100644 webclient/src/websocket/commands/game/setCounter.ts create mode 100644 webclient/src/websocket/commands/game/setSideboardLock.ts create mode 100644 webclient/src/websocket/commands/game/setSideboardPlan.ts create mode 100644 webclient/src/websocket/commands/game/shuffle.ts create mode 100644 webclient/src/websocket/commands/game/undoDraw.ts create mode 100644 webclient/src/websocket/events/game/attachCard.ts create mode 100644 webclient/src/websocket/events/game/changeZoneProperties.ts create mode 100644 webclient/src/websocket/events/game/createArrow.ts create mode 100644 webclient/src/websocket/events/game/createCounter.ts create mode 100644 webclient/src/websocket/events/game/createToken.ts create mode 100644 webclient/src/websocket/events/game/delCounter.ts create mode 100644 webclient/src/websocket/events/game/deleteArrow.ts create mode 100644 webclient/src/websocket/events/game/destroyCard.ts create mode 100644 webclient/src/websocket/events/game/drawCards.ts create mode 100644 webclient/src/websocket/events/game/dumpZone.ts create mode 100644 webclient/src/websocket/events/game/flipCard.ts create mode 100644 webclient/src/websocket/events/game/gameClosed.ts create mode 100644 webclient/src/websocket/events/game/gameHostChanged.ts create mode 100644 webclient/src/websocket/events/game/gameSay.ts create mode 100644 webclient/src/websocket/events/game/gameStateChanged.ts create mode 100644 webclient/src/websocket/events/game/kicked.ts create mode 100644 webclient/src/websocket/events/game/moveCard.ts create mode 100644 webclient/src/websocket/events/game/playerPropertiesChanged.ts create mode 100644 webclient/src/websocket/events/game/revealCards.ts create mode 100644 webclient/src/websocket/events/game/reverseTurn.ts create mode 100644 webclient/src/websocket/events/game/rollDie.ts create mode 100644 webclient/src/websocket/events/game/setActivePhase.ts create mode 100644 webclient/src/websocket/events/game/setActivePlayer.ts create mode 100644 webclient/src/websocket/events/game/setCardAttr.ts create mode 100644 webclient/src/websocket/events/game/setCardCounter.ts create mode 100644 webclient/src/websocket/events/game/setCounter.ts create mode 100644 webclient/src/websocket/events/game/shuffle.ts diff --git a/webclient/src/store/game/game.actions.ts b/webclient/src/store/game/game.actions.ts new file mode 100644 index 000000000..3ae9950a6 --- /dev/null +++ b/webclient/src/store/game/game.actions.ts @@ -0,0 +1,234 @@ +import { + AttachCardData, + ChangeZonePropertiesData, + CreateArrowData, + CreateCounterData, + CreateTokenData, + DelCounterData, + DeleteArrowData, + DestroyCardData, + DrawCardsData, + DumpZoneData, + FlipCardData, + GameStateChangedData, + MoveCardData, + PlayerProperties, + RevealCardsData, + RollDieData, + SetCardAttrData, + SetCardCounterData, + SetCounterData, + ShuffleData, +} from 'types'; +import { GameEntry } from './game.interfaces'; +import { Types } from './game.types'; + +export const Actions = { + clearStore: () => ({ + type: Types.CLEAR_STORE, + }), + + gameJoined: (gameId: number, gameEntry: GameEntry) => ({ + type: Types.GAME_JOINED, + gameId, + gameEntry, + }), + + gameLeft: (gameId: number) => ({ + type: Types.GAME_LEFT, + gameId, + }), + + gameClosed: (gameId: number) => ({ + type: Types.GAME_CLOSED, + gameId, + }), + + gameHostChanged: (gameId: number, hostId: number) => ({ + type: Types.GAME_HOST_CHANGED, + gameId, + hostId, + }), + + gameStateChanged: (gameId: number, data: GameStateChangedData) => ({ + type: Types.GAME_STATE_CHANGED, + gameId, + data, + }), + + playerJoined: (gameId: number, playerProperties: PlayerProperties) => ({ + type: Types.PLAYER_JOINED, + gameId, + playerProperties, + }), + + playerLeft: (gameId: number, playerId: number, reason: number) => ({ + type: Types.PLAYER_LEFT, + gameId, + playerId, + reason, + }), + + playerPropertiesChanged: (gameId: number, playerId: number, properties: PlayerProperties) => ({ + type: Types.PLAYER_PROPERTIES_CHANGED, + gameId, + playerId, + properties, + }), + + kicked: (gameId: number) => ({ + type: Types.KICKED, + gameId, + }), + + cardMoved: (gameId: number, playerId: number, data: MoveCardData) => ({ + type: Types.CARD_MOVED, + gameId, + playerId, + data, + }), + + cardFlipped: (gameId: number, playerId: number, data: FlipCardData) => ({ + type: Types.CARD_FLIPPED, + gameId, + playerId, + data, + }), + + cardDestroyed: (gameId: number, playerId: number, data: DestroyCardData) => ({ + type: Types.CARD_DESTROYED, + gameId, + playerId, + data, + }), + + cardAttached: (gameId: number, playerId: number, data: AttachCardData) => ({ + type: Types.CARD_ATTACHED, + gameId, + playerId, + data, + }), + + tokenCreated: (gameId: number, playerId: number, data: CreateTokenData) => ({ + type: Types.TOKEN_CREATED, + gameId, + playerId, + data, + }), + + cardAttrChanged: (gameId: number, playerId: number, data: SetCardAttrData) => ({ + type: Types.CARD_ATTR_CHANGED, + gameId, + playerId, + data, + }), + + cardCounterChanged: (gameId: number, playerId: number, data: SetCardCounterData) => ({ + type: Types.CARD_COUNTER_CHANGED, + gameId, + playerId, + data, + }), + + arrowCreated: (gameId: number, playerId: number, data: CreateArrowData) => ({ + type: Types.ARROW_CREATED, + gameId, + playerId, + data, + }), + + arrowDeleted: (gameId: number, playerId: number, data: DeleteArrowData) => ({ + type: Types.ARROW_DELETED, + gameId, + playerId, + data, + }), + + counterCreated: (gameId: number, playerId: number, data: CreateCounterData) => ({ + type: Types.COUNTER_CREATED, + gameId, + playerId, + data, + }), + + counterSet: (gameId: number, playerId: number, data: SetCounterData) => ({ + type: Types.COUNTER_SET, + gameId, + playerId, + data, + }), + + counterDeleted: (gameId: number, playerId: number, data: DelCounterData) => ({ + type: Types.COUNTER_DELETED, + gameId, + playerId, + data, + }), + + cardsDrawn: (gameId: number, playerId: number, data: DrawCardsData) => ({ + type: Types.CARDS_DRAWN, + gameId, + playerId, + data, + }), + + cardsRevealed: (gameId: number, playerId: number, data: RevealCardsData) => ({ + type: Types.CARDS_REVEALED, + gameId, + playerId, + data, + }), + + zoneShuffled: (gameId: number, playerId: number, data: ShuffleData) => ({ + type: Types.ZONE_SHUFFLED, + gameId, + playerId, + data, + }), + + dieRolled: (gameId: number, playerId: number, data: RollDieData) => ({ + type: Types.DIE_ROLLED, + gameId, + playerId, + data, + }), + + activePlayerSet: (gameId: number, activePlayerId: number) => ({ + type: Types.ACTIVE_PLAYER_SET, + gameId, + activePlayerId, + }), + + activePhaseSet: (gameId: number, phase: number) => ({ + type: Types.ACTIVE_PHASE_SET, + gameId, + phase, + }), + + turnReversed: (gameId: number, reversed: boolean) => ({ + type: Types.TURN_REVERSED, + gameId, + reversed, + }), + + zoneDumped: (gameId: number, playerId: number, data: DumpZoneData) => ({ + type: Types.ZONE_DUMPED, + gameId, + playerId, + data, + }), + + zonePropertiesChanged: (gameId: number, playerId: number, data: ChangeZonePropertiesData) => ({ + type: Types.ZONE_PROPERTIES_CHANGED, + gameId, + playerId, + data, + }), + + gameSay: (gameId: number, playerId: number, message: string) => ({ + type: Types.GAME_SAY, + gameId, + playerId, + message, + }), +}; diff --git a/webclient/src/store/game/game.dispatch.ts b/webclient/src/store/game/game.dispatch.ts new file mode 100644 index 000000000..f56f6c5d7 --- /dev/null +++ b/webclient/src/store/game/game.dispatch.ts @@ -0,0 +1,155 @@ +import { + AttachCardData, + ChangeZonePropertiesData, + CreateArrowData, + CreateCounterData, + CreateTokenData, + DelCounterData, + DeleteArrowData, + DestroyCardData, + DrawCardsData, + DumpZoneData, + FlipCardData, + GameStateChangedData, + MoveCardData, + PlayerProperties, + RevealCardsData, + RollDieData, + SetCardAttrData, + SetCardCounterData, + SetCounterData, + ShuffleData, +} from 'types'; +import { store } from 'store/store'; +import { Actions } from './game.actions'; +import { GameEntry } from './game.interfaces'; + +export const Dispatch = { + clearStore: () => { + store.dispatch(Actions.clearStore()); + }, + + gameJoined: (gameId: number, gameEntry: GameEntry) => { + store.dispatch(Actions.gameJoined(gameId, gameEntry)); + }, + + gameLeft: (gameId: number) => { + store.dispatch(Actions.gameLeft(gameId)); + }, + + gameClosed: (gameId: number) => { + store.dispatch(Actions.gameClosed(gameId)); + }, + + gameHostChanged: (gameId: number, hostId: number) => { + store.dispatch(Actions.gameHostChanged(gameId, hostId)); + }, + + gameStateChanged: (gameId: number, data: GameStateChangedData) => { + store.dispatch(Actions.gameStateChanged(gameId, data)); + }, + + playerJoined: (gameId: number, playerProperties: PlayerProperties) => { + store.dispatch(Actions.playerJoined(gameId, playerProperties)); + }, + + playerLeft: (gameId: number, playerId: number, reason: number) => { + store.dispatch(Actions.playerLeft(gameId, playerId, reason)); + }, + + playerPropertiesChanged: (gameId: number, playerId: number, properties: PlayerProperties) => { + store.dispatch(Actions.playerPropertiesChanged(gameId, playerId, properties)); + }, + + kicked: (gameId: number) => { + store.dispatch(Actions.kicked(gameId)); + }, + + cardMoved: (gameId: number, playerId: number, data: MoveCardData) => { + store.dispatch(Actions.cardMoved(gameId, playerId, data)); + }, + + cardFlipped: (gameId: number, playerId: number, data: FlipCardData) => { + store.dispatch(Actions.cardFlipped(gameId, playerId, data)); + }, + + cardDestroyed: (gameId: number, playerId: number, data: DestroyCardData) => { + store.dispatch(Actions.cardDestroyed(gameId, playerId, data)); + }, + + cardAttached: (gameId: number, playerId: number, data: AttachCardData) => { + store.dispatch(Actions.cardAttached(gameId, playerId, data)); + }, + + tokenCreated: (gameId: number, playerId: number, data: CreateTokenData) => { + store.dispatch(Actions.tokenCreated(gameId, playerId, data)); + }, + + cardAttrChanged: (gameId: number, playerId: number, data: SetCardAttrData) => { + store.dispatch(Actions.cardAttrChanged(gameId, playerId, data)); + }, + + cardCounterChanged: (gameId: number, playerId: number, data: SetCardCounterData) => { + store.dispatch(Actions.cardCounterChanged(gameId, playerId, data)); + }, + + arrowCreated: (gameId: number, playerId: number, data: CreateArrowData) => { + store.dispatch(Actions.arrowCreated(gameId, playerId, data)); + }, + + arrowDeleted: (gameId: number, playerId: number, data: DeleteArrowData) => { + store.dispatch(Actions.arrowDeleted(gameId, playerId, data)); + }, + + counterCreated: (gameId: number, playerId: number, data: CreateCounterData) => { + store.dispatch(Actions.counterCreated(gameId, playerId, data)); + }, + + counterSet: (gameId: number, playerId: number, data: SetCounterData) => { + store.dispatch(Actions.counterSet(gameId, playerId, data)); + }, + + counterDeleted: (gameId: number, playerId: number, data: DelCounterData) => { + store.dispatch(Actions.counterDeleted(gameId, playerId, data)); + }, + + cardsDrawn: (gameId: number, playerId: number, data: DrawCardsData) => { + store.dispatch(Actions.cardsDrawn(gameId, playerId, data)); + }, + + cardsRevealed: (gameId: number, playerId: number, data: RevealCardsData) => { + store.dispatch(Actions.cardsRevealed(gameId, playerId, data)); + }, + + zoneShuffled: (gameId: number, playerId: number, data: ShuffleData) => { + store.dispatch(Actions.zoneShuffled(gameId, playerId, data)); + }, + + dieRolled: (gameId: number, playerId: number, data: RollDieData) => { + store.dispatch(Actions.dieRolled(gameId, playerId, data)); + }, + + activePlayerSet: (gameId: number, activePlayerId: number) => { + store.dispatch(Actions.activePlayerSet(gameId, activePlayerId)); + }, + + activePhaseSet: (gameId: number, phase: number) => { + store.dispatch(Actions.activePhaseSet(gameId, phase)); + }, + + turnReversed: (gameId: number, reversed: boolean) => { + store.dispatch(Actions.turnReversed(gameId, reversed)); + }, + + zoneDumped: (gameId: number, playerId: number, data: DumpZoneData) => { + store.dispatch(Actions.zoneDumped(gameId, playerId, data)); + }, + + zonePropertiesChanged: (gameId: number, playerId: number, data: ChangeZonePropertiesData) => { + store.dispatch(Actions.zonePropertiesChanged(gameId, playerId, data)); + }, + + gameSay: (gameId: number, playerId: number, message: string) => { + store.dispatch(Actions.gameSay(gameId, playerId, message)); + }, +}; diff --git a/webclient/src/store/game/game.interfaces.ts b/webclient/src/store/game/game.interfaces.ts new file mode 100644 index 000000000..2e5e28325 --- /dev/null +++ b/webclient/src/store/game/game.interfaces.ts @@ -0,0 +1,59 @@ +import { ArrowInfo, CardInfo, CounterInfo, PlayerProperties } from 'types'; + +export interface GamesState { + games: { [gameId: number]: GameEntry }; +} + +/** + * Full runtime state for a single active game (played or spectated). + * Keyed by gameId in GamesState so multiple concurrent games are supported. + */ +export interface GameEntry { + gameId: number; + roomId: number; + description: string; + hostId: number; + /** The playerId assigned to the local user in this game. */ + localPlayerId: number; + spectator: boolean; + judge: boolean; + started: boolean; + activePlayerId: number; + activePhase: number; + secondsElapsed: number; + reversed: boolean; + players: { [playerId: number]: PlayerEntry }; + messages: GameMessage[]; +} + +/** Normalized from ServerInfo_Player — keyed collections for O(1) lookup. */ +export interface PlayerEntry { + properties: PlayerProperties; + deckList: string; + /** Zones keyed by zone name (e.g. "hand", "deck", "table"). */ + zones: { [zoneName: string]: ZoneEntry }; + /** Player-level counters (e.g. life) keyed by counter id. */ + counters: { [counterId: number]: CounterInfo }; + /** Arrows keyed by arrow id. */ + arrows: { [arrowId: number]: ArrowInfo }; +} + +/** Normalized from ServerInfo_Zone — card list is an ordered array matching proto. */ +export interface ZoneEntry { + name: string; + /** ZoneType enum value (0=Private, 1=Public, 2=Hidden). */ + type: number; + withCoords: boolean; + /** Authoritative card count (used for hidden zones where cardList may be empty). */ + cardCount: number; + /** Ordered card list; may be empty for hidden zones with no dump active. */ + cards: CardInfo[]; + alwaysRevealTopCard: boolean; + alwaysLookAtTopCard: boolean; +} + +export interface GameMessage { + playerId: number; + message: string; + timeReceived: number; +} diff --git a/webclient/src/store/game/game.reducer.ts b/webclient/src/store/game/game.reducer.ts new file mode 100644 index 000000000..b6148fb4f --- /dev/null +++ b/webclient/src/store/game/game.reducer.ts @@ -0,0 +1,729 @@ +import { + ArrowInfo, + CardAttribute, + CardCounterInfo, + CardInfo, + CounterInfo, + PlayerInfo, + PlayerProperties, +} from 'types'; +import { GameEntry, GameMessage, GamesState, PlayerEntry, ZoneEntry } from './game.interfaces'; +import { Types } from './game.types'; + +// ── Helpers ────────────────────────────────────────────────────────────────── + +function updateGame(state: GamesState, gameId: number, updates: Partial): GamesState { + const game = state.games[gameId]; + if (!game) { + return state; + } + return { + ...state, + games: { ...state.games, [gameId]: { ...game, ...updates } }, + }; +} + +function updatePlayer( + state: GamesState, + gameId: number, + playerId: number, + updates: Partial +): GamesState { + const game = state.games[gameId]; + if (!game) { + return state; + } + const player = game.players[playerId]; + if (!player) { + return state; + } + return updateGame(state, gameId, { + players: { ...game.players, [playerId]: { ...player, ...updates } }, + }); +} + +function updateZone( + state: GamesState, + gameId: number, + playerId: number, + zoneName: string, + updates: Partial +): GamesState { + const game = state.games[gameId]; + if (!game) { + return state; + } + const player = game.players[playerId]; + if (!player) { + return state; + } + const zone = player.zones[zoneName]; + if (!zone) { + return state; + } + return updatePlayer(state, gameId, playerId, { + zones: { ...player.zones, [zoneName]: { ...zone, ...updates } }, + }); +} + +function removeGame(state: GamesState, gameId: number): GamesState { + const games = { ...state.games }; + delete games[gameId]; + return { ...state, games }; +} + +/** Converts the proto PlayerInfo[] array into the keyed PlayerEntry map used in the store. */ +function normalizePlayers(playerList: PlayerInfo[]): { [playerId: number]: PlayerEntry } { + const players: { [playerId: number]: PlayerEntry } = {}; + for (const player of playerList) { + const playerId = player.properties.playerId; + + const zones: { [zoneName: string]: ZoneEntry } = {}; + for (const zone of player.zoneList) { + zones[zone.name] = { + name: zone.name, + type: zone.type, + withCoords: zone.withCoords, + cardCount: zone.cardCount, + cards: [...zone.cardList], + alwaysRevealTopCard: zone.alwaysRevealTopCard, + alwaysLookAtTopCard: zone.alwaysLookAtTopCard, + }; + } + + const counters: { [counterId: number]: CounterInfo } = {}; + for (const counter of player.counterList) { + counters[counter.id] = counter; + } + + const arrows: { [arrowId: number]: ArrowInfo } = {}; + for (const arrow of player.arrowList) { + arrows[arrow.id] = arrow; + } + + players[playerId] = { + properties: player.properties, + deckList: player.deckList, + zones, + counters, + arrows, + }; + } + return players; +} + +function buildEmptyCard( + id: number, + name: string, + x: number, + y: number, + faceDown: boolean, + providerId: string +): CardInfo { + return { + id, + name, + x, + y, + faceDown, + tapped: false, + attacking: false, + color: '', + pt: '', + annotation: '', + destroyOnZoneChange: false, + doesntUntap: false, + counterList: [], + attachPlayerId: -1, + attachZone: '', + attachCardId: -1, + providerId, + }; +} + +// ── Initial state ───────────────────────────────────────────────────────────── + +const initialState: GamesState = { + games: {}, +}; + +// ── Reducer ─────────────────────────────────────────────────────────────────── + +export const gamesReducer = (state: GamesState = initialState, action: any): GamesState => { + switch (action.type) { + case Types.CLEAR_STORE: { + return initialState; + } + + case Types.GAME_JOINED: { + return { + ...state, + games: { ...state.games, [action.gameId]: action.gameEntry }, + }; + } + + case Types.GAME_LEFT: + case Types.GAME_CLOSED: + case Types.KICKED: { + return removeGame(state, action.gameId); + } + + case Types.GAME_HOST_CHANGED: { + return updateGame(state, action.gameId, { hostId: action.hostId }); + } + + case Types.GAME_STATE_CHANGED: { + const { gameId, data } = action; + const game = state.games[gameId]; + if (!game) { + return state; + } + + const updates: Partial = {}; + if (data.playerList?.length > 0) { + updates.players = normalizePlayers(data.playerList); + } + if (data.gameStarted !== undefined && data.gameStarted !== null) { + updates.started = data.gameStarted; + } + if (data.activePlayerId !== undefined && data.activePlayerId !== null) { + updates.activePlayerId = data.activePlayerId; + } + if (data.activePhase !== undefined && data.activePhase !== null) { + updates.activePhase = data.activePhase; + } + if (data.secondsElapsed !== undefined) { + updates.secondsElapsed = data.secondsElapsed; + } + return updateGame(state, gameId, updates); + } + + case Types.PLAYER_JOINED: { + const { gameId, playerProperties } = action; + const game = state.games[gameId]; + if (!game) { + return state; + } + const newPlayer: PlayerEntry = { + properties: playerProperties as PlayerProperties, + deckList: '', + zones: {}, + counters: {}, + arrows: {}, + }; + return updateGame(state, gameId, { + players: { ...game.players, [playerProperties.playerId]: newPlayer }, + }); + } + + case Types.PLAYER_LEFT: { + const { gameId, playerId } = action; + const game = state.games[gameId]; + if (!game) { + return state; + } + const players = { ...game.players }; + delete players[playerId]; + return updateGame(state, gameId, { players }); + } + + case Types.PLAYER_PROPERTIES_CHANGED: { + return updatePlayer(state, action.gameId, action.playerId, { + properties: action.properties, + }); + } + + // ── Card manipulation ──────────────────────────────────────────────────── + + case Types.CARD_MOVED: { + const { gameId, playerId, data } = action; + const { + cardId, + cardName, + startPlayerId, + startZone, + position, + targetPlayerId, + targetZone, + x, + y, + newCardId, + faceDown, + newCardProviderId, + } = data; + + const game = state.games[gameId]; + if (!game) { + return state; + } + + const effectiveStartPlayerId = startPlayerId >= 0 ? startPlayerId : playerId; + const sourcePlayer = game.players[effectiveStartPlayerId]; + const sourceZoneEntry = sourcePlayer?.zones[startZone]; + if (!sourcePlayer || !sourceZoneEntry) { + return state; + } + + // Locate card in source zone (by id for visible zones, by position for hidden) + let removedCard: CardInfo | undefined; + let newSourceCards: CardInfo[]; + if (cardId >= 0) { + removedCard = sourceZoneEntry.cards.find(c => c.id === cardId); + newSourceCards = sourceZoneEntry.cards.filter(c => c.id !== cardId); + } else if (position >= 0 && position < sourceZoneEntry.cards.length) { + removedCard = sourceZoneEntry.cards[position]; + newSourceCards = sourceZoneEntry.cards.filter((_, i) => i !== position); + } else { + // Hidden zone with unknown position — just decrement count + newSourceCards = sourceZoneEntry.cards; + } + + const effectiveNewId = newCardId >= 0 ? newCardId : (removedCard?.id ?? -1); + const movedCard: CardInfo = removedCard + ? { + ...removedCard, + id: effectiveNewId, + name: cardName || removedCard.name, + x, + y, + faceDown, + providerId: newCardProviderId || removedCard.providerId, + } + : buildEmptyCard(effectiveNewId, cardName, x, y, faceDown, newCardProviderId ?? ''); + + let newState = updateZone(state, gameId, effectiveStartPlayerId, startZone, { + cards: newSourceCards, + cardCount: Math.max(0, sourceZoneEntry.cardCount - 1), + }); + + const updatedGame = newState.games[gameId]; + const targetPlayer = updatedGame?.players[targetPlayerId]; + const targetZoneEntry = targetPlayer?.zones[targetZone]; + if (!targetPlayer || !targetZoneEntry) { + return newState; + } + + newState = updateZone(newState, gameId, targetPlayerId, targetZone, { + cards: [...targetZoneEntry.cards, movedCard], + cardCount: targetZoneEntry.cardCount + 1, + }); + return newState; + } + + case Types.CARD_FLIPPED: { + const { gameId, playerId, data } = action; + const { zoneName, cardId, cardName, faceDown, cardProviderId } = data; + const game = state.games[gameId]; + if (!game) { + return state; + } + const player = game.players[playerId]; + if (!player) { + return state; + } + const zone = player.zones[zoneName]; + if (!zone) { + return state; + } + + const cardIdx = zone.cards.findIndex(c => c.id === cardId); + if (cardIdx < 0) { + return state; + } + + const updatedCards = [...zone.cards]; + updatedCards[cardIdx] = { + ...updatedCards[cardIdx], + faceDown, + name: cardName || updatedCards[cardIdx].name, + providerId: cardProviderId || updatedCards[cardIdx].providerId, + }; + return updateZone(state, gameId, playerId, zoneName, { cards: updatedCards }); + } + + case Types.CARD_DESTROYED: { + const { gameId, playerId, data } = action; + const { zoneName, cardId } = data; + const game = state.games[gameId]; + if (!game) { + return state; + } + const player = game.players[playerId]; + if (!player) { + return state; + } + const zone = player.zones[zoneName]; + if (!zone) { + return state; + } + + return updateZone(state, gameId, playerId, zoneName, { + cards: zone.cards.filter(c => c.id !== cardId), + cardCount: Math.max(0, zone.cardCount - 1), + }); + } + + case Types.CARD_ATTACHED: { + const { gameId, playerId, data } = action; + const { startZone, cardId, targetPlayerId, targetZone, targetCardId } = data; + const game = state.games[gameId]; + if (!game) { + return state; + } + const player = game.players[playerId]; + if (!player) { + return state; + } + const zone = player.zones[startZone]; + if (!zone) { + return state; + } + + const cardIdx = zone.cards.findIndex(c => c.id === cardId); + if (cardIdx < 0) { + return state; + } + + const updatedCards = [...zone.cards]; + updatedCards[cardIdx] = { + ...updatedCards[cardIdx], + attachPlayerId: targetPlayerId, + attachZone: targetZone, + attachCardId: targetCardId, + }; + return updateZone(state, gameId, playerId, startZone, { cards: updatedCards }); + } + + case Types.TOKEN_CREATED: { + const { gameId, playerId, data } = action; + const { + zoneName, + cardId, + cardName, + color, + pt, + annotation, + destroyOnZoneChange, + x, + y, + cardProviderId, + faceDown, + } = data; + const game = state.games[gameId]; + if (!game) { + return state; + } + const player = game.players[playerId]; + if (!player) { + return state; + } + const zone = player.zones[zoneName]; + if (!zone) { + return state; + } + + const newCard: CardInfo = { + id: cardId, + name: cardName, + x, + y, + faceDown, + tapped: false, + attacking: false, + color, + pt, + annotation, + destroyOnZoneChange, + doesntUntap: false, + counterList: [], + attachPlayerId: -1, + attachZone: '', + attachCardId: -1, + providerId: cardProviderId, + }; + return updateZone(state, gameId, playerId, zoneName, { + cards: [...zone.cards, newCard], + cardCount: zone.cardCount + 1, + }); + } + + case Types.CARD_ATTR_CHANGED: { + const { gameId, playerId, data } = action; + const { zoneName, cardId, attribute, attrValue } = data; + const game = state.games[gameId]; + if (!game) { + return state; + } + const player = game.players[playerId]; + if (!player) { + return state; + } + const zone = player.zones[zoneName]; + if (!zone) { + return state; + } + + const cardIdx = zone.cards.findIndex(c => c.id === cardId); + if (cardIdx < 0) { + return state; + } + + const attrPatch: Partial = {}; + switch (attribute as CardAttribute) { + case CardAttribute.AttrTapped: attrPatch.tapped = attrValue === '1'; break; + case CardAttribute.AttrAttacking: attrPatch.attacking = attrValue === '1'; break; + case CardAttribute.AttrFaceDown: attrPatch.faceDown = attrValue === '1'; break; + case CardAttribute.AttrColor: attrPatch.color = attrValue; break; + case CardAttribute.AttrPT: attrPatch.pt = attrValue; break; + case CardAttribute.AttrAnnotation: attrPatch.annotation = attrValue; break; + case CardAttribute.AttrDoesntUntap: attrPatch.doesntUntap = attrValue === '1'; break; + } + + const updatedCards = [...zone.cards]; + updatedCards[cardIdx] = { ...updatedCards[cardIdx], ...attrPatch }; + return updateZone(state, gameId, playerId, zoneName, { cards: updatedCards }); + } + + case Types.CARD_COUNTER_CHANGED: { + const { gameId, playerId, data } = action; + const { zoneName, cardId, counterId, counterValue } = data; + const game = state.games[gameId]; + if (!game) { + return state; + } + const player = game.players[playerId]; + if (!player) { + return state; + } + const zone = player.zones[zoneName]; + if (!zone) { + return state; + } + + const cardIdx = zone.cards.findIndex(c => c.id === cardId); + if (cardIdx < 0) { + return state; + } + + const card = zone.cards[cardIdx]; + let newCounterList: CardCounterInfo[]; + if (counterValue <= 0) { + newCounterList = card.counterList.filter(c => c.id !== counterId); + } else { + const existing = card.counterList.findIndex(c => c.id === counterId); + newCounterList = + existing >= 0 + ? card.counterList.map(c => (c.id === counterId ? { ...c, value: counterValue } : c)) + : [...card.counterList, { id: counterId, value: counterValue }]; + } + + const updatedCards = [...zone.cards]; + updatedCards[cardIdx] = { ...card, counterList: newCounterList }; + return updateZone(state, gameId, playerId, zoneName, { cards: updatedCards }); + } + + // ── Arrows ─────────────────────────────────────────────────────────────── + + case Types.ARROW_CREATED: { + const { gameId, playerId, data } = action; + const { arrowInfo } = data; + const game = state.games[gameId]; + if (!game) { + return state; + } + const player = game.players[playerId]; + if (!player) { + return state; + } + return updatePlayer(state, gameId, playerId, { + arrows: { ...player.arrows, [arrowInfo.id]: arrowInfo }, + }); + } + + case Types.ARROW_DELETED: { + const { gameId, playerId, data } = action; + const { arrowId } = data; + const game = state.games[gameId]; + if (!game) { + return state; + } + const player = game.players[playerId]; + if (!player) { + return state; + } + const arrows = { ...player.arrows }; + delete arrows[arrowId]; + return updatePlayer(state, gameId, playerId, { arrows }); + } + + // ── Player counters ─────────────────────────────────────────────────────── + + case Types.COUNTER_CREATED: { + const { gameId, playerId, data } = action; + const { counterInfo } = data; + const game = state.games[gameId]; + if (!game) { + return state; + } + const player = game.players[playerId]; + if (!player) { + return state; + } + return updatePlayer(state, gameId, playerId, { + counters: { ...player.counters, [counterInfo.id]: counterInfo }, + }); + } + + case Types.COUNTER_SET: { + const { gameId, playerId, data } = action; + const { counterId, value } = data; + const game = state.games[gameId]; + if (!game) { + return state; + } + const player = game.players[playerId]; + if (!player) { + return state; + } + const counter = player.counters[counterId]; + if (!counter) { + return state; + } + return updatePlayer(state, gameId, playerId, { + counters: { ...player.counters, [counterId]: { ...counter, count: value } }, + }); + } + + case Types.COUNTER_DELETED: { + const { gameId, playerId, data } = action; + const { counterId } = data; + const game = state.games[gameId]; + if (!game) { + return state; + } + const player = game.players[playerId]; + if (!player) { + return state; + } + const counters = { ...player.counters }; + delete counters[counterId]; + return updatePlayer(state, gameId, playerId, { counters }); + } + + // ── Zone operations ─────────────────────────────────────────────────────── + + case Types.CARDS_DRAWN: { + const { gameId, playerId, data } = action; + const { number: drawCount, cards } = data; + const game = state.games[gameId]; + if (!game) { + return state; + } + const player = game.players[playerId]; + if (!player) { + return state; + } + + const deckZone = player.zones['deck']; + const handZone = player.zones['hand']; + if (!handZone) { + return state; + } + + // Decrement deck count for the drawing player + let newState = deckZone + ? updateZone(state, gameId, playerId, 'deck', { + cardCount: Math.max(0, deckZone.cardCount - drawCount), + }) + : state; + + // Append revealed cards to hand (cards array is empty for non-drawing players; + // use drawCount for count math so all observers track the correct hand/deck size) + const updatedHand = newState.games[gameId]!.players[playerId]!.zones['hand']!; + return updateZone(newState, gameId, playerId, 'hand', { + cards: [...updatedHand.cards, ...cards], + cardCount: updatedHand.cardCount + drawCount, + }); + } + + case Types.CARDS_REVEALED: { + const { gameId, playerId, data } = action; + const { zoneName, cards } = data; + const game = state.games[gameId]; + if (!game) { + return state; + } + const player = game.players[playerId]; + if (!player) { + return state; + } + const zone = player.zones[zoneName]; + if (!zone) { + return state; + } + + // Merge revealed card data into existing zone cards (update existing, append new) + const merged = [...zone.cards]; + for (const revealedCard of cards) { + const idx = merged.findIndex(c => c.id === revealedCard.id); + if (idx >= 0) { + merged[idx] = { ...merged[idx], ...revealedCard }; + } else { + merged.push(revealedCard); + } + } + return updateZone(state, gameId, playerId, zoneName, { cards: merged }); + } + + case Types.ZONE_PROPERTIES_CHANGED: { + const { gameId, playerId, data } = action; + const { zoneName, alwaysRevealTopCard, alwaysLookAtTopCard } = data; + const patch: Partial = {}; + if (alwaysRevealTopCard !== undefined && alwaysRevealTopCard !== null) { + patch.alwaysRevealTopCard = alwaysRevealTopCard; + } + if (alwaysLookAtTopCard !== undefined && alwaysLookAtTopCard !== null) { + patch.alwaysLookAtTopCard = alwaysLookAtTopCard; + } + return updateZone(state, gameId, playerId, zoneName, patch); + } + + // ── Turn / phase ────────────────────────────────────────────────────────── + + case Types.ACTIVE_PLAYER_SET: { + return updateGame(state, action.gameId, { activePlayerId: action.activePlayerId }); + } + + case Types.ACTIVE_PHASE_SET: { + return updateGame(state, action.gameId, { activePhase: action.phase }); + } + + case Types.TURN_REVERSED: { + return updateGame(state, action.gameId, { reversed: action.reversed }); + } + + // ── Chat ────────────────────────────────────────────────────────────────── + + case Types.GAME_SAY: { + const { gameId, playerId, message } = action; + const game = state.games[gameId]; + if (!game) { + return state; + } + const newMessage: GameMessage = { playerId, message, timeReceived: Date.now() }; + return updateGame(state, gameId, { + messages: [...game.messages, newMessage], + }); + } + + // ── Log-only events (state unchanged, future game log will use these) ───── + case Types.ZONE_SHUFFLED: + case Types.ZONE_DUMPED: + case Types.DIE_ROLLED: { + return state; + } + + default: + return state; + } +}; diff --git a/webclient/src/store/game/game.selectors.ts b/webclient/src/store/game/game.selectors.ts new file mode 100644 index 000000000..2de0cb1d1 --- /dev/null +++ b/webclient/src/store/game/game.selectors.ts @@ -0,0 +1,72 @@ +import { GamesState, GameEntry, PlayerEntry, ZoneEntry } from './game.interfaces'; + +interface State { + games: GamesState; +} + +export const Selectors = { + getGames: ({ games }: State): { [gameId: number]: GameEntry } => games.games, + + getGame: ({ games }: State, gameId: number): GameEntry | undefined => games.games[gameId], + + getPlayers: ({ games }: State, gameId: number): { [playerId: number]: PlayerEntry } | undefined => + games.games[gameId]?.players, + + getPlayer: ({ games }: State, gameId: number, playerId: number): PlayerEntry | undefined => + games.games[gameId]?.players[playerId], + + getLocalPlayerId: ({ games }: State, gameId: number): number | undefined => + games.games[gameId]?.localPlayerId, + + getLocalPlayer: (state: State, gameId: number): PlayerEntry | undefined => { + const game = state.games.games[gameId]; + if (!game) { + return undefined; + } + return game.players[game.localPlayerId]; + }, + + getZones: ( + { games }: State, + gameId: number, + playerId: number + ): { [zoneName: string]: ZoneEntry } | undefined => + games.games[gameId]?.players[playerId]?.zones, + + getZone: ( + { games }: State, + gameId: number, + playerId: number, + zoneName: string + ): ZoneEntry | undefined => games.games[gameId]?.players[playerId]?.zones[zoneName], + + getCards: ({ games }: State, gameId: number, playerId: number, zoneName: string) => + games.games[gameId]?.players[playerId]?.zones[zoneName]?.cards ?? [], + + getCounters: ({ games }: State, gameId: number, playerId: number) => + games.games[gameId]?.players[playerId]?.counters ?? {}, + + getArrows: ({ games }: State, gameId: number, playerId: number) => + games.games[gameId]?.players[playerId]?.arrows ?? {}, + + getActivePlayerId: ({ games }: State, gameId: number): number | undefined => + games.games[gameId]?.activePlayerId, + + getActivePhase: ({ games }: State, gameId: number): number | undefined => + games.games[gameId]?.activePhase, + + isStarted: ({ games }: State, gameId: number): boolean => + games.games[gameId]?.started ?? false, + + isSpectator: ({ games }: State, gameId: number): boolean => + games.games[gameId]?.spectator ?? false, + + isReversed: ({ games }: State, gameId: number): boolean => + games.games[gameId]?.reversed ?? false, + + getMessages: ({ games }: State, gameId: number) => + games.games[gameId]?.messages ?? [], + + getActiveGameIds: ({ games }: State): number[] => + Object.keys(games.games).map(Number), +}; diff --git a/webclient/src/store/game/game.types.ts b/webclient/src/store/game/game.types.ts new file mode 100644 index 000000000..19ed016b7 --- /dev/null +++ b/webclient/src/store/game/game.types.ts @@ -0,0 +1,34 @@ +export const Types = { + CLEAR_STORE: '[Games] Clear Store', + GAME_JOINED: '[Games] Game Joined', + GAME_LEFT: '[Games] Game Left', + GAME_CLOSED: '[Games] Game Closed', + GAME_HOST_CHANGED: '[Games] Game Host Changed', + GAME_STATE_CHANGED: '[Games] Game State Changed', + PLAYER_JOINED: '[Games] Player Joined', + PLAYER_LEFT: '[Games] Player Left', + PLAYER_PROPERTIES_CHANGED: '[Games] Player Properties Changed', + KICKED: '[Games] Kicked', + CARD_MOVED: '[Games] Card Moved', + CARD_FLIPPED: '[Games] Card Flipped', + CARD_DESTROYED: '[Games] Card Destroyed', + CARD_ATTACHED: '[Games] Card Attached', + TOKEN_CREATED: '[Games] Token Created', + CARD_ATTR_CHANGED: '[Games] Card Attribute Changed', + CARD_COUNTER_CHANGED: '[Games] Card Counter Changed', + ARROW_CREATED: '[Games] Arrow Created', + ARROW_DELETED: '[Games] Arrow Deleted', + COUNTER_CREATED: '[Games] Counter Created', + COUNTER_SET: '[Games] Counter Set', + COUNTER_DELETED: '[Games] Counter Deleted', + CARDS_DRAWN: '[Games] Cards Drawn', + CARDS_REVEALED: '[Games] Cards Revealed', + ZONE_SHUFFLED: '[Games] Zone Shuffled', + DIE_ROLLED: '[Games] Die Rolled', + ACTIVE_PLAYER_SET: '[Games] Active Player Set', + ACTIVE_PHASE_SET: '[Games] Active Phase Set', + TURN_REVERSED: '[Games] Turn Reversed', + ZONE_DUMPED: '[Games] Zone Dumped', + ZONE_PROPERTIES_CHANGED: '[Games] Zone Properties Changed', + GAME_SAY: '[Games] Game Say', +}; diff --git a/webclient/src/store/game/index.ts b/webclient/src/store/game/index.ts new file mode 100644 index 000000000..77accdb13 --- /dev/null +++ b/webclient/src/store/game/index.ts @@ -0,0 +1,6 @@ +export { Types } from './game.types'; +export { gamesReducer } from './game.reducer'; +export { Actions } from './game.actions'; +export { Dispatch } from './game.dispatch'; +export { Selectors } from './game.selectors'; +export * from './game.interfaces'; diff --git a/webclient/src/store/index.ts b/webclient/src/store/index.ts index b43f290c5..7c257b09e 100644 --- a/webclient/src/store/index.ts +++ b/webclient/src/store/index.ts @@ -3,8 +3,15 @@ export { store } from './store'; // Common export { SortUtil } from './common'; -// Server +// Games +export { + Types as GameTypes, + Selectors as GameSelectors, + Dispatch as GameDispatch } from './game'; +export * from 'store/game/game.interfaces'; + +// Server export { Types as ServerTypes, Selectors as ServerSelectors, diff --git a/webclient/src/store/rootReducer.ts b/webclient/src/store/rootReducer.ts index 0f39d7e37..8da693fce 100644 --- a/webclient/src/store/rootReducer.ts +++ b/webclient/src/store/rootReducer.ts @@ -1,11 +1,13 @@ import { combineReducers } from 'redux'; +import { gamesReducer } from './game'; import { roomsReducer } from './rooms'; import { serverReducer } from './server'; import { reducer as formReducer } from 'redux-form' import { actionReducer } from './actions' export default combineReducers({ + games: gamesReducer, rooms: roomsReducer, server: serverReducer, diff --git a/webclient/src/types/game.ts b/webclient/src/types/game.ts index b9fcc1dc2..4961dc92c 100644 --- a/webclient/src/types/game.ts +++ b/webclient/src/types/game.ts @@ -40,3 +40,461 @@ export enum LeaveGameReason { USER_LEFT = 3, USER_DISCONNECTED = 4 } + +// ── Enums ──────────────────────────────────────────────────────────────────── + +export enum ZoneType { + PrivateZone = 0, + PublicZone = 1, + HiddenZone = 2, +} + +/** Matches CardAttribute enum in card_attributes.proto */ +export enum CardAttribute { + AttrTapped = 1, + AttrAttacking = 2, + AttrFaceDown = 3, + AttrColor = 4, + AttrPT = 5, + AttrAnnotation = 6, + AttrDoesntUntap = 7, +} + +// ── Primitive data structures (mirrors ServerInfo_* protos) ────────────────── + +export interface Color { + r: number; + g: number; + b: number; + a: number; +} + +/** Mirrors ServerInfo_CardCounter */ +export interface CardCounterInfo { + id: number; + value: number; +} + +/** Mirrors ServerInfo_Card */ +export interface CardInfo { + id: number; + name: string; + x: number; + y: number; + faceDown: boolean; + tapped: boolean; + attacking: boolean; + color: string; + pt: string; + annotation: string; + destroyOnZoneChange: boolean; + doesntUntap: boolean; + counterList: CardCounterInfo[]; + attachPlayerId: number; + attachZone: string; + attachCardId: number; + providerId: string; +} + +/** Mirrors ServerInfo_Zone */ +export interface ZoneInfo { + name: string; + type: ZoneType; + withCoords: boolean; + cardCount: number; + cardList: CardInfo[]; + alwaysRevealTopCard: boolean; + alwaysLookAtTopCard: boolean; +} + +/** Mirrors ServerInfo_Counter */ +export interface CounterInfo { + id: number; + name: string; + counterColor: Color; + radius: number; + count: number; +} + +/** Mirrors ServerInfo_Arrow */ +export interface ArrowInfo { + id: number; + startPlayerId: number; + startZone: string; + startCardId: number; + targetPlayerId: number; + targetZone: string; + targetCardId: number; + arrowColor: Color; +} + +/** Mirrors ServerInfo_PlayerProperties */ +export interface PlayerProperties { + playerId: number; + userInfo: any; + spectator: boolean; + conceded: boolean; + readyStart: boolean; + deckHash: string; + pingSeconds: number; + sideboardLocked: boolean; + judge: boolean; +} + +/** Mirrors ServerInfo_Player */ +export interface PlayerInfo { + properties: PlayerProperties; + deckList: string; + zoneList: ZoneInfo[]; + counterList: CounterInfo[]; + arrowList: ArrowInfo[]; +} + +// ── Game event payload interfaces (data arriving from server events) ────────── + +export interface GameStateChangedData { + playerList: PlayerInfo[]; + gameStarted: boolean; + activePlayerId: number; + activePhase: number; + secondsElapsed: number; +} + +export interface GameSayData { + message: string; +} + +export interface MoveCardData { + cardId: number; + cardName: string; + startPlayerId: number; + startZone: string; + position: number; + targetPlayerId: number; + targetZone: string; + x: number; + y: number; + newCardId: number; + faceDown: boolean; + newCardProviderId: string; +} + +export interface FlipCardData { + zoneName: string; + cardId: number; + cardName: string; + faceDown: boolean; + cardProviderId: string; +} + +export interface DestroyCardData { + zoneName: string; + cardId: number; +} + +export interface AttachCardData { + startZone: string; + cardId: number; + targetPlayerId: number; + targetZone: string; + targetCardId: number; +} + +export interface CreateTokenData { + zoneName: string; + cardId: number; + cardName: string; + color: string; + pt: string; + annotation: string; + destroyOnZoneChange: boolean; + x: number; + y: number; + cardProviderId: string; + faceDown: boolean; +} + +export interface SetCardAttrData { + zoneName: string; + cardId: number; + attribute: CardAttribute; + attrValue: string; +} + +export interface SetCardCounterData { + zoneName: string; + cardId: number; + counterId: number; + counterValue: number; +} + +export interface CreateArrowData { + arrowInfo: ArrowInfo; +} + +export interface DeleteArrowData { + arrowId: number; +} + +export interface CreateCounterData { + counterInfo: CounterInfo; +} + +export interface SetCounterData { + counterId: number; + value: number; +} + +export interface DelCounterData { + counterId: number; +} + +export interface DrawCardsData { + number: number; + cards: CardInfo[]; +} + +export interface RevealCardsData { + zoneName: string; + cardId: number[]; + otherPlayerId: number; + cards: CardInfo[]; + grantWriteAccess: boolean; + numberOfCards: number; +} + +export interface ShuffleData { + zoneName: string; + start: number; + end: number; +} + +export interface RollDieData { + sides: number; + value: number; + values: number[]; +} + +export interface DumpZoneData { + zoneOwnerId: number; + zoneName: string; + numberCards: number; + isReversed: boolean; +} + +export interface ChangeZonePropertiesData { + zoneName: string; + alwaysRevealTopCard: boolean; + alwaysLookAtTopCard: boolean; +} + +export interface SetActivePlayerData { + activePlayerId: number; +} + +export interface SetActivePhaseData { + phase: number; +} + +export interface ReverseTurnData { + reversed: boolean; +} + +/** + * Passed to every game event handler alongside the event payload. + * Contains per-container metadata from GameEventContainer. + * Not stored in Redux — transient routing metadata only. + */ +export interface GameEventMeta { + gameId: number; + playerId: number; + /** Raw protobuf GameEventContext object. Not stored in Redux. */ + context: any; + secondsElapsed: number; + /** Proto type is uint32. Non-zero means the action was forced by a judge. */ + forcedByJudge: number; +} + +// ── Command parameter interfaces ───────────────────────────────────────────── + +export interface CardToMove { + cardId: number; + faceDown?: boolean; + pt?: string; + tapped?: boolean; +} + +export interface MoveCardParams { + startPlayerId: number; + startZone: string; + cardsToMove: { card: CardToMove[] }; + targetPlayerId: number; + targetZone: string; + x?: number; + y?: number; + isReversed?: boolean; +} + +export interface DrawCardsParams { + number: number; +} + +export interface RollDieParams { + sides: number; + count?: number; +} + +export interface ShuffleParams { + zoneName: string; + start?: number; + end?: number; +} + +export interface FlipCardParams { + zone: string; + cardId: number; + faceDown: boolean; + pt?: string; +} + +export interface AttachCardParams { + startZone: string; + cardId: number; + targetPlayerId?: number; + targetZone?: string; + targetCardId?: number; +} + +export interface CreateTokenParams { + zone: string; + cardName: string; + color?: string; + pt?: string; + annotation?: string; + destroyOnZoneChange?: boolean; + x?: number; + y?: number; + targetZone?: string; + targetCardId?: number; + targetMode?: number; + cardProviderId?: string; + faceDown?: boolean; +} + +export interface SetCardAttrParams { + zone: string; + cardId: number; + attribute: CardAttribute; + attrValue: string; +} + +export interface SetCardCounterParams { + zone: string; + cardId: number; + counterId: number; + counterValue: number; +} + +export interface IncCardCounterParams { + zone: string; + cardId: number; + counterId: number; + counterDelta: number; +} + +export interface RevealCardsParams { + zoneName: string; + cardId?: number[]; + playerId?: number; + grantWriteAccess?: boolean; + topCards?: number; +} + +export interface DumpZoneParams { + playerId: number; + zoneName: string; + numberCards: number; + isReversed?: boolean; +} + +export interface ChangeZonePropertiesParams { + zoneName: string; + alwaysRevealTopCard?: boolean; + alwaysLookAtTopCard?: boolean; +} + +export interface CreateArrowParams { + startPlayerId: number; + startZone: string; + startCardId: number; + targetPlayerId: number; + targetZone?: string; + targetCardId?: number; + arrowColor: Color; + deleteInPhase?: number; +} + +export interface DeleteArrowParams { + arrowId: number; +} + +export interface CreateCounterParams { + counterName: string; + counterColor: Color; + radius: number; + value: number; +} + +export interface SetCounterParams { + counterId: number; + value: number; +} + +export interface IncCounterParams { + counterId: number; + delta: number; +} + +export interface DelCounterParams { + counterId: number; +} + +export interface KickFromGameParams { + playerId: number; +} + +export interface ReadyStartParams { + ready: boolean; + forceStart?: boolean; +} + +export interface MulliganParams { + number: number; +} + +export interface DeckSelectParams { + deck?: string; + deckId?: number; +} + +export interface MoveCardToZone { + cardName: string; + startZone: string; + targetZone: string; +} + +export interface SetSideboardPlanParams { + moveList: MoveCardToZone[]; +} + +export interface SetSideboardLockParams { + locked: boolean; +} + +export interface SetActivePhaseParams { + phase: number; +} + +export interface GameSayParams { + message: string; +} diff --git a/webclient/src/websocket/commands/game/attachCard.ts b/webclient/src/websocket/commands/game/attachCard.ts new file mode 100644 index 000000000..bc9ebce75 --- /dev/null +++ b/webclient/src/websocket/commands/game/attachCard.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { AttachCardParams } from 'types'; + +export function attachCard(gameId: number, params: AttachCardParams): void { + BackendService.sendGameCommand(gameId, 'Command_AttachCard', params); +} diff --git a/webclient/src/websocket/commands/game/changeZoneProperties.ts b/webclient/src/websocket/commands/game/changeZoneProperties.ts new file mode 100644 index 000000000..77167cc72 --- /dev/null +++ b/webclient/src/websocket/commands/game/changeZoneProperties.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { ChangeZonePropertiesParams } from 'types'; + +export function changeZoneProperties(gameId: number, params: ChangeZonePropertiesParams): void { + BackendService.sendGameCommand(gameId, 'Command_ChangeZoneProperties', params); +} diff --git a/webclient/src/websocket/commands/game/concede.ts b/webclient/src/websocket/commands/game/concede.ts new file mode 100644 index 000000000..fd587464b --- /dev/null +++ b/webclient/src/websocket/commands/game/concede.ts @@ -0,0 +1,5 @@ +import { BackendService } from '../../services/BackendService'; + +export function concede(gameId: number): void { + BackendService.sendGameCommand(gameId, 'Command_Concede', {}); +} diff --git a/webclient/src/websocket/commands/game/createArrow.ts b/webclient/src/websocket/commands/game/createArrow.ts new file mode 100644 index 000000000..7b3a8a294 --- /dev/null +++ b/webclient/src/websocket/commands/game/createArrow.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { CreateArrowParams } from 'types'; + +export function createArrow(gameId: number, params: CreateArrowParams): void { + BackendService.sendGameCommand(gameId, 'Command_CreateArrow', params); +} diff --git a/webclient/src/websocket/commands/game/createCounter.ts b/webclient/src/websocket/commands/game/createCounter.ts new file mode 100644 index 000000000..562ac06b9 --- /dev/null +++ b/webclient/src/websocket/commands/game/createCounter.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { CreateCounterParams } from 'types'; + +export function createCounter(gameId: number, params: CreateCounterParams): void { + BackendService.sendGameCommand(gameId, 'Command_CreateCounter', params); +} diff --git a/webclient/src/websocket/commands/game/createToken.ts b/webclient/src/websocket/commands/game/createToken.ts new file mode 100644 index 000000000..16e170e70 --- /dev/null +++ b/webclient/src/websocket/commands/game/createToken.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { CreateTokenParams } from 'types'; + +export function createToken(gameId: number, params: CreateTokenParams): void { + BackendService.sendGameCommand(gameId, 'Command_CreateToken', params); +} diff --git a/webclient/src/websocket/commands/game/deckSelect.ts b/webclient/src/websocket/commands/game/deckSelect.ts new file mode 100644 index 000000000..077cee580 --- /dev/null +++ b/webclient/src/websocket/commands/game/deckSelect.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { DeckSelectParams } from 'types'; + +export function deckSelect(gameId: number, params: DeckSelectParams): void { + BackendService.sendGameCommand(gameId, 'Command_DeckSelect', params); +} diff --git a/webclient/src/websocket/commands/game/delCounter.ts b/webclient/src/websocket/commands/game/delCounter.ts new file mode 100644 index 000000000..ade08ef21 --- /dev/null +++ b/webclient/src/websocket/commands/game/delCounter.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { DelCounterParams } from 'types'; + +export function delCounter(gameId: number, params: DelCounterParams): void { + BackendService.sendGameCommand(gameId, 'Command_DelCounter', params); +} diff --git a/webclient/src/websocket/commands/game/deleteArrow.ts b/webclient/src/websocket/commands/game/deleteArrow.ts new file mode 100644 index 000000000..fceef8a95 --- /dev/null +++ b/webclient/src/websocket/commands/game/deleteArrow.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { DeleteArrowParams } from 'types'; + +export function deleteArrow(gameId: number, params: DeleteArrowParams): void { + BackendService.sendGameCommand(gameId, 'Command_DeleteArrow', params); +} diff --git a/webclient/src/websocket/commands/game/drawCards.ts b/webclient/src/websocket/commands/game/drawCards.ts new file mode 100644 index 000000000..ae8e80744 --- /dev/null +++ b/webclient/src/websocket/commands/game/drawCards.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { DrawCardsParams } from 'types'; + +export function drawCards(gameId: number, params: DrawCardsParams): void { + BackendService.sendGameCommand(gameId, 'Command_DrawCards', params); +} diff --git a/webclient/src/websocket/commands/game/dumpZone.ts b/webclient/src/websocket/commands/game/dumpZone.ts new file mode 100644 index 000000000..18aec44f6 --- /dev/null +++ b/webclient/src/websocket/commands/game/dumpZone.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { DumpZoneParams } from 'types'; + +export function dumpZone(gameId: number, params: DumpZoneParams): void { + BackendService.sendGameCommand(gameId, 'Command_DumpZone', params); +} diff --git a/webclient/src/websocket/commands/game/flipCard.ts b/webclient/src/websocket/commands/game/flipCard.ts new file mode 100644 index 000000000..907f1e6a4 --- /dev/null +++ b/webclient/src/websocket/commands/game/flipCard.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { FlipCardParams } from 'types'; + +export function flipCard(gameId: number, params: FlipCardParams): void { + BackendService.sendGameCommand(gameId, 'Command_FlipCard', params); +} diff --git a/webclient/src/websocket/commands/game/gameSay.ts b/webclient/src/websocket/commands/game/gameSay.ts new file mode 100644 index 000000000..2f574b52e --- /dev/null +++ b/webclient/src/websocket/commands/game/gameSay.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { GameSayParams } from 'types'; + +export function gameSay(gameId: number, params: GameSayParams): void { + BackendService.sendGameCommand(gameId, 'Command_GameSay', params); +} diff --git a/webclient/src/websocket/commands/game/incCardCounter.ts b/webclient/src/websocket/commands/game/incCardCounter.ts new file mode 100644 index 000000000..e2fda829a --- /dev/null +++ b/webclient/src/websocket/commands/game/incCardCounter.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { IncCardCounterParams } from 'types'; + +export function incCardCounter(gameId: number, params: IncCardCounterParams): void { + BackendService.sendGameCommand(gameId, 'Command_IncCardCounter', params); +} diff --git a/webclient/src/websocket/commands/game/incCounter.ts b/webclient/src/websocket/commands/game/incCounter.ts new file mode 100644 index 000000000..a24173679 --- /dev/null +++ b/webclient/src/websocket/commands/game/incCounter.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { IncCounterParams } from 'types'; + +export function incCounter(gameId: number, params: IncCounterParams): void { + BackendService.sendGameCommand(gameId, 'Command_IncCounter', params); +} diff --git a/webclient/src/websocket/commands/game/index.ts b/webclient/src/websocket/commands/game/index.ts new file mode 100644 index 000000000..8978c938c --- /dev/null +++ b/webclient/src/websocket/commands/game/index.ts @@ -0,0 +1,31 @@ +export { leaveGame } from './leaveGame'; +export { kickFromGame } from './kickFromGame'; +export { gameSay } from './gameSay'; +export { readyStart } from './readyStart'; +export { concede } from './concede'; +export { nextTurn } from './nextTurn'; +export { setActivePhase } from './setActivePhase'; +export { reverseTurn } from './reverseTurn'; +export { moveCard } from './moveCard'; +export { flipCard } from './flipCard'; +export { attachCard } from './attachCard'; +export { createToken } from './createToken'; +export { setCardAttr } from './setCardAttr'; +export { setCardCounter } from './setCardCounter'; +export { incCardCounter } from './incCardCounter'; +export { drawCards } from './drawCards'; +export { undoDraw } from './undoDraw'; +export { createArrow } from './createArrow'; +export { deleteArrow } from './deleteArrow'; +export { createCounter } from './createCounter'; +export { setCounter } from './setCounter'; +export { incCounter } from './incCounter'; +export { delCounter } from './delCounter'; +export { shuffle } from './shuffle'; +export { dumpZone } from './dumpZone'; +export { revealCards } from './revealCards'; +export { changeZoneProperties } from './changeZoneProperties'; +export { deckSelect } from './deckSelect'; +export { setSideboardPlan } from './setSideboardPlan'; +export { setSideboardLock } from './setSideboardLock'; +export { mulligan } from './mulligan'; diff --git a/webclient/src/websocket/commands/game/kickFromGame.ts b/webclient/src/websocket/commands/game/kickFromGame.ts new file mode 100644 index 000000000..c14aab980 --- /dev/null +++ b/webclient/src/websocket/commands/game/kickFromGame.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { KickFromGameParams } from 'types'; + +export function kickFromGame(gameId: number, params: KickFromGameParams): void { + BackendService.sendGameCommand(gameId, 'Command_KickFromGame', params); +} diff --git a/webclient/src/websocket/commands/game/leaveGame.ts b/webclient/src/websocket/commands/game/leaveGame.ts new file mode 100644 index 000000000..48c8dcc2a --- /dev/null +++ b/webclient/src/websocket/commands/game/leaveGame.ts @@ -0,0 +1,5 @@ +import { BackendService } from '../../services/BackendService'; + +export function leaveGame(gameId: number): void { + BackendService.sendGameCommand(gameId, 'Command_LeaveGame', {}); +} diff --git a/webclient/src/websocket/commands/game/moveCard.ts b/webclient/src/websocket/commands/game/moveCard.ts new file mode 100644 index 000000000..aebc97bbd --- /dev/null +++ b/webclient/src/websocket/commands/game/moveCard.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { MoveCardParams } from 'types'; + +export function moveCard(gameId: number, params: MoveCardParams): void { + BackendService.sendGameCommand(gameId, 'Command_MoveCard', params); +} diff --git a/webclient/src/websocket/commands/game/mulligan.ts b/webclient/src/websocket/commands/game/mulligan.ts new file mode 100644 index 000000000..9871acf27 --- /dev/null +++ b/webclient/src/websocket/commands/game/mulligan.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { MulliganParams } from 'types'; + +export function mulligan(gameId: number, params: MulliganParams): void { + BackendService.sendGameCommand(gameId, 'Command_Mulligan', params); +} diff --git a/webclient/src/websocket/commands/game/nextTurn.ts b/webclient/src/websocket/commands/game/nextTurn.ts new file mode 100644 index 000000000..696002256 --- /dev/null +++ b/webclient/src/websocket/commands/game/nextTurn.ts @@ -0,0 +1,5 @@ +import { BackendService } from '../../services/BackendService'; + +export function nextTurn(gameId: number): void { + BackendService.sendGameCommand(gameId, 'Command_NextTurn', {}); +} diff --git a/webclient/src/websocket/commands/game/readyStart.ts b/webclient/src/websocket/commands/game/readyStart.ts new file mode 100644 index 000000000..50e818914 --- /dev/null +++ b/webclient/src/websocket/commands/game/readyStart.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { ReadyStartParams } from 'types'; + +export function readyStart(gameId: number, params: ReadyStartParams): void { + BackendService.sendGameCommand(gameId, 'Command_ReadyStart', params); +} diff --git a/webclient/src/websocket/commands/game/revealCards.ts b/webclient/src/websocket/commands/game/revealCards.ts new file mode 100644 index 000000000..567970db5 --- /dev/null +++ b/webclient/src/websocket/commands/game/revealCards.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { RevealCardsParams } from 'types'; + +export function revealCards(gameId: number, params: RevealCardsParams): void { + BackendService.sendGameCommand(gameId, 'Command_RevealCards', params); +} diff --git a/webclient/src/websocket/commands/game/reverseTurn.ts b/webclient/src/websocket/commands/game/reverseTurn.ts new file mode 100644 index 000000000..27662ea7e --- /dev/null +++ b/webclient/src/websocket/commands/game/reverseTurn.ts @@ -0,0 +1,5 @@ +import { BackendService } from '../../services/BackendService'; + +export function reverseTurn(gameId: number): void { + BackendService.sendGameCommand(gameId, 'Command_ReverseTurn', {}); +} diff --git a/webclient/src/websocket/commands/game/setActivePhase.ts b/webclient/src/websocket/commands/game/setActivePhase.ts new file mode 100644 index 000000000..664815f16 --- /dev/null +++ b/webclient/src/websocket/commands/game/setActivePhase.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { SetActivePhaseParams } from 'types'; + +export function setActivePhase(gameId: number, params: SetActivePhaseParams): void { + BackendService.sendGameCommand(gameId, 'Command_SetActivePhase', params); +} diff --git a/webclient/src/websocket/commands/game/setCardAttr.ts b/webclient/src/websocket/commands/game/setCardAttr.ts new file mode 100644 index 000000000..3d4418aa7 --- /dev/null +++ b/webclient/src/websocket/commands/game/setCardAttr.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { SetCardAttrParams } from 'types'; + +export function setCardAttr(gameId: number, params: SetCardAttrParams): void { + BackendService.sendGameCommand(gameId, 'Command_SetCardAttr', params); +} diff --git a/webclient/src/websocket/commands/game/setCardCounter.ts b/webclient/src/websocket/commands/game/setCardCounter.ts new file mode 100644 index 000000000..87e1940d2 --- /dev/null +++ b/webclient/src/websocket/commands/game/setCardCounter.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { SetCardCounterParams } from 'types'; + +export function setCardCounter(gameId: number, params: SetCardCounterParams): void { + BackendService.sendGameCommand(gameId, 'Command_SetCardCounter', params); +} diff --git a/webclient/src/websocket/commands/game/setCounter.ts b/webclient/src/websocket/commands/game/setCounter.ts new file mode 100644 index 000000000..a850d5111 --- /dev/null +++ b/webclient/src/websocket/commands/game/setCounter.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { SetCounterParams } from 'types'; + +export function setCounter(gameId: number, params: SetCounterParams): void { + BackendService.sendGameCommand(gameId, 'Command_SetCounter', params); +} diff --git a/webclient/src/websocket/commands/game/setSideboardLock.ts b/webclient/src/websocket/commands/game/setSideboardLock.ts new file mode 100644 index 000000000..00df8c763 --- /dev/null +++ b/webclient/src/websocket/commands/game/setSideboardLock.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { SetSideboardLockParams } from 'types'; + +export function setSideboardLock(gameId: number, params: SetSideboardLockParams): void { + BackendService.sendGameCommand(gameId, 'Command_SetSideboardLock', params); +} diff --git a/webclient/src/websocket/commands/game/setSideboardPlan.ts b/webclient/src/websocket/commands/game/setSideboardPlan.ts new file mode 100644 index 000000000..ad7278738 --- /dev/null +++ b/webclient/src/websocket/commands/game/setSideboardPlan.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { SetSideboardPlanParams } from 'types'; + +export function setSideboardPlan(gameId: number, params: SetSideboardPlanParams): void { + BackendService.sendGameCommand(gameId, 'Command_SetSideboardPlan', params); +} diff --git a/webclient/src/websocket/commands/game/shuffle.ts b/webclient/src/websocket/commands/game/shuffle.ts new file mode 100644 index 000000000..777d03fd6 --- /dev/null +++ b/webclient/src/websocket/commands/game/shuffle.ts @@ -0,0 +1,6 @@ +import { BackendService } from '../../services/BackendService'; +import { ShuffleParams } from 'types'; + +export function shuffle(gameId: number, params: ShuffleParams): void { + BackendService.sendGameCommand(gameId, 'Command_Shuffle', params); +} diff --git a/webclient/src/websocket/commands/game/undoDraw.ts b/webclient/src/websocket/commands/game/undoDraw.ts new file mode 100644 index 000000000..28d47c093 --- /dev/null +++ b/webclient/src/websocket/commands/game/undoDraw.ts @@ -0,0 +1,5 @@ +import { BackendService } from '../../services/BackendService'; + +export function undoDraw(gameId: number): void { + BackendService.sendGameCommand(gameId, 'Command_UndoDraw', {}); +} diff --git a/webclient/src/websocket/commands/index.ts b/webclient/src/websocket/commands/index.ts index 2c68fcfb9..5bed30ffe 100644 --- a/webclient/src/websocket/commands/index.ts +++ b/webclient/src/websocket/commands/index.ts @@ -1,4 +1,5 @@ export * as AdminCommands from './admin'; +export * as GameCommands from './game'; export * as ModeratorCommands from './moderator'; export * as RoomCommands from './room'; export * as SessionCommands from './session'; diff --git a/webclient/src/websocket/events/common/index.ts b/webclient/src/websocket/events/common/index.ts index 77a325c1e..305171fbc 100644 --- a/webclient/src/websocket/events/common/index.ts +++ b/webclient/src/websocket/events/common/index.ts @@ -1,6 +1,3 @@ import { ProtobufEvents } from '../../services/ProtobufService'; -import { playerPropertiesChanged } from './playerPropertiesChanged'; -export const CommonEvents: ProtobufEvents = { - '.Event_PlayerPropertiesChanged.ext': playerPropertiesChanged, -} +export const CommonEvents: ProtobufEvents = {}; diff --git a/webclient/src/websocket/events/common/playerPropertiesChanged.ts b/webclient/src/websocket/events/common/playerPropertiesChanged.ts index 557e57ac9..ff2527fd5 100644 --- a/webclient/src/websocket/events/common/playerPropertiesChanged.ts +++ b/webclient/src/websocket/events/common/playerPropertiesChanged.ts @@ -1,6 +1,3 @@ -import { PlayerGamePropertiesData } from '../session/interfaces'; -import { SessionPersistence } from '../../persistence'; - -export function playerPropertiesChanged(payload: PlayerGamePropertiesData): void { - SessionPersistence.playerPropertiesChanged(payload); -} +// Event_PlayerPropertiesChanged is handled as a game event in websocket/events/game/playerPropertiesChanged.ts +// This file is retained for reference but is no longer registered in CommonEvents. +export {}; diff --git a/webclient/src/websocket/events/game/attachCard.ts b/webclient/src/websocket/events/game/attachCard.ts new file mode 100644 index 000000000..612d8e0bc --- /dev/null +++ b/webclient/src/websocket/events/game/attachCard.ts @@ -0,0 +1,6 @@ +import { AttachCardData, GameEventMeta } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function attachCard(data: AttachCardData, meta: GameEventMeta): void { + GamePersistence.cardAttached(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/events/game/changeZoneProperties.ts b/webclient/src/websocket/events/game/changeZoneProperties.ts new file mode 100644 index 000000000..d18008d46 --- /dev/null +++ b/webclient/src/websocket/events/game/changeZoneProperties.ts @@ -0,0 +1,6 @@ +import { ChangeZonePropertiesData, GameEventMeta } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function changeZoneProperties(data: ChangeZonePropertiesData, meta: GameEventMeta): void { + GamePersistence.zonePropertiesChanged(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/events/game/createArrow.ts b/webclient/src/websocket/events/game/createArrow.ts new file mode 100644 index 000000000..188b308d5 --- /dev/null +++ b/webclient/src/websocket/events/game/createArrow.ts @@ -0,0 +1,6 @@ +import { CreateArrowData, GameEventMeta } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function createArrow(data: CreateArrowData, meta: GameEventMeta): void { + GamePersistence.arrowCreated(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/events/game/createCounter.ts b/webclient/src/websocket/events/game/createCounter.ts new file mode 100644 index 000000000..aa9c9ed5a --- /dev/null +++ b/webclient/src/websocket/events/game/createCounter.ts @@ -0,0 +1,6 @@ +import { CreateCounterData, GameEventMeta } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function createCounter(data: CreateCounterData, meta: GameEventMeta): void { + GamePersistence.counterCreated(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/events/game/createToken.ts b/webclient/src/websocket/events/game/createToken.ts new file mode 100644 index 000000000..d2389e98a --- /dev/null +++ b/webclient/src/websocket/events/game/createToken.ts @@ -0,0 +1,6 @@ +import { CreateTokenData, GameEventMeta } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function createToken(data: CreateTokenData, meta: GameEventMeta): void { + GamePersistence.tokenCreated(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/events/game/delCounter.ts b/webclient/src/websocket/events/game/delCounter.ts new file mode 100644 index 000000000..431e64696 --- /dev/null +++ b/webclient/src/websocket/events/game/delCounter.ts @@ -0,0 +1,6 @@ +import { DelCounterData, GameEventMeta } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function delCounter(data: DelCounterData, meta: GameEventMeta): void { + GamePersistence.counterDeleted(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/events/game/deleteArrow.ts b/webclient/src/websocket/events/game/deleteArrow.ts new file mode 100644 index 000000000..30710e9ec --- /dev/null +++ b/webclient/src/websocket/events/game/deleteArrow.ts @@ -0,0 +1,6 @@ +import { DeleteArrowData, GameEventMeta } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function deleteArrow(data: DeleteArrowData, meta: GameEventMeta): void { + GamePersistence.arrowDeleted(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/events/game/destroyCard.ts b/webclient/src/websocket/events/game/destroyCard.ts new file mode 100644 index 000000000..79e4a536a --- /dev/null +++ b/webclient/src/websocket/events/game/destroyCard.ts @@ -0,0 +1,6 @@ +import { DestroyCardData, GameEventMeta } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function destroyCard(data: DestroyCardData, meta: GameEventMeta): void { + GamePersistence.cardDestroyed(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/events/game/drawCards.ts b/webclient/src/websocket/events/game/drawCards.ts new file mode 100644 index 000000000..7946fed92 --- /dev/null +++ b/webclient/src/websocket/events/game/drawCards.ts @@ -0,0 +1,6 @@ +import { DrawCardsData, GameEventMeta } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function drawCards(data: DrawCardsData, meta: GameEventMeta): void { + GamePersistence.cardsDrawn(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/events/game/dumpZone.ts b/webclient/src/websocket/events/game/dumpZone.ts new file mode 100644 index 000000000..39e195288 --- /dev/null +++ b/webclient/src/websocket/events/game/dumpZone.ts @@ -0,0 +1,6 @@ +import { DumpZoneData, GameEventMeta } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function dumpZone(data: DumpZoneData, meta: GameEventMeta): void { + GamePersistence.zoneDumped(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/events/game/flipCard.ts b/webclient/src/websocket/events/game/flipCard.ts new file mode 100644 index 000000000..040445ebc --- /dev/null +++ b/webclient/src/websocket/events/game/flipCard.ts @@ -0,0 +1,6 @@ +import { FlipCardData, GameEventMeta } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function flipCard(data: FlipCardData, meta: GameEventMeta): void { + GamePersistence.cardFlipped(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/events/game/gameClosed.ts b/webclient/src/websocket/events/game/gameClosed.ts new file mode 100644 index 000000000..9ebdaba5c --- /dev/null +++ b/webclient/src/websocket/events/game/gameClosed.ts @@ -0,0 +1,6 @@ +import { GameEventMeta } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function gameClosed(_data: {}, meta: GameEventMeta): void { + GamePersistence.gameClosed(meta.gameId); +} diff --git a/webclient/src/websocket/events/game/gameEvents.spec.ts b/webclient/src/websocket/events/game/gameEvents.spec.ts index 34154fa7e..7f4f9f6b5 100644 --- a/webclient/src/websocket/events/game/gameEvents.spec.ts +++ b/webclient/src/websocket/events/game/gameEvents.spec.ts @@ -1,29 +1,31 @@ jest.mock('../../persistence', () => ({ GamePersistence: { - joinGame: jest.fn(), - leaveGame: jest.fn(), + playerJoined: jest.fn(), + playerLeft: jest.fn(), }, })); import { GamePersistence } from '../../persistence'; +import { joinGame } from './joinGame'; +import { leaveGame } from './leaveGame'; beforeEach(() => jest.clearAllMocks()); describe('joinGame event', () => { - const { joinGame } = jest.requireActual('./joinGame'); - - it('delegates to GamePersistence.joinGame', () => { - const data = { gameId: 5, player: { playerId: 1 } } as any; - joinGame(data); - expect(GamePersistence.joinGame).toHaveBeenCalledWith(data); + it('delegates to GamePersistence.playerJoined with gameId from meta', () => { + const playerProperties = { playerId: 1 }; + const data = { playerProperties } as any; + const meta = { gameId: 5, playerId: 1, context: null, secondsElapsed: 0, forcedByJudge: 0 }; + joinGame(data, meta); + expect(GamePersistence.playerJoined).toHaveBeenCalledWith(5, playerProperties); }); }); describe('leaveGame event', () => { - const { leaveGame } = jest.requireActual('./leaveGame'); - - it('delegates to GamePersistence.leaveGame', () => { - leaveGame(42 as any); - expect(GamePersistence.leaveGame).toHaveBeenCalledWith(42); + it('delegates to GamePersistence.playerLeft with gameId/playerId from meta', () => { + const data = { reason: 3 }; + const meta = { gameId: 5, playerId: 2, context: null, secondsElapsed: 0, forcedByJudge: 0 }; + leaveGame(data, meta); + expect(GamePersistence.playerLeft).toHaveBeenCalledWith(5, 2, 3); }); }); diff --git a/webclient/src/websocket/events/game/gameHostChanged.ts b/webclient/src/websocket/events/game/gameHostChanged.ts new file mode 100644 index 000000000..174b2fca1 --- /dev/null +++ b/webclient/src/websocket/events/game/gameHostChanged.ts @@ -0,0 +1,10 @@ +import { GameEventMeta } from 'types'; +import { GamePersistence } from '../../persistence'; + +/** + * Event_GameHostChanged carries no payload fields. + * The new host is identified by GameEvent.player_id (meta.playerId). + */ +export function gameHostChanged(_data: {}, meta: GameEventMeta): void { + GamePersistence.gameHostChanged(meta.gameId, meta.playerId); +} diff --git a/webclient/src/websocket/events/game/gameSay.ts b/webclient/src/websocket/events/game/gameSay.ts new file mode 100644 index 000000000..1f649a2fa --- /dev/null +++ b/webclient/src/websocket/events/game/gameSay.ts @@ -0,0 +1,6 @@ +import { GameEventMeta, GameSayData } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function gameSay(data: GameSayData, meta: GameEventMeta): void { + GamePersistence.gameSay(meta.gameId, meta.playerId, data.message); +} diff --git a/webclient/src/websocket/events/game/gameStateChanged.ts b/webclient/src/websocket/events/game/gameStateChanged.ts new file mode 100644 index 000000000..94ce9a136 --- /dev/null +++ b/webclient/src/websocket/events/game/gameStateChanged.ts @@ -0,0 +1,6 @@ +import { GameEventMeta, GameStateChangedData } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function gameStateChanged(data: GameStateChangedData, meta: GameEventMeta): void { + GamePersistence.gameStateChanged(meta.gameId, data); +} diff --git a/webclient/src/websocket/events/game/index.ts b/webclient/src/websocket/events/game/index.ts index a7b3277a5..aa4be6391 100644 --- a/webclient/src/websocket/events/game/index.ts +++ b/webclient/src/websocket/events/game/index.ts @@ -1,36 +1,62 @@ import { ProtobufEvents } from '../../services/ProtobufService'; +import { attachCard } from './attachCard'; +import { changeZoneProperties } from './changeZoneProperties'; +import { createArrow } from './createArrow'; +import { createCounter } from './createCounter'; +import { createToken } from './createToken'; +import { delCounter } from './delCounter'; +import { deleteArrow } from './deleteArrow'; +import { destroyCard } from './destroyCard'; +import { drawCards } from './drawCards'; +import { dumpZone } from './dumpZone'; +import { flipCard } from './flipCard'; +import { gameClosed } from './gameClosed'; +import { gameHostChanged } from './gameHostChanged'; +import { gameSay } from './gameSay'; +import { gameStateChanged } from './gameStateChanged'; import { joinGame } from './joinGame'; +import { kicked } from './kicked'; import { leaveGame } from './leaveGame'; - +import { moveCard } from './moveCard'; +import { playerPropertiesChanged } from './playerPropertiesChanged'; +import { revealCards } from './revealCards'; +import { reverseTurn } from './reverseTurn'; +import { rollDie } from './rollDie'; +import { setActivePhase } from './setActivePhase'; +import { setActivePlayer } from './setActivePlayer'; +import { setCardAttr } from './setCardAttr'; +import { setCardCounter } from './setCardCounter'; +import { setCounter } from './setCounter'; +import { shuffle } from './shuffle'; export const GameEvents: ProtobufEvents = { '.Event_Join.ext': joinGame, '.Event_Leave.ext': leaveGame, - '.Event_GameClosed.ext': () => console.log('Event_GameClosed.ext'), - '.Event_GameHostChanged.ext': () => console.log('Event_GameHostChanged.ext'), - '.Event_Kicked.ext': () => console.log('Event_Kicked.ext'), - '.Event_GameStateChanged.ext': () => console.log('Event_GameStateChanged.ext'), - // '.Event_PlayerPropertiesChanged.ext': () => console.log("Event_PlayerProperties.ext"), - '.Event_GameSay.ext': () => console.log('Event_GameSay.ext'), - '.Event_CreateArrow.ext': () => console.log('Event_CreateArrow.ext'), - '.Event_DeleteArrow.ext': () => console.log('Event_DeleteArrow.ext'), - '.Event_CreateCounter.ext': () => console.log('Event_CreateCounter.ext'), - '.Event_SetCounter.ext': () => console.log('Event_SetCounter.ext'), - '.Event_DelCounter.ext': () => console.log('Event_DelCounter.ext'), - '.Event_DrawCards.ext': () => console.log('Event_DrawCards.ext'), - '.Event_RevealCards.ext': () => console.log('Event_RevealCards.ext'), - '.Event_Shuffle.ext': () => console.log('Event_Shuffle.ext'), - '.Event_RollDie.ext': () => console.log('Event_Roll.ext'), - '.Event_MoveCard.ext': () => console.log('Event_MoveCard.ext'), - '.Event_FlipCard.ext': () => console.log('Event_FlipCard.ext'), - '.Event_DestroyCard.ext': () => console.log('Event_DestroyCard.ext'), - '.Event_AttachCard.ext': () => console.log('Event_AttachCard.ext'), - '.Event_CreateToken.ext': () => console.log('Event_CreateToken.ext'), - '.Event_SetCardAttribute.ext': () => console.log('Event_SetCardAttribute.ext'), - '.Event_SetCardCounter.ext': () => console.log('Event_SetCardCounter.ext'), - '.Event_SetActivePlayer.ext': () => console.log('Event_SetActivePlayer.ext'), - '.Event_SetActivePhase.ext': () => console.log('Event_SetActivePhase.ext'), - '.Event_DumpZone.ext': () => console.log('Event_DumpZone.ext'), - '.Event_ChangeZoneProperties.ext': () => console.log('Event_ChangeZoneProperties.ext'), - '.Event_ReverseTurn.ext': () => console.log('Event_ReverseTurn.ext'), + '.Event_GameClosed.ext': gameClosed, + '.Event_GameHostChanged.ext': gameHostChanged, + '.Event_Kicked.ext': kicked, + '.Event_GameStateChanged.ext': gameStateChanged, + '.Event_PlayerPropertiesChanged.ext': playerPropertiesChanged, + '.Event_GameSay.ext': gameSay, + '.Event_CreateArrow.ext': createArrow, + '.Event_DeleteArrow.ext': deleteArrow, + '.Event_CreateCounter.ext': createCounter, + '.Event_SetCounter.ext': setCounter, + '.Event_DelCounter.ext': delCounter, + '.Event_DrawCards.ext': drawCards, + '.Event_RevealCards.ext': revealCards, + '.Event_Shuffle.ext': shuffle, + '.Event_RollDie.ext': rollDie, + '.Event_MoveCard.ext': moveCard, + '.Event_FlipCard.ext': flipCard, + '.Event_DestroyCard.ext': destroyCard, + '.Event_AttachCard.ext': attachCard, + '.Event_CreateToken.ext': createToken, + '.Event_SetCardAttr.ext': setCardAttr, + '.Event_SetCardCounter.ext': setCardCounter, + '.Event_SetActivePlayer.ext': setActivePlayer, + '.Event_SetActivePhase.ext': setActivePhase, + '.Event_DumpZone.ext': dumpZone, + '.Event_ChangeZoneProperties.ext': changeZoneProperties, + '.Event_ReverseTurn.ext': reverseTurn, }; diff --git a/webclient/src/websocket/events/game/joinGame.ts b/webclient/src/websocket/events/game/joinGame.ts index 30cef87dc..b0150937b 100644 --- a/webclient/src/websocket/events/game/joinGame.ts +++ b/webclient/src/websocket/events/game/joinGame.ts @@ -1,6 +1,6 @@ import { GamePersistence } from '../../persistence'; -import { PlayerGamePropertiesData } from '../session/interfaces'; +import { GameEventMeta, PlayerProperties } from 'types'; -export function joinGame(playerGamePropertiesData: PlayerGamePropertiesData): void { - GamePersistence.joinGame(playerGamePropertiesData); +export function joinGame(data: { playerProperties: PlayerProperties }, meta: GameEventMeta): void { + GamePersistence.playerJoined(meta.gameId, data.playerProperties); } diff --git a/webclient/src/websocket/events/game/kicked.ts b/webclient/src/websocket/events/game/kicked.ts new file mode 100644 index 000000000..7bf94d5ad --- /dev/null +++ b/webclient/src/websocket/events/game/kicked.ts @@ -0,0 +1,6 @@ +import { GameEventMeta } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function kicked(_data: {}, meta: GameEventMeta): void { + GamePersistence.kicked(meta.gameId); +} diff --git a/webclient/src/websocket/events/game/leaveGame.ts b/webclient/src/websocket/events/game/leaveGame.ts index 74a4dc6c5..9367ba5bb 100644 --- a/webclient/src/websocket/events/game/leaveGame.ts +++ b/webclient/src/websocket/events/game/leaveGame.ts @@ -1,7 +1,6 @@ -import { LeaveGameReason } from 'types'; +import { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; - -export function leaveGame(reason: LeaveGameReason): void { - GamePersistence.leaveGame(reason); +export function leaveGame(data: { reason: number }, meta: GameEventMeta): void { + GamePersistence.playerLeft(meta.gameId, meta.playerId, data.reason ?? 1); } diff --git a/webclient/src/websocket/events/game/moveCard.ts b/webclient/src/websocket/events/game/moveCard.ts new file mode 100644 index 000000000..8a0f3d167 --- /dev/null +++ b/webclient/src/websocket/events/game/moveCard.ts @@ -0,0 +1,6 @@ +import { GameEventMeta, MoveCardData } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function moveCard(data: MoveCardData, meta: GameEventMeta): void { + GamePersistence.cardMoved(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/events/game/playerPropertiesChanged.ts b/webclient/src/websocket/events/game/playerPropertiesChanged.ts new file mode 100644 index 000000000..e55f05251 --- /dev/null +++ b/webclient/src/websocket/events/game/playerPropertiesChanged.ts @@ -0,0 +1,6 @@ +import { GameEventMeta, PlayerProperties } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function playerPropertiesChanged(data: { playerProperties: PlayerProperties }, meta: GameEventMeta): void { + GamePersistence.playerPropertiesChanged(meta.gameId, meta.playerId, data.playerProperties); +} diff --git a/webclient/src/websocket/events/game/revealCards.ts b/webclient/src/websocket/events/game/revealCards.ts new file mode 100644 index 000000000..1402f3b87 --- /dev/null +++ b/webclient/src/websocket/events/game/revealCards.ts @@ -0,0 +1,6 @@ +import { GameEventMeta, RevealCardsData } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function revealCards(data: RevealCardsData, meta: GameEventMeta): void { + GamePersistence.cardsRevealed(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/events/game/reverseTurn.ts b/webclient/src/websocket/events/game/reverseTurn.ts new file mode 100644 index 000000000..d7194890a --- /dev/null +++ b/webclient/src/websocket/events/game/reverseTurn.ts @@ -0,0 +1,6 @@ +import { GameEventMeta, ReverseTurnData } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function reverseTurn(data: ReverseTurnData, meta: GameEventMeta): void { + GamePersistence.turnReversed(meta.gameId, data.reversed); +} diff --git a/webclient/src/websocket/events/game/rollDie.ts b/webclient/src/websocket/events/game/rollDie.ts new file mode 100644 index 000000000..27b00d395 --- /dev/null +++ b/webclient/src/websocket/events/game/rollDie.ts @@ -0,0 +1,6 @@ +import { GameEventMeta, RollDieData } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function rollDie(data: RollDieData, meta: GameEventMeta): void { + GamePersistence.dieRolled(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/events/game/setActivePhase.ts b/webclient/src/websocket/events/game/setActivePhase.ts new file mode 100644 index 000000000..013250088 --- /dev/null +++ b/webclient/src/websocket/events/game/setActivePhase.ts @@ -0,0 +1,6 @@ +import { GameEventMeta, SetActivePhaseData } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function setActivePhase(data: SetActivePhaseData, meta: GameEventMeta): void { + GamePersistence.activePhaseSet(meta.gameId, data.phase); +} diff --git a/webclient/src/websocket/events/game/setActivePlayer.ts b/webclient/src/websocket/events/game/setActivePlayer.ts new file mode 100644 index 000000000..878fab9f6 --- /dev/null +++ b/webclient/src/websocket/events/game/setActivePlayer.ts @@ -0,0 +1,6 @@ +import { GameEventMeta, SetActivePlayerData } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function setActivePlayer(data: SetActivePlayerData, meta: GameEventMeta): void { + GamePersistence.activePlayerSet(meta.gameId, data.activePlayerId); +} diff --git a/webclient/src/websocket/events/game/setCardAttr.ts b/webclient/src/websocket/events/game/setCardAttr.ts new file mode 100644 index 000000000..3461f9cc0 --- /dev/null +++ b/webclient/src/websocket/events/game/setCardAttr.ts @@ -0,0 +1,6 @@ +import { GameEventMeta, SetCardAttrData } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function setCardAttr(data: SetCardAttrData, meta: GameEventMeta): void { + GamePersistence.cardAttrChanged(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/events/game/setCardCounter.ts b/webclient/src/websocket/events/game/setCardCounter.ts new file mode 100644 index 000000000..364a30585 --- /dev/null +++ b/webclient/src/websocket/events/game/setCardCounter.ts @@ -0,0 +1,6 @@ +import { GameEventMeta, SetCardCounterData } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function setCardCounter(data: SetCardCounterData, meta: GameEventMeta): void { + GamePersistence.cardCounterChanged(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/events/game/setCounter.ts b/webclient/src/websocket/events/game/setCounter.ts new file mode 100644 index 000000000..b729c1718 --- /dev/null +++ b/webclient/src/websocket/events/game/setCounter.ts @@ -0,0 +1,6 @@ +import { GameEventMeta, SetCounterData } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function setCounter(data: SetCounterData, meta: GameEventMeta): void { + GamePersistence.counterSet(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/events/game/shuffle.ts b/webclient/src/websocket/events/game/shuffle.ts new file mode 100644 index 000000000..f94703555 --- /dev/null +++ b/webclient/src/websocket/events/game/shuffle.ts @@ -0,0 +1,6 @@ +import { GameEventMeta, ShuffleData } from 'types'; +import { GamePersistence } from '../../persistence'; + +export function shuffle(data: ShuffleData, meta: GameEventMeta): void { + GamePersistence.zoneShuffled(meta.gameId, meta.playerId, data); +} diff --git a/webclient/src/websocket/persistence/GamePersistence.spec.ts b/webclient/src/websocket/persistence/GamePersistence.spec.ts index 2720b58ca..29377c339 100644 --- a/webclient/src/websocket/persistence/GamePersistence.spec.ts +++ b/webclient/src/websocket/persistence/GamePersistence.spec.ts @@ -1,18 +1,25 @@ import { GamePersistence } from './GamePersistence'; +jest.mock('store', () => ({ + GameDispatch: { + playerJoined: jest.fn(), + playerLeft: jest.fn(), + }, +})); + +import { GameDispatch } from 'store'; + +beforeEach(() => jest.clearAllMocks()); + describe('GamePersistence', () => { - it('joinGame logs to console', () => { - const spy = jest.spyOn(console, 'log').mockImplementation(() => {}); + it('playerJoined dispatches via GameDispatch', () => { const data = { playerId: 1 } as any; - GamePersistence.joinGame(data); - expect(spy).toHaveBeenCalledWith('joinGame', data); - spy.mockRestore(); + GamePersistence.playerJoined(5, data); + expect(GameDispatch.playerJoined).toHaveBeenCalledWith(5, data); }); - it('leaveGame logs to console', () => { - const spy = jest.spyOn(console, 'log').mockImplementation(() => {}); - GamePersistence.leaveGame(0 as any); - expect(spy).toHaveBeenCalledWith('leaveGame', 0); - spy.mockRestore(); + it('playerLeft dispatches via GameDispatch', () => { + GamePersistence.playerLeft(5, 1, 3); + expect(GameDispatch.playerLeft).toHaveBeenCalledWith(5, 1, 3); }); }); diff --git a/webclient/src/websocket/persistence/GamePersistence.ts b/webclient/src/websocket/persistence/GamePersistence.ts index e39c1b5a9..16eced49e 100644 --- a/webclient/src/websocket/persistence/GamePersistence.ts +++ b/webclient/src/websocket/persistence/GamePersistence.ts @@ -1,12 +1,142 @@ -import { PlayerGamePropertiesData } from '../events/session/interfaces'; -import { LeaveGameReason } from '../../types'; +import { GameDispatch } from 'store'; +import { + AttachCardData, + ChangeZonePropertiesData, + CreateArrowData, + CreateCounterData, + CreateTokenData, + DelCounterData, + DeleteArrowData, + DestroyCardData, + DrawCardsData, + DumpZoneData, + FlipCardData, + GameStateChangedData, + MoveCardData, + PlayerProperties, + RevealCardsData, + RollDieData, + SetCardAttrData, + SetCardCounterData, + SetCounterData, + ShuffleData, +} from 'types'; export class GamePersistence { - static joinGame(playerGamePropertiesData: PlayerGamePropertiesData) { - console.log('joinGame', playerGamePropertiesData); + static gameStateChanged(gameId: number, data: GameStateChangedData): void { + GameDispatch.gameStateChanged(gameId, data); } - static leaveGame(reason: LeaveGameReason) { - console.log('leaveGame', reason); + static playerJoined(gameId: number, playerProperties: PlayerProperties): void { + GameDispatch.playerJoined(gameId, playerProperties); + } + + static playerLeft(gameId: number, playerId: number, reason: number): void { + GameDispatch.playerLeft(gameId, playerId, reason); + } + + static playerPropertiesChanged(gameId: number, playerId: number, properties: PlayerProperties): void { + GameDispatch.playerPropertiesChanged(gameId, playerId, properties); + } + + static gameClosed(gameId: number): void { + GameDispatch.gameClosed(gameId); + } + + static gameHostChanged(gameId: number, hostId: number): void { + GameDispatch.gameHostChanged(gameId, hostId); + } + + static kicked(gameId: number): void { + GameDispatch.kicked(gameId); + } + + static gameSay(gameId: number, playerId: number, message: string): void { + GameDispatch.gameSay(gameId, playerId, message); + } + + static cardMoved(gameId: number, playerId: number, data: MoveCardData): void { + GameDispatch.cardMoved(gameId, playerId, data); + } + + static cardFlipped(gameId: number, playerId: number, data: FlipCardData): void { + GameDispatch.cardFlipped(gameId, playerId, data); + } + + static cardDestroyed(gameId: number, playerId: number, data: DestroyCardData): void { + GameDispatch.cardDestroyed(gameId, playerId, data); + } + + static cardAttached(gameId: number, playerId: number, data: AttachCardData): void { + GameDispatch.cardAttached(gameId, playerId, data); + } + + static tokenCreated(gameId: number, playerId: number, data: CreateTokenData): void { + GameDispatch.tokenCreated(gameId, playerId, data); + } + + static cardAttrChanged(gameId: number, playerId: number, data: SetCardAttrData): void { + GameDispatch.cardAttrChanged(gameId, playerId, data); + } + + static cardCounterChanged(gameId: number, playerId: number, data: SetCardCounterData): void { + GameDispatch.cardCounterChanged(gameId, playerId, data); + } + + static arrowCreated(gameId: number, playerId: number, data: CreateArrowData): void { + GameDispatch.arrowCreated(gameId, playerId, data); + } + + static arrowDeleted(gameId: number, playerId: number, data: DeleteArrowData): void { + GameDispatch.arrowDeleted(gameId, playerId, data); + } + + static counterCreated(gameId: number, playerId: number, data: CreateCounterData): void { + GameDispatch.counterCreated(gameId, playerId, data); + } + + static counterSet(gameId: number, playerId: number, data: SetCounterData): void { + GameDispatch.counterSet(gameId, playerId, data); + } + + static counterDeleted(gameId: number, playerId: number, data: DelCounterData): void { + GameDispatch.counterDeleted(gameId, playerId, data); + } + + static cardsDrawn(gameId: number, playerId: number, data: DrawCardsData): void { + GameDispatch.cardsDrawn(gameId, playerId, data); + } + + static cardsRevealed(gameId: number, playerId: number, data: RevealCardsData): void { + GameDispatch.cardsRevealed(gameId, playerId, data); + } + + static zoneShuffled(gameId: number, playerId: number, data: ShuffleData): void { + GameDispatch.zoneShuffled(gameId, playerId, data); + } + + static dieRolled(gameId: number, playerId: number, data: RollDieData): void { + GameDispatch.dieRolled(gameId, playerId, data); + } + + static activePlayerSet(gameId: number, activePlayerId: number): void { + GameDispatch.activePlayerSet(gameId, activePlayerId); + } + + static activePhaseSet(gameId: number, phase: number): void { + GameDispatch.activePhaseSet(gameId, phase); + } + + static turnReversed(gameId: number, reversed: boolean): void { + GameDispatch.turnReversed(gameId, reversed); + } + + static zoneDumped(gameId: number, playerId: number, data: DumpZoneData): void { + GameDispatch.zoneDumped(gameId, playerId, data); + } + + static zonePropertiesChanged(gameId: number, playerId: number, data: ChangeZonePropertiesData): void { + GameDispatch.zonePropertiesChanged(gameId, playerId, data); } } + diff --git a/webclient/src/websocket/persistence/SessionPersistence.spec.ts b/webclient/src/websocket/persistence/SessionPersistence.spec.ts index d5dd8e1b5..38080d578 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.spec.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.spec.ts @@ -54,6 +54,10 @@ jest.mock('store', () => ({ replayModifyMatch: jest.fn(), replayDeleteMatch: jest.fn(), }, + GameDispatch: { + gameJoined: jest.fn(), + playerPropertiesChanged: jest.fn(), + }, })); jest.mock('websocket/utils', () => ({ @@ -68,7 +72,7 @@ jest.mock('../utils/NormalizeService', () => ({ })); import { SessionPersistence } from './SessionPersistence'; -import { ServerDispatch } from 'store'; +import { ServerDispatch, GameDispatch } from 'store'; import { sanitizeHtml } from 'websocket/utils'; import NormalizeService from '../utils/NormalizeService'; import { StatusEnum } from 'types'; @@ -318,11 +322,9 @@ describe('SessionPersistence', () => { expect(ServerDispatch.notifyUser).toHaveBeenCalledWith(notif); }); - it('playerPropertiesChanged logs to console', () => { - const spy = jest.spyOn(console, 'log').mockImplementation(() => {}); - SessionPersistence.playerPropertiesChanged({} as any); - expect(spy).toHaveBeenCalled(); - spy.mockRestore(); + it('playerPropertiesChanged dispatches via GameDispatch', () => { + SessionPersistence.playerPropertiesChanged(5, 1, {} as any); + expect(GameDispatch.playerPropertiesChanged).toHaveBeenCalledWith(5, 1, {}); }); it('serverShutdown passes data', () => { diff --git a/webclient/src/websocket/persistence/SessionPersistence.ts b/webclient/src/websocket/persistence/SessionPersistence.ts index 9962af968..960a844f6 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.ts @@ -1,5 +1,6 @@ -import { ServerDispatch } from 'store'; +import { GameDispatch, ServerDispatch } from 'store'; import { DeckList, DeckStorageTreeItem, ReplayMatch, StatusEnum, User, WebSocketConnectOptions } from 'types'; +import { GameEntry } from 'store/game/game.interfaces'; import { sanitizeHtml } from 'websocket/utils'; import { @@ -180,15 +181,32 @@ export class SessionPersistence { } static gameJoined(gameJoinedData: GameJoinedData): void { - console.log('gameJoined', gameJoinedData); + const { gameInfo, hostId, playerId, spectator, judge } = gameJoinedData; + const gameEntry: GameEntry = { + gameId: gameInfo.gameId, + roomId: gameInfo.roomId, + description: gameInfo.description, + hostId, + localPlayerId: playerId, + spectator, + judge, + started: gameInfo.started, + activePlayerId: -1, + activePhase: -1, + secondsElapsed: 0, + reversed: false, + players: {}, + messages: [], + }; + GameDispatch.gameJoined(gameInfo.gameId, gameEntry); } static notifyUser(notification: NotifyUserData): void { ServerDispatch.notifyUser(notification); } - static playerPropertiesChanged(payload: PlayerGamePropertiesData): void { - console.log('playerPropertiesChanged', payload); + static playerPropertiesChanged(gameId: number, playerId: number, payload: PlayerGamePropertiesData): void { + GameDispatch.playerPropertiesChanged(gameId, playerId, payload); } static serverShutdown(data: ServerShutdownData): void { diff --git a/webclient/src/websocket/services/BackendService.ts b/webclient/src/websocket/services/BackendService.ts index fce741d35..94319c47e 100644 --- a/webclient/src/websocket/services/BackendService.ts +++ b/webclient/src/websocket/services/BackendService.ts @@ -10,6 +10,16 @@ export interface CommandOptions { } export class BackendService { + static sendGameCommand(gameId: number, commandName: string, params: any, options: CommandOptions = {}): void { + const command = ProtoController.root[commandName].create(params || {}); + const gc = ProtoController.root.GameCommand.create({ + [`.${commandName}.ext`]: command, + }); + webClient.protobuf.sendGameCommand(gameId, gc, (raw: any) => { + BackendService.handleResponse(commandName, raw, options); + }); + } + static sendSessionCommand(commandName: string, params: any, options: CommandOptions): void { const command = ProtoController.root[commandName].create(params || {}); const sc = ProtoController.root.SessionCommand.create({ diff --git a/webclient/src/websocket/services/ProtobufService.ts b/webclient/src/websocket/services/ProtobufService.ts index 20c4e8599..626e65913 100644 --- a/webclient/src/websocket/services/ProtobufService.ts +++ b/webclient/src/websocket/services/ProtobufService.ts @@ -2,6 +2,7 @@ import { CommonEvents, GameEvents, RoomEvents, SessionEvents } from '../events'; import { WebClient } from '../WebClient'; import { SessionCommands } from 'websocket'; import { ProtoController } from './ProtoController'; +import { GameEventMeta } from 'types'; export interface ProtobufEvents { [event: string]: Function; @@ -23,6 +24,15 @@ export class ProtobufService { this.pendingCommands = {}; } + public sendGameCommand(gameId: number, gameCmd: any, callback?: Function) { + const cmd = ProtoController.root.CommandContainer.create({ + gameId, + gameCommand: [gameCmd], + }); + + this.sendCommand(cmd, (raw: any) => callback && callback(raw)); + } + public sendRoomCommand(roomId: number, roomCmd: any, callback?: Function) { const cmd = ProtoController.root.CommandContainer.create({ 'roomId': roomId, @@ -88,7 +98,7 @@ export class ProtobufService { this.processSessionEvent(msg.sessionEvent, msg); break; case ProtoController.root.ServerMessage.MessageType.GAME_EVENT_CONTAINER: - this.processGameEvent(msg.gameEvent, msg); + this.processGameEvent(msg.gameEventContainer, msg); break; default: console.log(msg); @@ -121,8 +131,42 @@ export class ProtobufService { this.processEvent(response, SessionEvents, raw); } - private processGameEvent(response: any, raw: any): void { - this.processEvent(response, GameEvents, raw); + private processGameEvent(container: any, raw: any): void { + if (!container?.eventList?.length) { + return; + } + + const { gameId, context, secondsElapsed, forcedByJudge } = container; + + for (const event of container.eventList) { + const meta: GameEventMeta = { + gameId: gameId ?? -1, + playerId: event.playerId ?? -1, + context, + secondsElapsed: secondsElapsed ?? 0, + forcedByJudge: forcedByJudge ?? 0, + }; + + // Try registered game event handlers first, then common event handlers + let handled = false; + for (const key of Object.keys(GameEvents)) { + const payload = event[key]; + if (payload !== undefined && payload !== null) { + (GameEvents[key] as Function)(payload, meta); + handled = true; + break; + } + } + if (!handled) { + for (const key of Object.keys(CommonEvents)) { + const payload = event[key]; + if (payload !== undefined && payload !== null) { + (CommonEvents[key] as Function)(payload, meta); + break; + } + } + } + } } private processEvent(response: any, events: ProtobufEvents, raw: any) { From 367852866fd6b6d2ff7ca3adb55eb058da20e7ea Mon Sep 17 00:00:00 2001 From: seavor Date: Sun, 12 Apr 2026 11:33:55 -0500 Subject: [PATCH 04/38] implement test coverage for game layer --- .../src/store/game/__mocks__/fixtures.ts | 123 ++ webclient/src/store/game/game.actions.spec.ts | 175 +++ .../src/store/game/game.dispatch.spec.ts | 198 +++ webclient/src/store/game/game.reducer.spec.ts | 1316 +++++++++++++++++ .../src/store/game/game.selectors.spec.ts | 158 ++ .../commands/game/gameCommands.spec.ts | 200 +++ .../events/common/commonEvents.spec.ts | 20 +- .../websocket/events/game/gameEvents.spec.ts | 272 +++- .../persistence/GamePersistence.spec.ts | 182 +++ .../persistence/SessionPersistence.spec.ts | 9 +- .../websocket/services/BackendService.spec.ts | 12 + .../services/ProtobufService.spec.ts | 80 +- 12 files changed, 2721 insertions(+), 24 deletions(-) create mode 100644 webclient/src/store/game/__mocks__/fixtures.ts create mode 100644 webclient/src/store/game/game.actions.spec.ts create mode 100644 webclient/src/store/game/game.dispatch.spec.ts create mode 100644 webclient/src/store/game/game.reducer.spec.ts create mode 100644 webclient/src/store/game/game.selectors.spec.ts create mode 100644 webclient/src/websocket/commands/game/gameCommands.spec.ts diff --git a/webclient/src/store/game/__mocks__/fixtures.ts b/webclient/src/store/game/__mocks__/fixtures.ts new file mode 100644 index 000000000..ca19cdadd --- /dev/null +++ b/webclient/src/store/game/__mocks__/fixtures.ts @@ -0,0 +1,123 @@ +import { ArrowInfo, CardInfo, CounterInfo, PlayerProperties } from 'types'; +import { GameEntry, GamesState, PlayerEntry, ZoneEntry } from '../game.interfaces'; + +export function makeCard(overrides: Partial = {}): CardInfo { + return { + id: 1, + name: 'Test Card', + x: 0, + y: 0, + faceDown: false, + tapped: false, + attacking: false, + color: '', + pt: '', + annotation: '', + destroyOnZoneChange: false, + doesntUntap: false, + counterList: [], + attachPlayerId: -1, + attachZone: '', + attachCardId: -1, + providerId: '', + ...overrides, + }; +} + +export function makeCounter(overrides: Partial = {}): CounterInfo { + return { + id: 1, + name: 'Life', + counterColor: { r: 0, g: 0, b: 0, a: 255 }, + radius: 1, + count: 20, + ...overrides, + }; +} + +export function makeArrow(overrides: Partial = {}): ArrowInfo { + return { + id: 1, + startPlayerId: 1, + startZone: 'table', + startCardId: 1, + targetPlayerId: 1, + targetZone: 'table', + targetCardId: 2, + arrowColor: { r: 255, g: 0, b: 0, a: 255 }, + ...overrides, + }; +} + +export function makeZoneEntry(overrides: Partial = {}): ZoneEntry { + return { + name: 'hand', + type: 1, + withCoords: false, + cardCount: 0, + cards: [], + alwaysRevealTopCard: false, + alwaysLookAtTopCard: false, + ...overrides, + }; +} + +export function makePlayerProperties(overrides: Partial = {}): PlayerProperties { + return { + playerId: 1, + userInfo: null, + spectator: false, + conceded: false, + readyStart: false, + deckHash: '', + pingSeconds: 0, + sideboardLocked: false, + judge: false, + ...overrides, + }; +} + +export function makePlayerEntry(overrides: Partial = {}): PlayerEntry { + return { + properties: makePlayerProperties(), + deckList: '', + zones: { + hand: makeZoneEntry({ name: 'hand' }), + deck: makeZoneEntry({ name: 'deck' }), + }, + counters: {}, + arrows: {}, + ...overrides, + }; +} + +export function makeGameEntry(overrides: Partial = {}): GameEntry { + return { + gameId: 1, + roomId: 1, + description: 'Test Game', + hostId: 1, + localPlayerId: 1, + spectator: false, + judge: false, + started: false, + activePlayerId: 0, + activePhase: 0, + secondsElapsed: 0, + reversed: false, + players: { + 1: makePlayerEntry(), + }, + messages: [], + ...overrides, + }; +} + +export function makeState(overrides: Partial = {}): GamesState { + return { + games: { + 1: makeGameEntry(), + }, + ...overrides, + }; +} diff --git a/webclient/src/store/game/game.actions.spec.ts b/webclient/src/store/game/game.actions.spec.ts new file mode 100644 index 000000000..0fb2c361a --- /dev/null +++ b/webclient/src/store/game/game.actions.spec.ts @@ -0,0 +1,175 @@ +import { Actions } from './game.actions'; +import { Types } from './game.types'; +import { + makeArrow, + makeCard, + makeCounter, + makeGameEntry, + makePlayerProperties, + makeZoneEntry, +} from './__mocks__/fixtures'; + +describe('Actions', () => { + it('clearStore', () => { + expect(Actions.clearStore()).toEqual({ type: Types.CLEAR_STORE }); + }); + + it('gameJoined', () => { + const entry = makeGameEntry(); + expect(Actions.gameJoined(1, entry)).toEqual({ type: Types.GAME_JOINED, gameId: 1, gameEntry: entry }); + }); + + it('gameLeft', () => { + expect(Actions.gameLeft(2)).toEqual({ type: Types.GAME_LEFT, gameId: 2 }); + }); + + it('gameClosed', () => { + expect(Actions.gameClosed(3)).toEqual({ type: Types.GAME_CLOSED, gameId: 3 }); + }); + + it('gameHostChanged', () => { + expect(Actions.gameHostChanged(1, 7)).toEqual({ type: Types.GAME_HOST_CHANGED, gameId: 1, hostId: 7 }); + }); + + it('gameStateChanged', () => { + const data = { playerList: [], gameStarted: true, activePlayerId: 1, activePhase: 0, secondsElapsed: 0 }; + expect(Actions.gameStateChanged(1, data)).toEqual({ type: Types.GAME_STATE_CHANGED, gameId: 1, data }); + }); + + it('playerJoined', () => { + const props = makePlayerProperties(); + expect(Actions.playerJoined(1, props)).toEqual({ type: Types.PLAYER_JOINED, gameId: 1, playerProperties: props }); + }); + + it('playerLeft', () => { + expect(Actions.playerLeft(1, 2, 3)).toEqual({ type: Types.PLAYER_LEFT, gameId: 1, playerId: 2, reason: 3 }); + }); + + it('playerPropertiesChanged', () => { + const props = makePlayerProperties(); + expect(Actions.playerPropertiesChanged(1, 2, props)).toEqual({ + type: Types.PLAYER_PROPERTIES_CHANGED, + gameId: 1, + playerId: 2, + properties: props, + }); + }); + + it('kicked', () => { + expect(Actions.kicked(1)).toEqual({ type: Types.KICKED, gameId: 1 }); + }); + + it('cardMoved', () => { + const data = { cardId: 1 } as any; + expect(Actions.cardMoved(1, 2, data)).toEqual({ type: Types.CARD_MOVED, gameId: 1, playerId: 2, data }); + }); + + it('cardFlipped', () => { + const data = { cardId: 1 } as any; + expect(Actions.cardFlipped(1, 2, data)).toEqual({ type: Types.CARD_FLIPPED, gameId: 1, playerId: 2, data }); + }); + + it('cardDestroyed', () => { + const data = { cardId: 1 } as any; + expect(Actions.cardDestroyed(1, 2, data)).toEqual({ type: Types.CARD_DESTROYED, gameId: 1, playerId: 2, data }); + }); + + it('cardAttached', () => { + const data = { cardId: 1 } as any; + expect(Actions.cardAttached(1, 2, data)).toEqual({ type: Types.CARD_ATTACHED, gameId: 1, playerId: 2, data }); + }); + + it('tokenCreated', () => { + const data = { cardId: 1 } as any; + expect(Actions.tokenCreated(1, 2, data)).toEqual({ type: Types.TOKEN_CREATED, gameId: 1, playerId: 2, data }); + }); + + it('cardAttrChanged', () => { + const data = { cardId: 1 } as any; + expect(Actions.cardAttrChanged(1, 2, data)).toEqual({ type: Types.CARD_ATTR_CHANGED, gameId: 1, playerId: 2, data }); + }); + + it('cardCounterChanged', () => { + const data = { cardId: 1 } as any; + expect(Actions.cardCounterChanged(1, 2, data)).toEqual({ type: Types.CARD_COUNTER_CHANGED, gameId: 1, playerId: 2, data }); + }); + + it('arrowCreated', () => { + const arrow = makeArrow(); + const data = { arrowInfo: arrow }; + expect(Actions.arrowCreated(1, 2, data)).toEqual({ type: Types.ARROW_CREATED, gameId: 1, playerId: 2, data }); + }); + + it('arrowDeleted', () => { + const data = { arrowId: 3 }; + expect(Actions.arrowDeleted(1, 2, data)).toEqual({ type: Types.ARROW_DELETED, gameId: 1, playerId: 2, data }); + }); + + it('counterCreated', () => { + const counter = makeCounter(); + const data = { counterInfo: counter }; + expect(Actions.counterCreated(1, 2, data)).toEqual({ type: Types.COUNTER_CREATED, gameId: 1, playerId: 2, data }); + }); + + it('counterSet', () => { + const data = { counterId: 1, value: 10 }; + expect(Actions.counterSet(1, 2, data)).toEqual({ type: Types.COUNTER_SET, gameId: 1, playerId: 2, data }); + }); + + it('counterDeleted', () => { + const data = { counterId: 1 }; + expect(Actions.counterDeleted(1, 2, data)).toEqual({ type: Types.COUNTER_DELETED, gameId: 1, playerId: 2, data }); + }); + + it('cardsDrawn', () => { + const card = makeCard(); + const data = { number: 2, cards: [card] }; + expect(Actions.cardsDrawn(1, 2, data)).toEqual({ type: Types.CARDS_DRAWN, gameId: 1, playerId: 2, data }); + }); + + it('cardsRevealed', () => { + const data = { zoneName: 'hand', cards: [] } as any; + expect(Actions.cardsRevealed(1, 2, data)).toEqual({ type: Types.CARDS_REVEALED, gameId: 1, playerId: 2, data }); + }); + + it('zoneShuffled', () => { + const data = { zoneName: 'deck', start: 0, end: 39 }; + expect(Actions.zoneShuffled(1, 2, data)).toEqual({ type: Types.ZONE_SHUFFLED, gameId: 1, playerId: 2, data }); + }); + + it('dieRolled', () => { + const data = { sides: 6, value: 4, values: [4] }; + expect(Actions.dieRolled(1, 2, data)).toEqual({ type: Types.DIE_ROLLED, gameId: 1, playerId: 2, data }); + }); + + it('activePlayerSet', () => { + expect(Actions.activePlayerSet(1, 3)).toEqual({ type: Types.ACTIVE_PLAYER_SET, gameId: 1, activePlayerId: 3 }); + }); + + it('activePhaseSet', () => { + expect(Actions.activePhaseSet(1, 2)).toEqual({ type: Types.ACTIVE_PHASE_SET, gameId: 1, phase: 2 }); + }); + + it('turnReversed', () => { + expect(Actions.turnReversed(1, true)).toEqual({ type: Types.TURN_REVERSED, gameId: 1, reversed: true }); + }); + + it('zoneDumped', () => { + const data = { zoneOwnerId: 1, zoneName: 'hand', numberCards: 3, isReversed: false }; + expect(Actions.zoneDumped(1, 2, data)).toEqual({ type: Types.ZONE_DUMPED, gameId: 1, playerId: 2, data }); + }); + + it('zonePropertiesChanged', () => { + const data = { zoneName: 'deck', alwaysRevealTopCard: true, alwaysLookAtTopCard: false }; + expect(Actions.zonePropertiesChanged(1, 2, data)).toEqual({ + type: Types.ZONE_PROPERTIES_CHANGED, + gameId: 1, + playerId: 2, + data, + }); + }); + + it('gameSay', () => { + expect(Actions.gameSay(1, 2, 'hello')).toEqual({ type: Types.GAME_SAY, gameId: 1, playerId: 2, message: 'hello' }); + }); +}); diff --git a/webclient/src/store/game/game.dispatch.spec.ts b/webclient/src/store/game/game.dispatch.spec.ts new file mode 100644 index 000000000..e4f9c51b9 --- /dev/null +++ b/webclient/src/store/game/game.dispatch.spec.ts @@ -0,0 +1,198 @@ +jest.mock('store/store', () => ({ store: { dispatch: jest.fn() } })); + +import { store } from 'store/store'; +import { Actions } from './game.actions'; +import { Dispatch } from './game.dispatch'; +import { + makeArrow, + makeCard, + makeCounter, + makeGameEntry, + makePlayerProperties, +} from './__mocks__/fixtures'; + +beforeEach(() => jest.clearAllMocks()); + +describe('Dispatch', () => { + it('clearStore dispatches Actions.clearStore()', () => { + Dispatch.clearStore(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.clearStore()); + }); + + it('gameJoined dispatches Actions.gameJoined()', () => { + const entry = makeGameEntry(); + Dispatch.gameJoined(1, entry); + expect(store.dispatch).toHaveBeenCalledWith(Actions.gameJoined(1, entry)); + }); + + it('gameLeft dispatches Actions.gameLeft()', () => { + Dispatch.gameLeft(2); + expect(store.dispatch).toHaveBeenCalledWith(Actions.gameLeft(2)); + }); + + it('gameClosed dispatches Actions.gameClosed()', () => { + Dispatch.gameClosed(3); + expect(store.dispatch).toHaveBeenCalledWith(Actions.gameClosed(3)); + }); + + it('gameHostChanged dispatches Actions.gameHostChanged()', () => { + Dispatch.gameHostChanged(1, 7); + expect(store.dispatch).toHaveBeenCalledWith(Actions.gameHostChanged(1, 7)); + }); + + it('gameStateChanged dispatches Actions.gameStateChanged()', () => { + const data = { playerList: [], gameStarted: false, activePlayerId: 0, activePhase: 0, secondsElapsed: 0 }; + Dispatch.gameStateChanged(1, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.gameStateChanged(1, data)); + }); + + it('playerJoined dispatches Actions.playerJoined()', () => { + const props = makePlayerProperties(); + Dispatch.playerJoined(1, props); + expect(store.dispatch).toHaveBeenCalledWith(Actions.playerJoined(1, props)); + }); + + it('playerLeft dispatches Actions.playerLeft()', () => { + Dispatch.playerLeft(1, 2, 3); + expect(store.dispatch).toHaveBeenCalledWith(Actions.playerLeft(1, 2, 3)); + }); + + it('playerPropertiesChanged dispatches Actions.playerPropertiesChanged()', () => { + const props = makePlayerProperties(); + Dispatch.playerPropertiesChanged(1, 2, props); + expect(store.dispatch).toHaveBeenCalledWith(Actions.playerPropertiesChanged(1, 2, props)); + }); + + it('kicked dispatches Actions.kicked()', () => { + Dispatch.kicked(1); + expect(store.dispatch).toHaveBeenCalledWith(Actions.kicked(1)); + }); + + it('cardMoved dispatches Actions.cardMoved()', () => { + const data = { cardId: 1 } as any; + Dispatch.cardMoved(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.cardMoved(1, 2, data)); + }); + + it('cardFlipped dispatches Actions.cardFlipped()', () => { + const data = { cardId: 1 } as any; + Dispatch.cardFlipped(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.cardFlipped(1, 2, data)); + }); + + it('cardDestroyed dispatches Actions.cardDestroyed()', () => { + const data = { cardId: 1 } as any; + Dispatch.cardDestroyed(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.cardDestroyed(1, 2, data)); + }); + + it('cardAttached dispatches Actions.cardAttached()', () => { + const data = { cardId: 1 } as any; + Dispatch.cardAttached(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.cardAttached(1, 2, data)); + }); + + it('tokenCreated dispatches Actions.tokenCreated()', () => { + const data = { cardId: 1 } as any; + Dispatch.tokenCreated(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.tokenCreated(1, 2, data)); + }); + + it('cardAttrChanged dispatches Actions.cardAttrChanged()', () => { + const data = { cardId: 1 } as any; + Dispatch.cardAttrChanged(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.cardAttrChanged(1, 2, data)); + }); + + it('cardCounterChanged dispatches Actions.cardCounterChanged()', () => { + const data = { cardId: 1 } as any; + Dispatch.cardCounterChanged(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.cardCounterChanged(1, 2, data)); + }); + + it('arrowCreated dispatches Actions.arrowCreated()', () => { + const data = { arrowInfo: makeArrow() }; + Dispatch.arrowCreated(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.arrowCreated(1, 2, data)); + }); + + it('arrowDeleted dispatches Actions.arrowDeleted()', () => { + const data = { arrowId: 3 }; + Dispatch.arrowDeleted(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.arrowDeleted(1, 2, data)); + }); + + it('counterCreated dispatches Actions.counterCreated()', () => { + const data = { counterInfo: makeCounter() }; + Dispatch.counterCreated(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.counterCreated(1, 2, data)); + }); + + it('counterSet dispatches Actions.counterSet()', () => { + const data = { counterId: 1, value: 10 }; + Dispatch.counterSet(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.counterSet(1, 2, data)); + }); + + it('counterDeleted dispatches Actions.counterDeleted()', () => { + const data = { counterId: 1 }; + Dispatch.counterDeleted(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.counterDeleted(1, 2, data)); + }); + + it('cardsDrawn dispatches Actions.cardsDrawn()', () => { + const data = { number: 2, cards: [makeCard()] }; + Dispatch.cardsDrawn(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.cardsDrawn(1, 2, data)); + }); + + it('cardsRevealed dispatches Actions.cardsRevealed()', () => { + const data = { zoneName: 'hand', cards: [] } as any; + Dispatch.cardsRevealed(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.cardsRevealed(1, 2, data)); + }); + + it('zoneShuffled dispatches Actions.zoneShuffled()', () => { + const data = { zoneName: 'deck', start: 0, end: 39 }; + Dispatch.zoneShuffled(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.zoneShuffled(1, 2, data)); + }); + + it('dieRolled dispatches Actions.dieRolled()', () => { + const data = { sides: 6, value: 4, values: [4] }; + Dispatch.dieRolled(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.dieRolled(1, 2, data)); + }); + + it('activePlayerSet dispatches Actions.activePlayerSet()', () => { + Dispatch.activePlayerSet(1, 3); + expect(store.dispatch).toHaveBeenCalledWith(Actions.activePlayerSet(1, 3)); + }); + + it('activePhaseSet dispatches Actions.activePhaseSet()', () => { + Dispatch.activePhaseSet(1, 2); + expect(store.dispatch).toHaveBeenCalledWith(Actions.activePhaseSet(1, 2)); + }); + + it('turnReversed dispatches Actions.turnReversed()', () => { + Dispatch.turnReversed(1, true); + expect(store.dispatch).toHaveBeenCalledWith(Actions.turnReversed(1, true)); + }); + + it('zoneDumped dispatches Actions.zoneDumped()', () => { + const data = { zoneOwnerId: 1, zoneName: 'hand', numberCards: 3, isReversed: false }; + Dispatch.zoneDumped(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.zoneDumped(1, 2, data)); + }); + + it('zonePropertiesChanged dispatches Actions.zonePropertiesChanged()', () => { + const data = { zoneName: 'deck', alwaysRevealTopCard: true, alwaysLookAtTopCard: false }; + Dispatch.zonePropertiesChanged(1, 2, data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.zonePropertiesChanged(1, 2, data)); + }); + + it('gameSay dispatches Actions.gameSay()', () => { + Dispatch.gameSay(1, 2, 'gg wp'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.gameSay(1, 2, 'gg wp')); + }); +}); diff --git a/webclient/src/store/game/game.reducer.spec.ts b/webclient/src/store/game/game.reducer.spec.ts new file mode 100644 index 000000000..92e8477cf --- /dev/null +++ b/webclient/src/store/game/game.reducer.spec.ts @@ -0,0 +1,1316 @@ +import { CardAttribute, PlayerInfo } from 'types'; +import { gamesReducer } from './game.reducer'; +import { Types } from './game.types'; +import { + makeArrow, + makeCard, + makeCounter, + makeGameEntry, + makePlayerEntry, + makePlayerProperties, + makeState, + makeZoneEntry, +} from './__mocks__/fixtures'; + +// ── 2A: Initialisation & lifecycle ─────────────────────────────────────────── + +describe('2A: Initialisation & lifecycle', () => { + it('returns initialState ({ games: {} }) when called with undefined state', () => { + const result = gamesReducer(undefined, { type: '@@INIT' }); + expect(result).toEqual({ games: {} }); + }); + + it('CLEAR_STORE → resets to initialState', () => { + const state = makeState(); + const result = gamesReducer(state, { type: Types.CLEAR_STORE }); + expect(result).toEqual({ games: {} }); + }); + + it('GAME_JOINED → inserts gameEntry keyed by gameId', () => { + const entry = makeGameEntry({ gameId: 42 }); + const result = gamesReducer({ games: {} }, { type: Types.GAME_JOINED, gameId: 42, gameEntry: entry }); + expect(result.games[42]).toBe(entry); + }); + + it('GAME_LEFT → removes game by gameId', () => { + const state = makeState(); + const result = gamesReducer(state, { type: Types.GAME_LEFT, gameId: 1 }); + expect(result.games[1]).toBeUndefined(); + }); + + it('GAME_CLOSED → removes game by gameId', () => { + const state = makeState(); + const result = gamesReducer(state, { type: Types.GAME_CLOSED, gameId: 1 }); + expect(result.games[1]).toBeUndefined(); + }); + + it('KICKED → removes game by gameId', () => { + const state = makeState(); + const result = gamesReducer(state, { type: Types.KICKED, gameId: 1 }); + expect(result.games[1]).toBeUndefined(); + }); + + it('GAME_HOST_CHANGED → updates hostId on existing game', () => { + const state = makeState(); + const result = gamesReducer(state, { type: Types.GAME_HOST_CHANGED, gameId: 1, hostId: 99 }); + expect(result.games[1].hostId).toBe(99); + expect(result).not.toBe(state); + }); +}); + +// ── 2B: Game state & player management ─────────────────────────────────────── + +describe('2B: Game state & player management', () => { + it('GAME_STATE_CHANGED with playerList → replaces players via normalizePlayers', () => { + const state = makeState(); + const card = makeCard({ id: 5 }); + const counter = makeCounter({ id: 2 }); + const arrow = makeArrow({ id: 3 }); + const playerList: PlayerInfo[] = [ + { + properties: makePlayerProperties({ playerId: 7 }), + deckList: 'some deck', + zoneList: [ + { + name: 'hand', + type: 1, + withCoords: false, + cardCount: 1, + cardList: [card], + alwaysRevealTopCard: false, + alwaysLookAtTopCard: false, + }, + ], + counterList: [counter], + arrowList: [arrow], + }, + ]; + + const result = gamesReducer(state, { + type: Types.GAME_STATE_CHANGED, + gameId: 1, + data: { playerList }, + }); + + const player = result.games[1].players[7]; + expect(player).toBeDefined(); + expect(player.zones['hand'].cards[0]).toEqual(card); + expect(player.counters[2]).toEqual(counter); + expect(player.arrows[3]).toEqual(arrow); + }); + + it('GAME_STATE_CHANGED with scalar fields → updates started, activePlayerId, activePhase, secondsElapsed', () => { + const state = makeState(); + const result = gamesReducer(state, { + type: Types.GAME_STATE_CHANGED, + gameId: 1, + data: { + gameStarted: true, + activePlayerId: 3, + activePhase: 2, + secondsElapsed: 60, + }, + }); + + expect(result.games[1].started).toBe(true); + expect(result.games[1].activePlayerId).toBe(3); + expect(result.games[1].activePhase).toBe(2); + expect(result.games[1].secondsElapsed).toBe(60); + }); + + it('PLAYER_JOINED → adds new empty PlayerEntry keyed by playerId', () => { + const state = makeState(); + const props = makePlayerProperties({ playerId: 5 }); + const result = gamesReducer(state, { type: Types.PLAYER_JOINED, gameId: 1, playerProperties: props }); + const newPlayer = result.games[1].players[5]; + expect(newPlayer).toBeDefined(); + expect(newPlayer.properties).toBe(props); + expect(newPlayer.zones).toEqual({}); + expect(newPlayer.counters).toEqual({}); + expect(newPlayer.arrows).toEqual({}); + }); + + it('PLAYER_LEFT → removes player from game.players', () => { + const state = makeState(); + const result = gamesReducer(state, { type: Types.PLAYER_LEFT, gameId: 1, playerId: 1 }); + expect(result.games[1].players[1]).toBeUndefined(); + }); + + it('PLAYER_PROPERTIES_CHANGED → replaces properties on existing player', () => { + const state = makeState(); + const newProps = makePlayerProperties({ playerId: 1, conceded: true }); + const result = gamesReducer(state, { + type: Types.PLAYER_PROPERTIES_CHANGED, + gameId: 1, + playerId: 1, + properties: newProps, + }); + expect(result.games[1].players[1].properties).toBe(newProps); + }); +}); + +// ── 2C: CARD_MOVED ──────────────────────────────────────────────────────────── + +describe('2C: CARD_MOVED', () => { + function stateWithCard(cardOverrides: Parameters[0] = {}) { + const card = makeCard({ id: 10, ...cardOverrides }); + const state = makeState({ + games: { + 1: makeGameEntry({ + players: { + 1: makePlayerEntry({ + zones: { + hand: makeZoneEntry({ name: 'hand', cards: [card], cardCount: 1 }), + table: makeZoneEntry({ name: 'table', cardCount: 0 }), + }, + }), + }, + }), + }, + }); + return { state, card }; + } + + it('moves card by cardId ≥ 0 from source to target zone', () => { + const { state } = stateWithCard(); + const result = gamesReducer(state, { + type: Types.CARD_MOVED, + gameId: 1, + playerId: 1, + data: { + cardId: 10, + cardName: '', + startPlayerId: 1, + startZone: 'hand', + position: -1, + targetPlayerId: 1, + targetZone: 'table', + x: 5, + y: 7, + newCardId: -1, + faceDown: false, + newCardProviderId: '', + }, + }); + + expect(result.games[1].players[1].zones['hand'].cards).toHaveLength(0); + expect(result.games[1].players[1].zones['hand'].cardCount).toBe(0); + const movedCard = result.games[1].players[1].zones['table'].cards[0]; + expect(movedCard.id).toBe(10); + expect(movedCard.x).toBe(5); + expect(movedCard.y).toBe(7); + expect(result.games[1].players[1].zones['table'].cardCount).toBe(1); + }); + + it('moves card by position index when cardId < 0', () => { + const card = makeCard({ id: 11 }); + const state = makeState({ + games: { + 1: makeGameEntry({ + players: { + 1: makePlayerEntry({ + zones: { + deck: makeZoneEntry({ name: 'deck', cards: [card], cardCount: 1 }), + hand: makeZoneEntry({ name: 'hand', cardCount: 0 }), + }, + }), + }, + }), + }, + }); + + const result = gamesReducer(state, { + type: Types.CARD_MOVED, + gameId: 1, + playerId: 1, + data: { + cardId: -1, + cardName: '', + startPlayerId: 1, + startZone: 'deck', + position: 0, + targetPlayerId: 1, + targetZone: 'hand', + x: 0, + y: 0, + newCardId: -1, + faceDown: false, + newCardProviderId: '', + }, + }); + + expect(result.games[1].players[1].zones['deck'].cards).toHaveLength(0); + expect(result.games[1].players[1].zones['hand'].cards[0].id).toBe(11); + }); + + it('hidden-zone move: cardId < 0, position out of range → decrements source cardCount, builds empty card in target', () => { + const state = makeState({ + games: { + 1: makeGameEntry({ + players: { + 1: makePlayerEntry({ + zones: { + deck: makeZoneEntry({ name: 'deck', cards: [], cardCount: 5 }), + hand: makeZoneEntry({ name: 'hand', cards: [], cardCount: 0 }), + }, + }), + }, + }), + }, + }); + + const result = gamesReducer(state, { + type: Types.CARD_MOVED, + gameId: 1, + playerId: 1, + data: { + cardId: -1, + cardName: 'Hidden', + startPlayerId: 1, + startZone: 'deck', + position: 99, + targetPlayerId: 1, + targetZone: 'hand', + x: 0, + y: 0, + newCardId: 7, + faceDown: true, + newCardProviderId: 'prov', + }, + }); + + expect(result.games[1].players[1].zones['deck'].cardCount).toBe(4); + const movedCard = result.games[1].players[1].zones['hand'].cards[0]; + expect(movedCard.id).toBe(7); + expect(movedCard.name).toBe('Hidden'); + expect(movedCard.faceDown).toBe(true); + }); + + it('cross-player move: card leaves source player zone and enters target player zone', () => { + const card = makeCard({ id: 20 }); + const state = makeState({ + games: { + 1: makeGameEntry({ + players: { + 1: makePlayerEntry({ + zones: { + hand: makeZoneEntry({ name: 'hand', cards: [card], cardCount: 1 }), + }, + }), + 2: makePlayerEntry({ + properties: makePlayerProperties({ playerId: 2 }), + zones: { + hand: makeZoneEntry({ name: 'hand', cardCount: 0 }), + }, + }), + }, + }), + }, + }); + + const result = gamesReducer(state, { + type: Types.CARD_MOVED, + gameId: 1, + playerId: 1, + data: { + cardId: 20, + cardName: '', + startPlayerId: 1, + startZone: 'hand', + position: -1, + targetPlayerId: 2, + targetZone: 'hand', + x: 0, + y: 0, + newCardId: -1, + faceDown: false, + newCardProviderId: '', + }, + }); + + expect(result.games[1].players[1].zones['hand'].cards).toHaveLength(0); + expect(result.games[1].players[2].zones['hand'].cards[0].id).toBe(20); + }); + + it('assigns newCardId when newCardId ≥ 0', () => { + const { state } = stateWithCard(); + const result = gamesReducer(state, { + type: Types.CARD_MOVED, + gameId: 1, + playerId: 1, + data: { + cardId: 10, + cardName: '', + startPlayerId: 1, + startZone: 'hand', + position: -1, + targetPlayerId: 1, + targetZone: 'table', + x: 0, + y: 0, + newCardId: 999, + faceDown: false, + newCardProviderId: '', + }, + }); + + expect(result.games[1].players[1].zones['table'].cards[0].id).toBe(999); + }); + + it('applies newCardProviderId and cardName to moved card', () => { + const { state } = stateWithCard({ name: 'Old Name', providerId: 'old-prov' }); + const result = gamesReducer(state, { + type: Types.CARD_MOVED, + gameId: 1, + playerId: 1, + data: { + cardId: 10, + cardName: 'New Name', + startPlayerId: 1, + startZone: 'hand', + position: -1, + targetPlayerId: 1, + targetZone: 'table', + x: 0, + y: 0, + newCardId: -1, + faceDown: false, + newCardProviderId: 'new-prov', + }, + }); + + const moved = result.games[1].players[1].zones['table'].cards[0]; + expect(moved.name).toBe('New Name'); + expect(moved.providerId).toBe('new-prov'); + }); +}); + +// ── 2D: Card mutations ──────────────────────────────────────────────────────── + +describe('2D: Card mutations', () => { + function stateWithCardInZone(zoneName: string) { + const card = makeCard({ id: 5, name: 'Old', providerId: 'old', faceDown: false }); + return { + card, + state: makeState({ + games: { + 1: makeGameEntry({ + players: { + 1: makePlayerEntry({ + zones: { + [zoneName]: makeZoneEntry({ name: zoneName, cards: [card], cardCount: 1 }), + }, + }), + }, + }), + }, + }), + }; + } + + it('CARD_FLIPPED → updates faceDown, name, and providerId', () => { + const { state } = stateWithCardInZone('hand'); + const result = gamesReducer(state, { + type: Types.CARD_FLIPPED, + gameId: 1, + playerId: 1, + data: { zoneName: 'hand', cardId: 5, cardName: 'Revealed', faceDown: true, cardProviderId: 'new-prov' }, + }); + + const card = result.games[1].players[1].zones['hand'].cards[0]; + expect(card.faceDown).toBe(true); + expect(card.name).toBe('Revealed'); + expect(card.providerId).toBe('new-prov'); + }); + + it('CARD_DESTROYED → removes card from zone and decrements cardCount', () => { + const { state } = stateWithCardInZone('hand'); + const result = gamesReducer(state, { + type: Types.CARD_DESTROYED, + gameId: 1, + playerId: 1, + data: { zoneName: 'hand', cardId: 5 }, + }); + + expect(result.games[1].players[1].zones['hand'].cards).toHaveLength(0); + expect(result.games[1].players[1].zones['hand'].cardCount).toBe(0); + }); + + it('CARD_ATTACHED → sets attachPlayerId, attachZone, attachCardId on matched card', () => { + const { state } = stateWithCardInZone('table'); + const result = gamesReducer(state, { + type: Types.CARD_ATTACHED, + gameId: 1, + playerId: 1, + data: { startZone: 'table', cardId: 5, targetPlayerId: 2, targetZone: 'table', targetCardId: 99 }, + }); + + const card = result.games[1].players[1].zones['table'].cards[0]; + expect(card.attachPlayerId).toBe(2); + expect(card.attachZone).toBe('table'); + expect(card.attachCardId).toBe(99); + }); + + it('TOKEN_CREATED → builds full CardInfo, appends to zone, increments cardCount', () => { + const state = makeState({ + games: { + 1: makeGameEntry({ + players: { + 1: makePlayerEntry({ + zones: { + table: makeZoneEntry({ name: 'table', cards: [], cardCount: 0 }), + }, + }), + }, + }), + }, + }); + + const result = gamesReducer(state, { + type: Types.TOKEN_CREATED, + gameId: 1, + playerId: 1, + data: { + zoneName: 'table', + cardId: 77, + cardName: 'Goblin', + color: 'red', + pt: '1/1', + annotation: '', + destroyOnZoneChange: true, + x: 3, + y: 4, + cardProviderId: 'prov', + faceDown: false, + }, + }); + + const zone = result.games[1].players[1].zones['table']; + expect(zone.cardCount).toBe(1); + expect(zone.cards[0].id).toBe(77); + expect(zone.cards[0].name).toBe('Goblin'); + expect(zone.cards[0].destroyOnZoneChange).toBe(true); + }); +}); + +// ── 2E: CARD_ATTR_CHANGED ───────────────────────────────────────────────────── + +describe('2E: CARD_ATTR_CHANGED', () => { + function stateWithCard() { + const card = makeCard({ + id: 3, + tapped: false, + attacking: false, + faceDown: false, + color: '', + pt: '', + annotation: '', + doesntUntap: false, + }); + return makeState({ + games: { + 1: makeGameEntry({ + players: { + 1: makePlayerEntry({ + zones: { + table: makeZoneEntry({ name: 'table', cards: [card], cardCount: 1 }), + }, + }), + }, + }), + }, + }); + } + + function dispatchAttr(state: ReturnType, attribute: CardAttribute, attrValue: string) { + return gamesReducer(state, { + type: Types.CARD_ATTR_CHANGED, + gameId: 1, + playerId: 1, + data: { zoneName: 'table', cardId: 3, attribute, attrValue }, + }); + } + + it('AttrTapped (1) → card.tapped = true when attrValue is "1"', () => { + const result = dispatchAttr(stateWithCard(), CardAttribute.AttrTapped, '1'); + expect(result.games[1].players[1].zones['table'].cards[0].tapped).toBe(true); + }); + + it('AttrAttacking (2) → card.attacking = true when attrValue is "1"', () => { + const result = dispatchAttr(stateWithCard(), CardAttribute.AttrAttacking, '1'); + expect(result.games[1].players[1].zones['table'].cards[0].attacking).toBe(true); + }); + + it('AttrFaceDown (3) → card.faceDown = true when attrValue is "1"', () => { + const result = dispatchAttr(stateWithCard(), CardAttribute.AttrFaceDown, '1'); + expect(result.games[1].players[1].zones['table'].cards[0].faceDown).toBe(true); + }); + + it('AttrColor (4) → card.color = attrValue', () => { + const result = dispatchAttr(stateWithCard(), CardAttribute.AttrColor, 'red'); + expect(result.games[1].players[1].zones['table'].cards[0].color).toBe('red'); + }); + + it('AttrPT (5) → card.pt = attrValue', () => { + const result = dispatchAttr(stateWithCard(), CardAttribute.AttrPT, '2/3'); + expect(result.games[1].players[1].zones['table'].cards[0].pt).toBe('2/3'); + }); + + it('AttrAnnotation (6) → card.annotation = attrValue', () => { + const result = dispatchAttr(stateWithCard(), CardAttribute.AttrAnnotation, 'enchanted'); + expect(result.games[1].players[1].zones['table'].cards[0].annotation).toBe('enchanted'); + }); + + it('AttrDoesntUntap (7) → card.doesntUntap = true when attrValue is "1"', () => { + const result = dispatchAttr(stateWithCard(), CardAttribute.AttrDoesntUntap, '1'); + expect(result.games[1].players[1].zones['table'].cards[0].doesntUntap).toBe(true); + }); +}); + +// ── 2F: CARD_COUNTER_CHANGED ───────────────────────────────────────────────── + +describe('2F: CARD_COUNTER_CHANGED', () => { + function stateWithCard(existingCounters: any[] = []) { + const card = makeCard({ id: 4, counterList: existingCounters }); + return makeState({ + games: { + 1: makeGameEntry({ + players: { + 1: makePlayerEntry({ + zones: { + table: makeZoneEntry({ name: 'table', cards: [card], cardCount: 1 }), + }, + }), + }, + }), + }, + }); + } + + it('adds new counter to counterList when counterId not present and counterValue > 0', () => { + const state = stateWithCard([]); + const result = gamesReducer(state, { + type: Types.CARD_COUNTER_CHANGED, + gameId: 1, + playerId: 1, + data: { zoneName: 'table', cardId: 4, counterId: 1, counterValue: 3 }, + }); + expect(result.games[1].players[1].zones['table'].cards[0].counterList).toEqual([{ id: 1, value: 3 }]); + }); + + it('updates existing counter value when counterId matches', () => { + const state = stateWithCard([{ id: 1, value: 3 }]); + const result = gamesReducer(state, { + type: Types.CARD_COUNTER_CHANGED, + gameId: 1, + playerId: 1, + data: { zoneName: 'table', cardId: 4, counterId: 1, counterValue: 7 }, + }); + expect(result.games[1].players[1].zones['table'].cards[0].counterList).toEqual([{ id: 1, value: 7 }]); + }); + + it('removes counter from counterList when counterValue ≤ 0', () => { + const state = stateWithCard([{ id: 1, value: 3 }]); + const result = gamesReducer(state, { + type: Types.CARD_COUNTER_CHANGED, + gameId: 1, + playerId: 1, + data: { zoneName: 'table', cardId: 4, counterId: 1, counterValue: 0 }, + }); + expect(result.games[1].players[1].zones['table'].cards[0].counterList).toEqual([]); + }); +}); + +// ── 2G: Arrows ──────────────────────────────────────────────────────────────── + +describe('2G: Arrows', () => { + it('ARROW_CREATED → inserts arrowInfo into player.arrows keyed by id', () => { + const state = makeState(); + const arrow = makeArrow({ id: 9 }); + const result = gamesReducer(state, { + type: Types.ARROW_CREATED, + gameId: 1, + playerId: 1, + data: { arrowInfo: arrow }, + }); + expect(result.games[1].players[1].arrows[9]).toEqual(arrow); + }); + + it('ARROW_DELETED → removes arrow from player.arrows by arrowId', () => { + const arrow = makeArrow({ id: 9 }); + const state = makeState({ + games: { + 1: makeGameEntry({ + players: { + 1: makePlayerEntry({ arrows: { 9: arrow } }), + }, + }), + }, + }); + const result = gamesReducer(state, { + type: Types.ARROW_DELETED, + gameId: 1, + playerId: 1, + data: { arrowId: 9 }, + }); + expect(result.games[1].players[1].arrows[9]).toBeUndefined(); + }); +}); + +// ── 2H: Player counters ─────────────────────────────────────────────────────── + +describe('2H: Player counters', () => { + it('COUNTER_CREATED → inserts counterInfo into player.counters keyed by id', () => { + const state = makeState(); + const counter = makeCounter({ id: 5, name: 'Poison' }); + const result = gamesReducer(state, { + type: Types.COUNTER_CREATED, + gameId: 1, + playerId: 1, + data: { counterInfo: counter }, + }); + expect(result.games[1].players[1].counters[5]).toEqual(counter); + }); + + it('COUNTER_SET → updates counter.count to new value', () => { + const counter = makeCounter({ id: 5, count: 20 }); + const state = makeState({ + games: { + 1: makeGameEntry({ + players: { + 1: makePlayerEntry({ counters: { 5: counter } }), + }, + }), + }, + }); + const result = gamesReducer(state, { + type: Types.COUNTER_SET, + gameId: 1, + playerId: 1, + data: { counterId: 5, value: 14 }, + }); + expect(result.games[1].players[1].counters[5].count).toBe(14); + }); + + it('COUNTER_DELETED → removes counter from player.counters by counterId', () => { + const counter = makeCounter({ id: 5 }); + const state = makeState({ + games: { + 1: makeGameEntry({ + players: { + 1: makePlayerEntry({ counters: { 5: counter } }), + }, + }), + }, + }); + const result = gamesReducer(state, { + type: Types.COUNTER_DELETED, + gameId: 1, + playerId: 1, + data: { counterId: 5 }, + }); + expect(result.games[1].players[1].counters[5]).toBeUndefined(); + }); +}); + +// ── 2I: Zone operations ─────────────────────────────────────────────────────── + +describe('2I: Zone operations', () => { + it('CARDS_DRAWN → decrements deck.cardCount, appends cards to hand, increments hand.cardCount', () => { + const drawnCard = makeCard({ id: 9 }); + const state = makeState({ + games: { + 1: makeGameEntry({ + players: { + 1: makePlayerEntry({ + zones: { + deck: makeZoneEntry({ name: 'deck', cardCount: 40 }), + hand: makeZoneEntry({ name: 'hand', cards: [], cardCount: 0 }), + }, + }), + }, + }), + }, + }); + + const result = gamesReducer(state, { + type: Types.CARDS_DRAWN, + gameId: 1, + playerId: 1, + data: { number: 2, cards: [drawnCard] }, + }); + + expect(result.games[1].players[1].zones['deck'].cardCount).toBe(38); + expect(result.games[1].players[1].zones['hand'].cards).toContainEqual(drawnCard); + expect(result.games[1].players[1].zones['hand'].cardCount).toBe(2); + }); + + it('CARDS_DRAWN → works when no deck zone exists (only updates hand)', () => { + const drawnCard = makeCard({ id: 9 }); + const state = makeState({ + games: { + 1: makeGameEntry({ + players: { + 1: makePlayerEntry({ + zones: { + hand: makeZoneEntry({ name: 'hand', cards: [], cardCount: 0 }), + }, + }), + }, + }), + }, + }); + + const result = gamesReducer(state, { + type: Types.CARDS_DRAWN, + gameId: 1, + playerId: 1, + data: { number: 1, cards: [drawnCard] }, + }); + + expect(result.games[1].players[1].zones['hand'].cardCount).toBe(1); + expect(result.games[1].players[1].zones['hand'].cards).toContainEqual(drawnCard); + }); + + it('CARDS_REVEALED (update path) → merges revealed cards into existing zone cards', () => { + const existing = makeCard({ id: 2, name: 'Old Name' }); + const state = makeState({ + games: { + 1: makeGameEntry({ + players: { + 1: makePlayerEntry({ + zones: { + deck: makeZoneEntry({ name: 'deck', cards: [existing], cardCount: 1 }), + }, + }), + }, + }), + }, + }); + + const result = gamesReducer(state, { + type: Types.CARDS_REVEALED, + gameId: 1, + playerId: 1, + data: { zoneName: 'deck', cards: [{ ...existing, name: 'Revealed Name' }] }, + }); + + expect(result.games[1].players[1].zones['deck'].cards[0].name).toBe('Revealed Name'); + expect(result.games[1].players[1].zones['deck'].cards).toHaveLength(1); + }); + + it('CARDS_REVEALED (append path) → appends new cards whose ids are not already in the zone', () => { + const existing = makeCard({ id: 1 }); + const newCard = makeCard({ id: 2 }); + const state = makeState({ + games: { + 1: makeGameEntry({ + players: { + 1: makePlayerEntry({ + zones: { + deck: makeZoneEntry({ name: 'deck', cards: [existing], cardCount: 1 }), + }, + }), + }, + }), + }, + }); + + const result = gamesReducer(state, { + type: Types.CARDS_REVEALED, + gameId: 1, + playerId: 1, + data: { zoneName: 'deck', cards: [newCard] }, + }); + + expect(result.games[1].players[1].zones['deck'].cards).toHaveLength(2); + expect(result.games[1].players[1].zones['deck'].cards[1]).toEqual(newCard); + }); + + it('ZONE_PROPERTIES_CHANGED → sets alwaysRevealTopCard and alwaysLookAtTopCard', () => { + const state = makeState(); + const result = gamesReducer(state, { + type: Types.ZONE_PROPERTIES_CHANGED, + gameId: 1, + playerId: 1, + data: { zoneName: 'hand', alwaysRevealTopCard: true, alwaysLookAtTopCard: true }, + }); + + const zone = result.games[1].players[1].zones['hand']; + expect(zone.alwaysRevealTopCard).toBe(true); + expect(zone.alwaysLookAtTopCard).toBe(true); + }); +}); + +// ── 2J: Turn / phase / chat ─────────────────────────────────────────────────── + +describe('2J: Turn, phase, and chat', () => { + it('ACTIVE_PLAYER_SET → sets game.activePlayerId', () => { + const state = makeState(); + const result = gamesReducer(state, { type: Types.ACTIVE_PLAYER_SET, gameId: 1, activePlayerId: 3 }); + expect(result.games[1].activePlayerId).toBe(3); + }); + + it('ACTIVE_PHASE_SET → sets game.activePhase', () => { + const state = makeState(); + const result = gamesReducer(state, { type: Types.ACTIVE_PHASE_SET, gameId: 1, phase: 5 }); + expect(result.games[1].activePhase).toBe(5); + }); + + it('TURN_REVERSED → sets game.reversed', () => { + const state = makeState(); + const result = gamesReducer(state, { type: Types.TURN_REVERSED, gameId: 1, reversed: true }); + expect(result.games[1].reversed).toBe(true); + }); + + it('GAME_SAY → appends message with mocked Date.now() as timeReceived', () => { + const state = makeState(); + jest.spyOn(Date, 'now').mockReturnValue(123456789); + const result = gamesReducer(state, { + type: Types.GAME_SAY, + gameId: 1, + playerId: 2, + message: 'gg', + }); + jest.restoreAllMocks(); + + expect(result.games[1].messages).toHaveLength(1); + expect(result.games[1].messages[0]).toEqual({ playerId: 2, message: 'gg', timeReceived: 123456789 }); + }); +}); + +// ── 2K: No-op / passthrough actions ────────────────────────────────────────── + +describe('2K: No-op / passthrough actions', () => { + it('ZONE_SHUFFLED → returns state unchanged (identity)', () => { + const state = makeState(); + const result = gamesReducer(state, { type: Types.ZONE_SHUFFLED, gameId: 1, playerId: 1 }); + expect(result).toBe(state); + }); + + it('ZONE_DUMPED → returns state unchanged (identity)', () => { + const state = makeState(); + const result = gamesReducer(state, { type: Types.ZONE_DUMPED, gameId: 1, playerId: 1 }); + expect(result).toBe(state); + }); + + it('DIE_ROLLED → returns state unchanged (identity)', () => { + const state = makeState(); + const result = gamesReducer(state, { type: Types.DIE_ROLLED, gameId: 1, playerId: 1 }); + expect(result).toBe(state); + }); + + it('unknown action type → returns state unchanged (identity)', () => { + const state = makeState(); + const result = gamesReducer(state, { type: 'UNKNOWN_ACTION_COMPLETELY' }); + expect(result).toBe(state); + }); +}); + +// ── 2L: Null-guard / missing entity early-returns ───────────────────────────── +// Each test dispatches an action with a non-existent gameId (999) or playerId/zone +// to exercise the `if (!game) return state` / `if (!player) return state` guards. + +describe('2L: Null-guard / missing entity early-returns', () => { + const UNKNOWN_GAME = 999; + const UNKNOWN_PLAYER = 999; + + it('updateGame guard: GAME_HOST_CHANGED with unknown gameId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { type: Types.GAME_HOST_CHANGED, gameId: UNKNOWN_GAME, hostId: 1 })).toBe(state); + }); + + it('GAME_STATE_CHANGED with unknown gameId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { type: Types.GAME_STATE_CHANGED, gameId: UNKNOWN_GAME, data: {} })).toBe(state); + }); + + it('PLAYER_JOINED with unknown gameId → state unchanged', () => { + const state = makeState(); + const props = makePlayerProperties({ playerId: 5 }); + expect(gamesReducer(state, { type: Types.PLAYER_JOINED, gameId: UNKNOWN_GAME, playerProperties: props })).toBe(state); + }); + + it('PLAYER_LEFT with unknown gameId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { type: Types.PLAYER_LEFT, gameId: UNKNOWN_GAME, playerId: 1 })).toBe(state); + }); + + it('updatePlayer guard: PLAYER_PROPERTIES_CHANGED with unknown gameId → state unchanged', () => { + const state = makeState(); + const props = makePlayerProperties({ playerId: 1 }); + expect(gamesReducer(state, { + type: Types.PLAYER_PROPERTIES_CHANGED, gameId: UNKNOWN_GAME, playerId: 1, properties: props, + })).toBe(state); + }); + + it('updatePlayer guard: PLAYER_PROPERTIES_CHANGED with unknown playerId → state unchanged', () => { + const state = makeState(); + const props = makePlayerProperties({ playerId: UNKNOWN_PLAYER }); + expect(gamesReducer(state, { + type: Types.PLAYER_PROPERTIES_CHANGED, gameId: 1, playerId: UNKNOWN_PLAYER, properties: props, + })).toBe(state); + }); + + it('CARD_MOVED with unknown gameId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_MOVED, gameId: UNKNOWN_GAME, playerId: 1, + data: { + cardId: 1, cardName: '', startPlayerId: 1, startZone: 'hand', position: -1, + targetPlayerId: 1, targetZone: 'hand', x: 0, y: 0, newCardId: -1, faceDown: false, newCardProviderId: '', + }, + })).toBe(state); + }); + + it('CARD_MOVED with unknown sourcePlayer → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_MOVED, gameId: 1, playerId: 1, + data: { + cardId: 1, cardName: '', startPlayerId: UNKNOWN_PLAYER, startZone: 'hand', position: -1, + targetPlayerId: 1, targetZone: 'hand', x: 0, y: 0, newCardId: -1, faceDown: false, newCardProviderId: '', + }, + })).toBe(state); + }); + + it('CARD_MOVED with unknown sourceZone → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_MOVED, gameId: 1, playerId: 1, + data: { + cardId: 1, cardName: '', startPlayerId: 1, startZone: 'nonexistent', position: -1, + targetPlayerId: 1, targetZone: 'hand', x: 0, y: 0, newCardId: -1, faceDown: false, newCardProviderId: '', + }, + })).toBe(state); + }); + + it('CARD_FLIPPED with unknown gameId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_FLIPPED, gameId: UNKNOWN_GAME, playerId: 1, + data: { zoneName: 'hand', cardId: 1, cardName: '', faceDown: false, cardProviderId: '' }, + })).toBe(state); + }); + + it('CARD_FLIPPED with unknown playerId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_FLIPPED, gameId: 1, playerId: UNKNOWN_PLAYER, + data: { zoneName: 'hand', cardId: 1, cardName: '', faceDown: false, cardProviderId: '' }, + })).toBe(state); + }); + + it('CARD_FLIPPED with unknown zone → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_FLIPPED, gameId: 1, playerId: 1, + data: { zoneName: 'nonexistent', cardId: 1, cardName: '', faceDown: false, cardProviderId: '' }, + })).toBe(state); + }); + + it('CARD_FLIPPED with unknown cardId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_FLIPPED, gameId: 1, playerId: 1, + data: { zoneName: 'hand', cardId: 9999, cardName: '', faceDown: false, cardProviderId: '' }, + })).toBe(state); + }); + + it('CARD_DESTROYED with unknown gameId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_DESTROYED, gameId: UNKNOWN_GAME, playerId: 1, + data: { zoneName: 'hand', cardId: 1 }, + })).toBe(state); + }); + + it('CARD_DESTROYED with unknown playerId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_DESTROYED, gameId: 1, playerId: UNKNOWN_PLAYER, + data: { zoneName: 'hand', cardId: 1 }, + })).toBe(state); + }); + + it('CARD_DESTROYED with unknown zone → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_DESTROYED, gameId: 1, playerId: 1, + data: { zoneName: 'nonexistent', cardId: 1 }, + })).toBe(state); + }); + + it('CARD_ATTACHED with unknown gameId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_ATTACHED, gameId: UNKNOWN_GAME, playerId: 1, + data: { startZone: 'hand', cardId: 1, targetPlayerId: 1, targetZone: 'hand', targetCardId: 1 }, + })).toBe(state); + }); + + it('CARD_ATTACHED with unknown playerId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_ATTACHED, gameId: 1, playerId: UNKNOWN_PLAYER, + data: { startZone: 'hand', cardId: 1, targetPlayerId: 1, targetZone: 'hand', targetCardId: 1 }, + })).toBe(state); + }); + + it('CARD_ATTACHED with unknown zone → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_ATTACHED, gameId: 1, playerId: 1, + data: { startZone: 'nonexistent', cardId: 1, targetPlayerId: 1, targetZone: 'hand', targetCardId: 1 }, + })).toBe(state); + }); + + it('CARD_ATTACHED with unknown cardId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_ATTACHED, gameId: 1, playerId: 1, + data: { startZone: 'hand', cardId: 9999, targetPlayerId: 1, targetZone: 'hand', targetCardId: 1 }, + })).toBe(state); + }); + + it('TOKEN_CREATED with unknown gameId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.TOKEN_CREATED, gameId: UNKNOWN_GAME, playerId: 1, + data: { + zoneName: 'hand', cardId: 1, cardName: 'T', color: '', pt: '', annotation: '', + destroyOnZoneChange: false, x: 0, y: 0, cardProviderId: '', faceDown: false, + }, + })).toBe(state); + }); + + it('TOKEN_CREATED with unknown playerId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.TOKEN_CREATED, gameId: 1, playerId: UNKNOWN_PLAYER, + data: { + zoneName: 'hand', cardId: 1, cardName: 'T', color: '', pt: '', annotation: '', + destroyOnZoneChange: false, x: 0, y: 0, cardProviderId: '', faceDown: false, + }, + })).toBe(state); + }); + + it('TOKEN_CREATED with unknown zone → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.TOKEN_CREATED, gameId: 1, playerId: 1, + data: { + zoneName: 'nonexistent', cardId: 1, cardName: 'T', color: '', pt: '', annotation: '', + destroyOnZoneChange: false, x: 0, y: 0, cardProviderId: '', faceDown: false, + }, + })).toBe(state); + }); + + it('CARD_ATTR_CHANGED with unknown gameId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_ATTR_CHANGED, gameId: UNKNOWN_GAME, playerId: 1, + data: { zoneName: 'hand', cardId: 1, attribute: 1, attrValue: '1' }, + })).toBe(state); + }); + + it('CARD_ATTR_CHANGED with unknown playerId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_ATTR_CHANGED, gameId: 1, playerId: UNKNOWN_PLAYER, + data: { zoneName: 'hand', cardId: 1, attribute: 1, attrValue: '1' }, + })).toBe(state); + }); + + it('CARD_ATTR_CHANGED with unknown zone → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_ATTR_CHANGED, gameId: 1, playerId: 1, + data: { zoneName: 'nonexistent', cardId: 1, attribute: 1, attrValue: '1' }, + })).toBe(state); + }); + + it('CARD_ATTR_CHANGED with unknown cardId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_ATTR_CHANGED, gameId: 1, playerId: 1, + data: { zoneName: 'hand', cardId: 9999, attribute: 1, attrValue: '1' }, + })).toBe(state); + }); + + it('CARD_COUNTER_CHANGED with unknown gameId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_COUNTER_CHANGED, gameId: UNKNOWN_GAME, playerId: 1, + data: { zoneName: 'hand', cardId: 1, counterId: 1, counterValue: 1 }, + })).toBe(state); + }); + + it('CARD_COUNTER_CHANGED with unknown playerId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_COUNTER_CHANGED, gameId: 1, playerId: UNKNOWN_PLAYER, + data: { zoneName: 'hand', cardId: 1, counterId: 1, counterValue: 1 }, + })).toBe(state); + }); + + it('CARD_COUNTER_CHANGED with unknown zone → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_COUNTER_CHANGED, gameId: 1, playerId: 1, + data: { zoneName: 'nonexistent', cardId: 1, counterId: 1, counterValue: 1 }, + })).toBe(state); + }); + + it('CARD_COUNTER_CHANGED with unknown cardId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARD_COUNTER_CHANGED, gameId: 1, playerId: 1, + data: { zoneName: 'hand', cardId: 9999, counterId: 1, counterValue: 1 }, + })).toBe(state); + }); + + it('ARROW_CREATED with unknown gameId → state unchanged', () => { + const state = makeState(); + const arrow = makeArrow({ id: 1 }); + expect(gamesReducer(state, { type: Types.ARROW_CREATED, gameId: UNKNOWN_GAME, playerId: 1, data: { arrowInfo: arrow } })).toBe(state); + }); + + it('ARROW_CREATED with unknown playerId → state unchanged', () => { + const state = makeState(); + const arrow = makeArrow({ id: 1 }); + expect(gamesReducer(state, { type: Types.ARROW_CREATED, gameId: 1, playerId: UNKNOWN_PLAYER, data: { arrowInfo: arrow } })).toBe(state); + }); + + it('ARROW_DELETED with unknown gameId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { type: Types.ARROW_DELETED, gameId: UNKNOWN_GAME, playerId: 1, data: { arrowId: 1 } })).toBe(state); + }); + + it('ARROW_DELETED with unknown playerId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { type: Types.ARROW_DELETED, gameId: 1, playerId: UNKNOWN_PLAYER, data: { arrowId: 1 } })).toBe(state); + }); + + it('COUNTER_CREATED with unknown gameId → state unchanged', () => { + const state = makeState(); + const counter = makeCounter({ id: 1 }); + expect(gamesReducer(state, { + type: Types.COUNTER_CREATED, gameId: UNKNOWN_GAME, playerId: 1, data: { counterInfo: counter }, + })).toBe(state); + }); + + it('COUNTER_CREATED with unknown playerId → state unchanged', () => { + const state = makeState(); + const counter = makeCounter({ id: 1 }); + expect(gamesReducer(state, { + type: Types.COUNTER_CREATED, gameId: 1, playerId: UNKNOWN_PLAYER, data: { counterInfo: counter }, + })).toBe(state); + }); + + it('COUNTER_SET with unknown gameId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.COUNTER_SET, gameId: UNKNOWN_GAME, playerId: 1, data: { counterId: 1, value: 5 }, + })).toBe(state); + }); + + it('COUNTER_SET with unknown playerId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.COUNTER_SET, gameId: 1, playerId: UNKNOWN_PLAYER, data: { counterId: 1, value: 5 }, + })).toBe(state); + }); + + it('COUNTER_SET with unknown counterId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { type: Types.COUNTER_SET, gameId: 1, playerId: 1, data: { counterId: 9999, value: 5 } })).toBe(state); + }); + + it('COUNTER_DELETED with unknown gameId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { type: Types.COUNTER_DELETED, gameId: UNKNOWN_GAME, playerId: 1, data: { counterId: 1 } })).toBe(state); + }); + + it('COUNTER_DELETED with unknown playerId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { type: Types.COUNTER_DELETED, gameId: 1, playerId: UNKNOWN_PLAYER, data: { counterId: 1 } })).toBe(state); + }); + + it('CARDS_DRAWN with unknown gameId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARDS_DRAWN, gameId: UNKNOWN_GAME, playerId: 1, data: { number: 1, cards: [] }, + })).toBe(state); + }); + + it('CARDS_DRAWN with unknown playerId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARDS_DRAWN, gameId: 1, playerId: UNKNOWN_PLAYER, data: { number: 1, cards: [] } + })).toBe(state); + }); + + it('CARDS_DRAWN with no hand zone → state unchanged', () => { + const state = makeState({ + games: { + 1: makeGameEntry({ + players: { + 1: makePlayerEntry({ zones: { deck: makeZoneEntry({ name: 'deck', cardCount: 10 }) } }), + }, + }), + }, + }); + expect(gamesReducer(state, { type: Types.CARDS_DRAWN, gameId: 1, playerId: 1, data: { number: 1, cards: [] } })).toBe(state); + }); + + it('CARDS_REVEALED with unknown gameId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARDS_REVEALED, gameId: UNKNOWN_GAME, playerId: 1, data: { zoneName: 'hand', cards: [] }, + })).toBe(state); + }); + + it('CARDS_REVEALED with unknown playerId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARDS_REVEALED, gameId: 1, playerId: UNKNOWN_PLAYER, data: { zoneName: 'hand', cards: [] }, + })).toBe(state); + }); + + it('CARDS_REVEALED with unknown zone → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.CARDS_REVEALED, gameId: 1, playerId: 1, data: { zoneName: 'nonexistent', cards: [] }, + })).toBe(state); + }); + + it('updateZone guard: ZONE_PROPERTIES_CHANGED with unknown gameId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.ZONE_PROPERTIES_CHANGED, gameId: UNKNOWN_GAME, playerId: 1, + data: { zoneName: 'hand', alwaysRevealTopCard: true, alwaysLookAtTopCard: true }, + })).toBe(state); + }); + + it('updateZone guard: ZONE_PROPERTIES_CHANGED with unknown playerId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.ZONE_PROPERTIES_CHANGED, gameId: 1, playerId: UNKNOWN_PLAYER, + data: { zoneName: 'hand', alwaysRevealTopCard: true, alwaysLookAtTopCard: true }, + })).toBe(state); + }); + + it('updateZone guard: ZONE_PROPERTIES_CHANGED with unknown zone → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { + type: Types.ZONE_PROPERTIES_CHANGED, gameId: 1, playerId: 1, + data: { zoneName: 'nonexistent', alwaysRevealTopCard: true, alwaysLookAtTopCard: true }, + })).toBe(state); + }); + + it('GAME_SAY with unknown gameId → state unchanged', () => { + const state = makeState(); + expect(gamesReducer(state, { type: Types.GAME_SAY, gameId: UNKNOWN_GAME, playerId: 1, message: 'hi' })).toBe(state); + }); +}); diff --git a/webclient/src/store/game/game.selectors.spec.ts b/webclient/src/store/game/game.selectors.spec.ts new file mode 100644 index 000000000..de38ef580 --- /dev/null +++ b/webclient/src/store/game/game.selectors.spec.ts @@ -0,0 +1,158 @@ +import { Selectors } from './game.selectors'; +import { + makeGameEntry, makePlayerEntry, makePlayerProperties, makeState, + makeZoneEntry, makeCard, makeCounter, makeArrow, +} from './__mocks__/fixtures'; +import { GamesState } from './game.interfaces'; + +function rootState(games: GamesState) { + return { games }; +} + +describe('Selectors', () => { + it('getGames → returns the games map', () => { + const state = makeState(); + expect(Selectors.getGames(rootState(state))).toBe(state.games); + }); + + it('getGame → returns the game entry for a given gameId', () => { + const state = makeState(); + expect(Selectors.getGame(rootState(state), 1)).toBe(state.games[1]); + }); + + it('getGame → returns undefined for unknown gameId', () => { + const state = makeState(); + expect(Selectors.getGame(rootState(state), 999)).toBeUndefined(); + }); + + it('getPlayers → returns players map for a game', () => { + const state = makeState(); + expect(Selectors.getPlayers(rootState(state), 1)).toBe(state.games[1].players); + }); + + it('getPlayers → returns undefined for unknown gameId', () => { + const state = makeState(); + expect(Selectors.getPlayers(rootState(state), 999)).toBeUndefined(); + }); + + it('getPlayer → returns a specific player', () => { + const state = makeState(); + expect(Selectors.getPlayer(rootState(state), 1, 1)).toBe(state.games[1].players[1]); + }); + + it('getLocalPlayerId → returns localPlayerId from game', () => { + const state = makeState({ games: { 1: makeGameEntry({ localPlayerId: 42 }) } }); + expect(Selectors.getLocalPlayerId(rootState(state), 1)).toBe(42); + }); + + it('getLocalPlayer → returns the player matching localPlayerId', () => { + const state = makeState({ games: { 1: makeGameEntry({ localPlayerId: 1 }) } }); + const result = Selectors.getLocalPlayer(rootState(state), 1); + expect(result).toBe(state.games[1].players[1]); + }); + + it('getLocalPlayer → returns undefined when game is not found', () => { + const state = makeState(); + expect(Selectors.getLocalPlayer(rootState(state), 999)).toBeUndefined(); + }); + + it('getZones → returns zones map for a player', () => { + const state = makeState(); + expect(Selectors.getZones(rootState(state), 1, 1)).toBe(state.games[1].players[1].zones); + }); + + it('getZone → returns a specific zone', () => { + const state = makeState(); + expect(Selectors.getZone(rootState(state), 1, 1, 'hand')).toBe(state.games[1].players[1].zones['hand']); + }); + + it('getCards → returns cards array for a zone', () => { + const card = makeCard(); + const state = makeState({ + games: { + 1: makeGameEntry({ + players: { + 1: makePlayerEntry({ + zones: { hand: makeZoneEntry({ name: 'hand', cards: [card] }) }, + }), + }, + }), + }, + }); + expect(Selectors.getCards(rootState(state), 1, 1, 'hand')).toEqual([card]); + }); + + it('getCards → returns [] when zone not found', () => { + const state = makeState(); + expect(Selectors.getCards(rootState(state), 1, 1, 'nonexistent')).toEqual([]); + }); + + it('getCounters → returns counters map for a player', () => { + const counter = makeCounter({ id: 2 }); + const state = makeState({ + games: { 1: makeGameEntry({ players: { 1: makePlayerEntry({ counters: { 2: counter } }) } }) }, + }); + expect(Selectors.getCounters(rootState(state), 1, 1)).toEqual({ 2: counter }); + }); + + it('getArrows → returns arrows map for a player', () => { + const arrow = makeArrow({ id: 3 }); + const state = makeState({ + games: { 1: makeGameEntry({ players: { 1: makePlayerEntry({ arrows: { 3: arrow } }) } }) }, + }); + expect(Selectors.getArrows(rootState(state), 1, 1)).toEqual({ 3: arrow }); + }); + + it('getActivePlayerId → returns activePlayerId from game', () => { + const state = makeState({ games: { 1: makeGameEntry({ activePlayerId: 7 }) } }); + expect(Selectors.getActivePlayerId(rootState(state), 1)).toBe(7); + }); + + it('getActivePhase → returns activePhase from game', () => { + const state = makeState({ games: { 1: makeGameEntry({ activePhase: 3 }) } }); + expect(Selectors.getActivePhase(rootState(state), 1)).toBe(3); + }); + + it('isStarted → returns true when game is started', () => { + const state = makeState({ games: { 1: makeGameEntry({ started: true }) } }); + expect(Selectors.isStarted(rootState(state), 1)).toBe(true); + }); + + it('isStarted → returns false when game not found', () => { + const state = makeState(); + expect(Selectors.isStarted(rootState(state), 999)).toBe(false); + }); + + it('isSpectator → returns spectator flag from game', () => { + const state = makeState({ games: { 1: makeGameEntry({ spectator: true }) } }); + expect(Selectors.isSpectator(rootState(state), 1)).toBe(true); + }); + + it('isReversed → returns reversed flag from game', () => { + const state = makeState({ games: { 1: makeGameEntry({ reversed: true }) } }); + expect(Selectors.isReversed(rootState(state), 1)).toBe(true); + }); + + it('getMessages → returns messages array from game', () => { + const messages = [{ playerId: 1, message: 'hi', timeReceived: 100 }]; + const state = makeState({ games: { 1: makeGameEntry({ messages }) } }); + expect(Selectors.getMessages(rootState(state), 1)).toBe(messages); + }); + + it('getMessages → returns [] when game not found', () => { + const state = makeState(); + expect(Selectors.getMessages(rootState(state), 999)).toEqual([]); + }); + + it('getActiveGameIds → returns numeric array of gameIds', () => { + const state = makeState({ + games: { + 1: makeGameEntry({ gameId: 1 }), + 2: makeGameEntry({ gameId: 2 }), + }, + }); + const ids = Selectors.getActiveGameIds(rootState(state)); + expect(ids).toEqual(expect.arrayContaining([1, 2])); + expect(ids).toHaveLength(2); + }); +}); diff --git a/webclient/src/websocket/commands/game/gameCommands.spec.ts b/webclient/src/websocket/commands/game/gameCommands.spec.ts new file mode 100644 index 000000000..6d802136d --- /dev/null +++ b/webclient/src/websocket/commands/game/gameCommands.spec.ts @@ -0,0 +1,200 @@ +import { BackendService } from '../../services/BackendService'; +import { attachCard } from './attachCard'; +import { changeZoneProperties } from './changeZoneProperties'; +import { concede } from './concede'; +import { createArrow } from './createArrow'; +import { createCounter } from './createCounter'; +import { createToken } from './createToken'; +import { deckSelect } from './deckSelect'; +import { delCounter } from './delCounter'; +import { deleteArrow } from './deleteArrow'; +import { drawCards } from './drawCards'; +import { dumpZone } from './dumpZone'; +import { flipCard } from './flipCard'; +import { gameSay } from './gameSay'; +import { incCardCounter } from './incCardCounter'; +import { incCounter } from './incCounter'; +import { kickFromGame } from './kickFromGame'; +import { leaveGame } from './leaveGame'; +import { moveCard } from './moveCard'; +import { mulligan } from './mulligan'; +import { nextTurn } from './nextTurn'; +import { readyStart } from './readyStart'; +import { revealCards } from './revealCards'; +import { reverseTurn } from './reverseTurn'; +import { setActivePhase } from './setActivePhase'; +import { setCardAttr } from './setCardAttr'; +import { setCardCounter } from './setCardCounter'; +import { setCounter } from './setCounter'; +import { setSideboardLock } from './setSideboardLock'; +import { setSideboardPlan } from './setSideboardPlan'; +import { shuffle } from './shuffle'; +import { undoDraw } from './undoDraw'; + +jest.mock('../../services/BackendService', () => ({ + BackendService: { sendGameCommand: jest.fn() }, +})); + +const gameId = 1; +const params = {} as any; + +beforeEach(() => { + (BackendService.sendGameCommand as jest.Mock).mockClear(); +}); + +describe('Game commands — delegate to BackendService.sendGameCommand', () => { + it('attachCard sends Command_AttachCard', () => { + attachCard(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_AttachCard', params); + }); + + it('changeZoneProperties sends Command_ChangeZoneProperties', () => { + changeZoneProperties(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_ChangeZoneProperties', params); + }); + + it('concede sends Command_Concede with empty object', () => { + concede(gameId); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_Concede', {}); + }); + + it('createArrow sends Command_CreateArrow', () => { + createArrow(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_CreateArrow', params); + }); + + it('createCounter sends Command_CreateCounter', () => { + createCounter(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_CreateCounter', params); + }); + + it('createToken sends Command_CreateToken', () => { + createToken(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_CreateToken', params); + }); + + it('deckSelect sends Command_DeckSelect', () => { + deckSelect(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_DeckSelect', params); + }); + + it('delCounter sends Command_DelCounter', () => { + delCounter(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_DelCounter', params); + }); + + it('deleteArrow sends Command_DeleteArrow', () => { + deleteArrow(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_DeleteArrow', params); + }); + + it('drawCards sends Command_DrawCards', () => { + drawCards(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_DrawCards', params); + }); + + it('dumpZone sends Command_DumpZone', () => { + dumpZone(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_DumpZone', params); + }); + + it('flipCard sends Command_FlipCard', () => { + flipCard(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_FlipCard', params); + }); + + it('gameSay sends Command_GameSay', () => { + gameSay(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_GameSay', params); + }); + + it('incCardCounter sends Command_IncCardCounter', () => { + incCardCounter(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_IncCardCounter', params); + }); + + it('incCounter sends Command_IncCounter', () => { + incCounter(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_IncCounter', params); + }); + + it('kickFromGame sends Command_KickFromGame', () => { + kickFromGame(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_KickFromGame', params); + }); + + it('leaveGame sends Command_LeaveGame with empty object', () => { + leaveGame(gameId); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_LeaveGame', {}); + }); + + it('moveCard sends Command_MoveCard', () => { + moveCard(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_MoveCard', params); + }); + + it('mulligan sends Command_Mulligan', () => { + mulligan(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_Mulligan', params); + }); + + it('nextTurn sends Command_NextTurn with empty object', () => { + nextTurn(gameId); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_NextTurn', {}); + }); + + it('readyStart sends Command_ReadyStart', () => { + readyStart(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_ReadyStart', params); + }); + + it('revealCards sends Command_RevealCards', () => { + revealCards(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_RevealCards', params); + }); + + it('reverseTurn sends Command_ReverseTurn with empty object', () => { + reverseTurn(gameId); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_ReverseTurn', {}); + }); + + it('setActivePhase sends Command_SetActivePhase', () => { + setActivePhase(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_SetActivePhase', params); + }); + + it('setCardAttr sends Command_SetCardAttr', () => { + setCardAttr(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_SetCardAttr', params); + }); + + it('setCardCounter sends Command_SetCardCounter', () => { + setCardCounter(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_SetCardCounter', params); + }); + + it('setCounter sends Command_SetCounter', () => { + setCounter(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_SetCounter', params); + }); + + it('setSideboardLock sends Command_SetSideboardLock', () => { + setSideboardLock(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_SetSideboardLock', params); + }); + + it('setSideboardPlan sends Command_SetSideboardPlan', () => { + setSideboardPlan(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_SetSideboardPlan', params); + }); + + it('shuffle sends Command_Shuffle', () => { + shuffle(gameId, params); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_Shuffle', params); + }); + + it('undoDraw sends Command_UndoDraw with empty object', () => { + undoDraw(gameId); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_UndoDraw', {}); + }); +}); diff --git a/webclient/src/websocket/events/common/commonEvents.spec.ts b/webclient/src/websocket/events/common/commonEvents.spec.ts index a080dc1e4..7dd74c37d 100644 --- a/webclient/src/websocket/events/common/commonEvents.spec.ts +++ b/webclient/src/websocket/events/common/commonEvents.spec.ts @@ -1,19 +1,7 @@ -jest.mock('../../persistence', () => ({ - SessionPersistence: { - playerPropertiesChanged: jest.fn(), - }, -})); +import { CommonEvents } from './index'; -import { SessionPersistence } from '../../persistence'; - -beforeEach(() => jest.clearAllMocks()); - -describe('playerPropertiesChanged', () => { - const { playerPropertiesChanged } = jest.requireActual('./playerPropertiesChanged'); - - it('delegates to SessionPersistence.playerPropertiesChanged', () => { - const payload = { gameId: 1, player: { playerId: 2 } } as any; - playerPropertiesChanged(payload); - expect(SessionPersistence.playerPropertiesChanged).toHaveBeenCalledWith(payload); +describe('CommonEvents', () => { + it('is an empty event map (all common events were moved to game/session events)', () => { + expect(CommonEvents).toEqual({}); }); }); diff --git a/webclient/src/websocket/events/game/gameEvents.spec.ts b/webclient/src/websocket/events/game/gameEvents.spec.ts index 7f4f9f6b5..82c78518d 100644 --- a/webclient/src/websocket/events/game/gameEvents.spec.ts +++ b/webclient/src/websocket/events/game/gameEvents.spec.ts @@ -1,21 +1,76 @@ jest.mock('../../persistence', () => ({ GamePersistence: { + gameStateChanged: jest.fn(), playerJoined: jest.fn(), playerLeft: jest.fn(), + playerPropertiesChanged: jest.fn(), + gameClosed: jest.fn(), + gameHostChanged: jest.fn(), + kicked: jest.fn(), + gameSay: jest.fn(), + cardMoved: jest.fn(), + cardFlipped: jest.fn(), + cardDestroyed: jest.fn(), + cardAttached: jest.fn(), + tokenCreated: jest.fn(), + cardAttrChanged: jest.fn(), + cardCounterChanged: jest.fn(), + arrowCreated: jest.fn(), + arrowDeleted: jest.fn(), + counterCreated: jest.fn(), + counterSet: jest.fn(), + counterDeleted: jest.fn(), + cardsDrawn: jest.fn(), + cardsRevealed: jest.fn(), + zoneShuffled: jest.fn(), + dieRolled: jest.fn(), + activePlayerSet: jest.fn(), + activePhaseSet: jest.fn(), + turnReversed: jest.fn(), + zoneDumped: jest.fn(), + zonePropertiesChanged: jest.fn(), }, })); import { GamePersistence } from '../../persistence'; +import { attachCard } from './attachCard'; +import { changeZoneProperties } from './changeZoneProperties'; +import { createArrow } from './createArrow'; +import { createCounter } from './createCounter'; +import { createToken } from './createToken'; +import { delCounter } from './delCounter'; +import { deleteArrow } from './deleteArrow'; +import { destroyCard } from './destroyCard'; +import { drawCards } from './drawCards'; +import { dumpZone } from './dumpZone'; +import { flipCard } from './flipCard'; +import { gameClosed } from './gameClosed'; +import { gameHostChanged } from './gameHostChanged'; +import { gameSay } from './gameSay'; +import { gameStateChanged } from './gameStateChanged'; import { joinGame } from './joinGame'; +import { kicked } from './kicked'; import { leaveGame } from './leaveGame'; +import { moveCard } from './moveCard'; +import { playerPropertiesChanged } from './playerPropertiesChanged'; +import { revealCards } from './revealCards'; +import { reverseTurn } from './reverseTurn'; +import { rollDie } from './rollDie'; +import { setActivePhase } from './setActivePhase'; +import { setActivePlayer } from './setActivePlayer'; +import { setCardAttr } from './setCardAttr'; +import { setCardCounter } from './setCardCounter'; +import { setCounter } from './setCounter'; +import { shuffle } from './shuffle'; beforeEach(() => jest.clearAllMocks()); +const meta = { gameId: 5, playerId: 2, context: null, secondsElapsed: 0, forcedByJudge: 0 }; + describe('joinGame event', () => { it('delegates to GamePersistence.playerJoined with gameId from meta', () => { const playerProperties = { playerId: 1 }; const data = { playerProperties } as any; - const meta = { gameId: 5, playerId: 1, context: null, secondsElapsed: 0, forcedByJudge: 0 }; joinGame(data, meta); expect(GamePersistence.playerJoined).toHaveBeenCalledWith(5, playerProperties); }); @@ -24,8 +79,221 @@ describe('joinGame event', () => { describe('leaveGame event', () => { it('delegates to GamePersistence.playerLeft with gameId/playerId from meta', () => { const data = { reason: 3 }; - const meta = { gameId: 5, playerId: 2, context: null, secondsElapsed: 0, forcedByJudge: 0 }; leaveGame(data, meta); expect(GamePersistence.playerLeft).toHaveBeenCalledWith(5, 2, 3); }); }); + +describe('gameClosed event', () => { + it('delegates to GamePersistence.gameClosed with gameId', () => { + gameClosed({}, meta); + expect(GamePersistence.gameClosed).toHaveBeenCalledWith(5); + }); +}); + +describe('gameHostChanged event', () => { + it('delegates to GamePersistence.gameHostChanged using meta.playerId as hostId', () => { + gameHostChanged({}, meta); + expect(GamePersistence.gameHostChanged).toHaveBeenCalledWith(5, 2); + }); +}); + +describe('kicked event', () => { + it('delegates to GamePersistence.kicked with gameId', () => { + kicked({}, meta); + expect(GamePersistence.kicked).toHaveBeenCalledWith(5); + }); +}); + +describe('gameStateChanged event', () => { + it('delegates to GamePersistence.gameStateChanged with gameId and full data', () => { + const data = { playerList: [] } as any; + gameStateChanged(data, meta); + expect(GamePersistence.gameStateChanged).toHaveBeenCalledWith(5, data); + }); +}); + +describe('playerPropertiesChanged event', () => { + it('delegates to GamePersistence.playerPropertiesChanged with gameId, playerId, properties', () => { + const playerProperties = { playerId: 2 } as any; + const data = { playerProperties } as any; + playerPropertiesChanged(data, meta); + expect(GamePersistence.playerPropertiesChanged).toHaveBeenCalledWith(5, 2, playerProperties); + }); +}); + +describe('gameSay event', () => { + it('delegates to GamePersistence.gameSay with gameId, playerId, message', () => { + const data = { message: 'gg' } as any; + gameSay(data, meta); + expect(GamePersistence.gameSay).toHaveBeenCalledWith(5, 2, 'gg'); + }); +}); + +describe('moveCard event', () => { + it('delegates to GamePersistence.cardMoved with gameId, playerId and data', () => { + const data = { cardId: 3 } as any; + moveCard(data, meta); + expect(GamePersistence.cardMoved).toHaveBeenCalledWith(5, 2, data); + }); +}); + +describe('flipCard event', () => { + it('delegates to GamePersistence.cardFlipped with gameId, playerId and data', () => { + const data = { cardId: 3 } as any; + flipCard(data, meta); + expect(GamePersistence.cardFlipped).toHaveBeenCalledWith(5, 2, data); + }); +}); + +describe('destroyCard event', () => { + it('delegates to GamePersistence.cardDestroyed with gameId, playerId and data', () => { + const data = { cardId: 3 } as any; + destroyCard(data, meta); + expect(GamePersistence.cardDestroyed).toHaveBeenCalledWith(5, 2, data); + }); +}); + +describe('attachCard event', () => { + it('delegates to GamePersistence.cardAttached with gameId, playerId and data', () => { + const data = { cardId: 3 } as any; + attachCard(data, meta); + expect(GamePersistence.cardAttached).toHaveBeenCalledWith(5, 2, data); + }); +}); + +describe('createToken event', () => { + it('delegates to GamePersistence.tokenCreated with gameId, playerId and data', () => { + const data = { cardId: 3 } as any; + createToken(data, meta); + expect(GamePersistence.tokenCreated).toHaveBeenCalledWith(5, 2, data); + }); +}); + +describe('setCardAttr event', () => { + it('delegates to GamePersistence.cardAttrChanged with gameId, playerId and data', () => { + const data = { cardId: 3 } as any; + setCardAttr(data, meta); + expect(GamePersistence.cardAttrChanged).toHaveBeenCalledWith(5, 2, data); + }); +}); + +describe('setCardCounter event', () => { + it('delegates to GamePersistence.cardCounterChanged with gameId, playerId and data', () => { + const data = { cardId: 3 } as any; + setCardCounter(data, meta); + expect(GamePersistence.cardCounterChanged).toHaveBeenCalledWith(5, 2, data); + }); +}); + +describe('createArrow event', () => { + it('delegates to GamePersistence.arrowCreated with gameId, playerId and data', () => { + const data = { arrowInfo: {} } as any; + createArrow(data, meta); + expect(GamePersistence.arrowCreated).toHaveBeenCalledWith(5, 2, data); + }); +}); + +describe('deleteArrow event', () => { + it('delegates to GamePersistence.arrowDeleted with gameId, playerId and data', () => { + const data = { arrowId: 9 } as any; + deleteArrow(data, meta); + expect(GamePersistence.arrowDeleted).toHaveBeenCalledWith(5, 2, data); + }); +}); + +describe('createCounter event', () => { + it('delegates to GamePersistence.counterCreated with gameId, playerId and data', () => { + const data = { counterInfo: {} } as any; + createCounter(data, meta); + expect(GamePersistence.counterCreated).toHaveBeenCalledWith(5, 2, data); + }); +}); + +describe('setCounter event', () => { + it('delegates to GamePersistence.counterSet with gameId, playerId and data', () => { + const data = { counterId: 1, value: 20 } as any; + setCounter(data, meta); + expect(GamePersistence.counterSet).toHaveBeenCalledWith(5, 2, data); + }); +}); + +describe('delCounter event', () => { + it('delegates to GamePersistence.counterDeleted with gameId, playerId and data', () => { + const data = { counterId: 1 } as any; + delCounter(data, meta); + expect(GamePersistence.counterDeleted).toHaveBeenCalledWith(5, 2, data); + }); +}); + +describe('drawCards event', () => { + it('delegates to GamePersistence.cardsDrawn with gameId, playerId and data', () => { + const data = { number: 2, cards: [] } as any; + drawCards(data, meta); + expect(GamePersistence.cardsDrawn).toHaveBeenCalledWith(5, 2, data); + }); +}); + +describe('revealCards event', () => { + it('delegates to GamePersistence.cardsRevealed with gameId, playerId and data', () => { + const data = { zoneName: 'hand', cards: [] } as any; + revealCards(data, meta); + expect(GamePersistence.cardsRevealed).toHaveBeenCalledWith(5, 2, data); + }); +}); + +describe('shuffle event', () => { + it('delegates to GamePersistence.zoneShuffled with gameId, playerId and data', () => { + const data = { zoneName: 'deck' } as any; + shuffle(data, meta); + expect(GamePersistence.zoneShuffled).toHaveBeenCalledWith(5, 2, data); + }); +}); + +describe('rollDie event', () => { + it('delegates to GamePersistence.dieRolled with gameId, playerId and data', () => { + const data = { die: 6, result: 4 } as any; + rollDie(data, meta); + expect(GamePersistence.dieRolled).toHaveBeenCalledWith(5, 2, data); + }); +}); + +describe('setActivePlayer event', () => { + it('delegates to GamePersistence.activePlayerSet with gameId and activePlayerId', () => { + const data = { activePlayerId: 3 } as any; + setActivePlayer(data, meta); + expect(GamePersistence.activePlayerSet).toHaveBeenCalledWith(5, 3); + }); +}); + +describe('setActivePhase event', () => { + it('delegates to GamePersistence.activePhaseSet with gameId and phase', () => { + const data = { phase: 4 } as any; + setActivePhase(data, meta); + expect(GamePersistence.activePhaseSet).toHaveBeenCalledWith(5, 4); + }); +}); + +describe('reverseTurn event', () => { + it('delegates to GamePersistence.turnReversed with gameId and reversed', () => { + const data = { reversed: true } as any; + reverseTurn(data, meta); + expect(GamePersistence.turnReversed).toHaveBeenCalledWith(5, true); + }); +}); + +describe('dumpZone event', () => { + it('delegates to GamePersistence.zoneDumped with gameId, playerId and data', () => { + const data = { zoneName: 'hand' } as any; + dumpZone(data, meta); + expect(GamePersistence.zoneDumped).toHaveBeenCalledWith(5, 2, data); + }); +}); + +describe('changeZoneProperties event', () => { + it('delegates to GamePersistence.zonePropertiesChanged with gameId, playerId and data', () => { + const data = { zoneName: 'hand', alwaysRevealTopCard: true } as any; + changeZoneProperties(data, meta); + expect(GamePersistence.zonePropertiesChanged).toHaveBeenCalledWith(5, 2, data); + }); +}); diff --git a/webclient/src/websocket/persistence/GamePersistence.spec.ts b/webclient/src/websocket/persistence/GamePersistence.spec.ts index 29377c339..43e7e86b0 100644 --- a/webclient/src/websocket/persistence/GamePersistence.spec.ts +++ b/webclient/src/websocket/persistence/GamePersistence.spec.ts @@ -2,8 +2,35 @@ import { GamePersistence } from './GamePersistence'; jest.mock('store', () => ({ GameDispatch: { + gameStateChanged: jest.fn(), playerJoined: jest.fn(), playerLeft: jest.fn(), + playerPropertiesChanged: jest.fn(), + gameClosed: jest.fn(), + gameHostChanged: jest.fn(), + kicked: jest.fn(), + gameSay: jest.fn(), + cardMoved: jest.fn(), + cardFlipped: jest.fn(), + cardDestroyed: jest.fn(), + cardAttached: jest.fn(), + tokenCreated: jest.fn(), + cardAttrChanged: jest.fn(), + cardCounterChanged: jest.fn(), + arrowCreated: jest.fn(), + arrowDeleted: jest.fn(), + counterCreated: jest.fn(), + counterSet: jest.fn(), + counterDeleted: jest.fn(), + cardsDrawn: jest.fn(), + cardsRevealed: jest.fn(), + zoneShuffled: jest.fn(), + dieRolled: jest.fn(), + activePlayerSet: jest.fn(), + activePhaseSet: jest.fn(), + turnReversed: jest.fn(), + zoneDumped: jest.fn(), + zonePropertiesChanged: jest.fn(), }, })); @@ -12,6 +39,12 @@ import { GameDispatch } from 'store'; beforeEach(() => jest.clearAllMocks()); describe('GamePersistence', () => { + it('gameStateChanged dispatches via GameDispatch', () => { + const data = { playerList: [] } as any; + GamePersistence.gameStateChanged(5, data); + expect(GameDispatch.gameStateChanged).toHaveBeenCalledWith(5, data); + }); + it('playerJoined dispatches via GameDispatch', () => { const data = { playerId: 1 } as any; GamePersistence.playerJoined(5, data); @@ -22,4 +55,153 @@ describe('GamePersistence', () => { GamePersistence.playerLeft(5, 1, 3); expect(GameDispatch.playerLeft).toHaveBeenCalledWith(5, 1, 3); }); + + it('playerPropertiesChanged dispatches via GameDispatch', () => { + const props = { playerId: 2 } as any; + GamePersistence.playerPropertiesChanged(5, 2, props); + expect(GameDispatch.playerPropertiesChanged).toHaveBeenCalledWith(5, 2, props); + }); + + it('gameClosed dispatches via GameDispatch', () => { + GamePersistence.gameClosed(5); + expect(GameDispatch.gameClosed).toHaveBeenCalledWith(5); + }); + + it('gameHostChanged dispatches via GameDispatch', () => { + GamePersistence.gameHostChanged(5, 7); + expect(GameDispatch.gameHostChanged).toHaveBeenCalledWith(5, 7); + }); + + it('kicked dispatches via GameDispatch', () => { + GamePersistence.kicked(5); + expect(GameDispatch.kicked).toHaveBeenCalledWith(5); + }); + + it('gameSay dispatches via GameDispatch', () => { + GamePersistence.gameSay(5, 1, 'hello'); + expect(GameDispatch.gameSay).toHaveBeenCalledWith(5, 1, 'hello'); + }); + + it('cardMoved dispatches via GameDispatch', () => { + const data = { cardId: 3 } as any; + GamePersistence.cardMoved(5, 1, data); + expect(GameDispatch.cardMoved).toHaveBeenCalledWith(5, 1, data); + }); + + it('cardFlipped dispatches via GameDispatch', () => { + const data = { cardId: 3 } as any; + GamePersistence.cardFlipped(5, 1, data); + expect(GameDispatch.cardFlipped).toHaveBeenCalledWith(5, 1, data); + }); + + it('cardDestroyed dispatches via GameDispatch', () => { + const data = { cardId: 3 } as any; + GamePersistence.cardDestroyed(5, 1, data); + expect(GameDispatch.cardDestroyed).toHaveBeenCalledWith(5, 1, data); + }); + + it('cardAttached dispatches via GameDispatch', () => { + const data = { cardId: 3 } as any; + GamePersistence.cardAttached(5, 1, data); + expect(GameDispatch.cardAttached).toHaveBeenCalledWith(5, 1, data); + }); + + it('tokenCreated dispatches via GameDispatch', () => { + const data = { cardId: 3 } as any; + GamePersistence.tokenCreated(5, 1, data); + expect(GameDispatch.tokenCreated).toHaveBeenCalledWith(5, 1, data); + }); + + it('cardAttrChanged dispatches via GameDispatch', () => { + const data = { cardId: 3 } as any; + GamePersistence.cardAttrChanged(5, 1, data); + expect(GameDispatch.cardAttrChanged).toHaveBeenCalledWith(5, 1, data); + }); + + it('cardCounterChanged dispatches via GameDispatch', () => { + const data = { cardId: 3 } as any; + GamePersistence.cardCounterChanged(5, 1, data); + expect(GameDispatch.cardCounterChanged).toHaveBeenCalledWith(5, 1, data); + }); + + it('arrowCreated dispatches via GameDispatch', () => { + const data = { arrowInfo: {} } as any; + GamePersistence.arrowCreated(5, 1, data); + expect(GameDispatch.arrowCreated).toHaveBeenCalledWith(5, 1, data); + }); + + it('arrowDeleted dispatches via GameDispatch', () => { + const data = { arrowId: 9 }; + GamePersistence.arrowDeleted(5, 1, data); + expect(GameDispatch.arrowDeleted).toHaveBeenCalledWith(5, 1, data); + }); + + it('counterCreated dispatches via GameDispatch', () => { + const data = { counterInfo: {} } as any; + GamePersistence.counterCreated(5, 1, data); + expect(GameDispatch.counterCreated).toHaveBeenCalledWith(5, 1, data); + }); + + it('counterSet dispatches via GameDispatch', () => { + const data = { counterId: 1, value: 20 }; + GamePersistence.counterSet(5, 1, data); + expect(GameDispatch.counterSet).toHaveBeenCalledWith(5, 1, data); + }); + + it('counterDeleted dispatches via GameDispatch', () => { + const data = { counterId: 1 }; + GamePersistence.counterDeleted(5, 1, data); + expect(GameDispatch.counterDeleted).toHaveBeenCalledWith(5, 1, data); + }); + + it('cardsDrawn dispatches via GameDispatch', () => { + const data = { number: 2, cards: [] } as any; + GamePersistence.cardsDrawn(5, 1, data); + expect(GameDispatch.cardsDrawn).toHaveBeenCalledWith(5, 1, data); + }); + + it('cardsRevealed dispatches via GameDispatch', () => { + const data = { zoneName: 'hand', cards: [] } as any; + GamePersistence.cardsRevealed(5, 1, data); + expect(GameDispatch.cardsRevealed).toHaveBeenCalledWith(5, 1, data); + }); + + it('zoneShuffled dispatches via GameDispatch', () => { + const data = { zoneName: 'deck' } as any; + GamePersistence.zoneShuffled(5, 1, data); + expect(GameDispatch.zoneShuffled).toHaveBeenCalledWith(5, 1, data); + }); + + it('dieRolled dispatches via GameDispatch', () => { + const data = { die: 6, result: 4 } as any; + GamePersistence.dieRolled(5, 1, data); + expect(GameDispatch.dieRolled).toHaveBeenCalledWith(5, 1, data); + }); + + it('activePlayerSet dispatches via GameDispatch', () => { + GamePersistence.activePlayerSet(5, 2); + expect(GameDispatch.activePlayerSet).toHaveBeenCalledWith(5, 2); + }); + + it('activePhaseSet dispatches via GameDispatch', () => { + GamePersistence.activePhaseSet(5, 3); + expect(GameDispatch.activePhaseSet).toHaveBeenCalledWith(5, 3); + }); + + it('turnReversed dispatches via GameDispatch', () => { + GamePersistence.turnReversed(5, true); + expect(GameDispatch.turnReversed).toHaveBeenCalledWith(5, true); + }); + + it('zoneDumped dispatches via GameDispatch', () => { + const data = { zoneName: 'hand' } as any; + GamePersistence.zoneDumped(5, 1, data); + expect(GameDispatch.zoneDumped).toHaveBeenCalledWith(5, 1, data); + }); + + it('zonePropertiesChanged dispatches via GameDispatch', () => { + const data = { zoneName: 'hand', alwaysRevealTopCard: true } as any; + GamePersistence.zonePropertiesChanged(5, 1, data); + expect(GameDispatch.zonePropertiesChanged).toHaveBeenCalledWith(5, 1, data); + }); }); diff --git a/webclient/src/websocket/persistence/SessionPersistence.spec.ts b/webclient/src/websocket/persistence/SessionPersistence.spec.ts index 38080d578..ff83f7684 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.spec.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.spec.ts @@ -309,11 +309,10 @@ describe('SessionPersistence', () => { spy.mockRestore(); }); - it('gameJoined logs to console', () => { - const spy = jest.spyOn(console, 'log').mockImplementation(() => {}); - SessionPersistence.gameJoined({ gameInfo: {} } as any); - expect(spy).toHaveBeenCalled(); - spy.mockRestore(); + it('gameJoined dispatches via GameDispatch.gameJoined', () => { + const gameInfo = { gameId: 10, roomId: 2, description: 'test', started: false }; + SessionPersistence.gameJoined({ gameInfo, hostId: 3, playerId: 4, spectator: false, judge: false } as any); + expect(GameDispatch.gameJoined).toHaveBeenCalledWith(10, expect.objectContaining({ gameId: 10, hostId: 3, localPlayerId: 4 })); }); it('notifyUser passes notification', () => { diff --git a/webclient/src/websocket/services/BackendService.spec.ts b/webclient/src/websocket/services/BackendService.spec.ts index bdc92c2a1..1619888dc 100644 --- a/webclient/src/websocket/services/BackendService.spec.ts +++ b/webclient/src/websocket/services/BackendService.spec.ts @@ -6,6 +6,7 @@ jest.mock('./ProtoController', () => ({ jest.mock('../WebClient', () => { const mockProtobuf = { + sendGameCommand: jest.fn(), sendSessionCommand: jest.fn(), sendRoomCommand: jest.fn(), sendModeratorCommand: jest.fn(), @@ -21,6 +22,8 @@ import webClient from '../WebClient'; beforeEach(() => { jest.clearAllMocks(); ProtoController.root = makeMockProtoRoot(); + ProtoController.root.GameCommand = { create: jest.fn(args => ({ ...args })) }; + ProtoController.root['Command_Game'] = { create: jest.fn(p => ({ ...p })) }; ProtoController.root['Command_Test'] = { create: jest.fn(p => ({ ...p })) }; ProtoController.root['Command_Room'] = { create: jest.fn(p => ({ ...p })) }; ProtoController.root['Command_Mod'] = { create: jest.fn(p => ({ ...p })) }; @@ -35,6 +38,7 @@ function captureCallback(sendFn: jest.Mock) { describe('BackendService', () => { describe('send commands', () => { it.each([ + ['sendGameCommand', () => BackendService.sendGameCommand(7, 'Command_Game', { g: 1 })], ['sendSessionCommand', () => BackendService.sendSessionCommand('Command_Test', { x: 1 }, {})], ['sendRoomCommand', () => BackendService.sendRoomCommand(5, 'Command_Room', { y: 2 }, {})], ['sendModeratorCommand', () => BackendService.sendModeratorCommand('Command_Mod', { z: 3 }, {})], @@ -46,6 +50,14 @@ describe('BackendService', () => { }); describe('handleResponse via non-session command callbacks', () => { + it('sendGameCommand callback invokes handleResponse', () => { + const onSuccess = jest.fn(); + BackendService.sendGameCommand(7, 'Command_Game', {}, { onSuccess }); + const cb = (webClient.protobuf as any).sendGameCommand.mock.calls[0][2]; + cb({ responseCode: 0 }); + expect(onSuccess).toHaveBeenCalled(); + }); + it('sendRoomCommand callback invokes handleResponse', () => { const onSuccess = jest.fn(); BackendService.sendRoomCommand(5, 'Command_Room', {}, { onSuccess }); diff --git a/webclient/src/websocket/services/ProtobufService.spec.ts b/webclient/src/websocket/services/ProtobufService.spec.ts index d0cfab31a..618f643a5 100644 --- a/webclient/src/websocket/services/ProtobufService.spec.ts +++ b/webclient/src/websocket/services/ProtobufService.spec.ts @@ -10,7 +10,7 @@ jest.mock('../commands/session', () => ({ })); jest.mock('../events', () => ({ - CommonEvents: {}, + CommonEvents: { '.Event_Common.ext': jest.fn() }, GameEvents: { '.Event_Game.ext': jest.fn() }, RoomEvents: { '.Event_Room.ext': jest.fn() }, SessionEvents: { '.Event_Session.ext': jest.fn() }, @@ -21,6 +21,7 @@ jest.mock('../WebClient'); import { ProtobufService } from './ProtobufService'; import { ProtoController } from './ProtoController'; import { ping as sessionPing } from '../commands/session'; +import { GameEvents, CommonEvents } from '../events'; let mockSocket: any; let mockWebClient: any; @@ -143,6 +144,35 @@ describe('ProtobufService', () => { }); }); + describe('sendGameCommand', () => { + it('creates a CommandContainer with gameId and gameCommand', () => { + const service = new ProtobufService(mockWebClient); + service.sendGameCommand(7, { gameCmdType: 'test' }, jest.fn()); + expect(ProtoController.root.CommandContainer.create).toHaveBeenCalledWith( + expect.objectContaining({ gameId: 7, gameCommand: expect.anything() }) + ); + }); + + it('invokes callback with raw response when the pending command is triggered', () => { + const service = new ProtobufService(mockWebClient); + const cb = jest.fn(); + service.sendGameCommand(7, { gameCmdType: 'test' }, cb); + + const storedCb = (service as any).pendingCommands[1]; + storedCb({ responseData: true }); + + expect(cb).toHaveBeenCalledWith({ responseData: true }); + }); + + it('does not throw when no callback is provided and pending command is triggered', () => { + const service = new ProtobufService(mockWebClient); + service.sendGameCommand(7, { gameCmdType: 'test' }); + + const storedCb = (service as any).pendingCommands[1]; + expect(() => storedCb({ responseData: true })).not.toThrow(); + }); + }); + describe('sendModeratorCommand', () => { it('creates a CommandContainer with moderatorCommand', () => { const service = new ProtobufService(mockWebClient); @@ -291,6 +321,54 @@ describe('ProtobufService', () => { }); }); + describe('processCommonEvent', () => { + it('delegates to processEvent with CommonEvents', () => { + const service = new ProtobufService(mockWebClient); + const processEvent = jest.spyOn(service as any, 'processEvent'); + const response = { '.Event_Common.ext': { data: 1 } }; + const raw = { extra: true }; + (service as any).processCommonEvent(response, raw); + expect(processEvent).toHaveBeenCalledWith(response, CommonEvents, raw); + }); + }); + + describe('processGameEvent', () => { + it('returns early when container has no eventList', () => { + const service = new ProtobufService(mockWebClient); + const gameEventHandler = (GameEvents as any)['.Event_Game.ext'] as jest.Mock; + (service as any).processGameEvent(null, {}); + expect(gameEventHandler).not.toHaveBeenCalled(); + }); + + it('dispatches to a GameEvents handler when event key matches', () => { + const service = new ProtobufService(mockWebClient); + const gameEventHandler = (GameEvents as any)['.Event_Game.ext'] as jest.Mock; + const payload = { someData: 1 }; + (service as any).processGameEvent({ + gameId: 42, + context: null, + secondsElapsed: 10, + forcedByJudge: 0, + eventList: [{ '.Event_Game.ext': payload, playerId: 5 }], + }, {}); + expect(gameEventHandler).toHaveBeenCalledWith(payload, expect.objectContaining({ gameId: 42, playerId: 5 })); + }); + + it('falls back to CommonEvents handler when no GameEvents key matches', () => { + const service = new ProtobufService(mockWebClient); + const commonEventHandler = (CommonEvents as any)['.Event_Common.ext'] as jest.Mock; + const payload = { commonData: 2 }; + (service as any).processGameEvent({ + gameId: 7, + context: null, + secondsElapsed: 0, + forcedByJudge: 0, + eventList: [{ '.Event_Common.ext': payload, playerId: 3 }], + }, {}); + expect(commonEventHandler).toHaveBeenCalledWith(payload, expect.objectContaining({ gameId: 7, playerId: 3 })); + }); + }); + describe('processEvent', () => { it('calls matching event handler with payload and raw', () => { const service = new ProtobufService(mockWebClient); From 3001925430aba61d402a2f4ff757083552bc3b49 Mon Sep 17 00:00:00 2001 From: seavor Date: Sun, 12 Apr 2026 12:53:51 -0500 Subject: [PATCH 05/38] complete unit testing of redux and api layers --- webclient/src/api/AdminService.spec.ts | 48 ++ .../src/api/AuthenticationService.spec.ts | 145 +++++ webclient/src/api/ModeratorService.spec.ts | 75 +++ webclient/src/api/RoomsService.spec.ts | 37 ++ webclient/src/api/SessionService.spec.ts | 102 ++++ .../src/store/actions/actionReducer.spec.ts | 40 ++ webclient/src/store/common/SortUtil.spec.ts | 178 ++++++ webclient/src/store/common/SortUtil.ts | 8 +- webclient/src/store/game/game.reducer.spec.ts | 25 + .../store/rooms/__mocks__/rooms-fixtures.ts | 82 +++ .../src/store/rooms/rooms.actions.spec.ts | 69 +++ .../src/store/rooms/rooms.dispatch.spec.ts | 90 +++ .../src/store/rooms/rooms.reducer.spec.ts | 285 ++++++++++ .../src/store/rooms/rooms.selectors.spec.ts | 107 ++++ .../store/server/__mocks__/server-fixtures.ts | 154 +++++ .../src/store/server/server.actions.spec.ts | 356 ++++++++++++ .../src/store/server/server.dispatch.spec.ts | 388 +++++++++++++ .../src/store/server/server.reducer.spec.ts | 526 ++++++++++++++++++ .../src/store/server/server.selectors.spec.ts | 98 ++++ 19 files changed, 2808 insertions(+), 5 deletions(-) create mode 100644 webclient/src/api/AdminService.spec.ts create mode 100644 webclient/src/api/AuthenticationService.spec.ts create mode 100644 webclient/src/api/ModeratorService.spec.ts create mode 100644 webclient/src/api/RoomsService.spec.ts create mode 100644 webclient/src/api/SessionService.spec.ts create mode 100644 webclient/src/store/actions/actionReducer.spec.ts create mode 100644 webclient/src/store/common/SortUtil.spec.ts create mode 100644 webclient/src/store/rooms/__mocks__/rooms-fixtures.ts create mode 100644 webclient/src/store/rooms/rooms.actions.spec.ts create mode 100644 webclient/src/store/rooms/rooms.dispatch.spec.ts create mode 100644 webclient/src/store/rooms/rooms.reducer.spec.ts create mode 100644 webclient/src/store/rooms/rooms.selectors.spec.ts create mode 100644 webclient/src/store/server/__mocks__/server-fixtures.ts create mode 100644 webclient/src/store/server/server.actions.spec.ts create mode 100644 webclient/src/store/server/server.dispatch.spec.ts create mode 100644 webclient/src/store/server/server.reducer.spec.ts create mode 100644 webclient/src/store/server/server.selectors.spec.ts diff --git a/webclient/src/api/AdminService.spec.ts b/webclient/src/api/AdminService.spec.ts new file mode 100644 index 000000000..be8e84c99 --- /dev/null +++ b/webclient/src/api/AdminService.spec.ts @@ -0,0 +1,48 @@ +jest.mock('websocket', () => ({ + AdminCommands: { + adjustMod: jest.fn(), + reloadConfig: jest.fn(), + shutdownServer: jest.fn(), + updateServerMessage: jest.fn(), + }, +})); + +import { AdminService } from './AdminService'; +import { AdminCommands } from 'websocket'; + +beforeEach(() => jest.clearAllMocks()); + +describe('AdminService', () => { + describe('adjustMod', () => { + it('delegates to AdminCommands.adjustMod with all arguments', () => { + AdminService.adjustMod('alice', true, false); + expect(AdminCommands.adjustMod).toHaveBeenCalledWith('alice', true, false); + }); + + it('delegates with optional arguments omitted', () => { + AdminService.adjustMod('alice'); + expect(AdminCommands.adjustMod).toHaveBeenCalledWith('alice', undefined, undefined); + }); + }); + + describe('reloadConfig', () => { + it('delegates to AdminCommands.reloadConfig', () => { + AdminService.reloadConfig(); + expect(AdminCommands.reloadConfig).toHaveBeenCalled(); + }); + }); + + describe('shutdownServer', () => { + it('delegates to AdminCommands.shutdownServer', () => { + AdminService.shutdownServer('maintenance', 10); + expect(AdminCommands.shutdownServer).toHaveBeenCalledWith('maintenance', 10); + }); + }); + + describe('updateServerMessage', () => { + it('delegates to AdminCommands.updateServerMessage', () => { + AdminService.updateServerMessage(); + expect(AdminCommands.updateServerMessage).toHaveBeenCalled(); + }); + }); +}); diff --git a/webclient/src/api/AuthenticationService.spec.ts b/webclient/src/api/AuthenticationService.spec.ts new file mode 100644 index 000000000..234ecbe47 --- /dev/null +++ b/webclient/src/api/AuthenticationService.spec.ts @@ -0,0 +1,145 @@ +jest.mock('websocket', () => ({ + SessionCommands: { + connect: jest.fn(), + disconnect: jest.fn(), + }, + webClient: { + connectionAttemptMade: false, + }, +})); + +jest.mock('websocket/services/ProtoController', () => ({ + ProtoController: { + root: { + ServerInfo_User: { + UserLevelFlag: { + IsModerator: 4, + }, + }, + }, + }, +})); + +import { AuthenticationService } from './AuthenticationService'; +import { SessionCommands, webClient } from 'websocket'; +import { StatusEnum, WebSocketConnectOptions, WebSocketConnectReason } from 'types'; + +const testOptions: WebSocketConnectOptions = { host: 'localhost', port: '4748', userName: 'user', password: 'pw' }; + +beforeEach(() => jest.clearAllMocks()); + +describe('AuthenticationService', () => { + describe('login', () => { + it('calls SessionCommands.connect with LOGIN reason', () => { + AuthenticationService.login(testOptions); + expect(SessionCommands.connect).toHaveBeenCalledWith(testOptions, WebSocketConnectReason.LOGIN); + }); + }); + + describe('testConnection', () => { + it('calls SessionCommands.connect with TEST_CONNECTION reason', () => { + AuthenticationService.testConnection(testOptions); + expect(SessionCommands.connect).toHaveBeenCalledWith(testOptions, WebSocketConnectReason.TEST_CONNECTION); + }); + }); + + describe('register', () => { + it('calls SessionCommands.connect with REGISTER reason', () => { + AuthenticationService.register(testOptions); + expect(SessionCommands.connect).toHaveBeenCalledWith(testOptions, WebSocketConnectReason.REGISTER); + }); + }); + + describe('activateAccount', () => { + it('calls SessionCommands.connect with ACTIVATE_ACCOUNT reason', () => { + AuthenticationService.activateAccount(testOptions); + expect(SessionCommands.connect).toHaveBeenCalledWith(testOptions, WebSocketConnectReason.ACTIVATE_ACCOUNT); + }); + }); + + describe('resetPasswordRequest', () => { + it('calls SessionCommands.connect with PASSWORD_RESET_REQUEST reason', () => { + AuthenticationService.resetPasswordRequest(testOptions); + expect(SessionCommands.connect).toHaveBeenCalledWith(testOptions, WebSocketConnectReason.PASSWORD_RESET_REQUEST); + }); + }); + + describe('resetPasswordChallenge', () => { + it('calls SessionCommands.connect with PASSWORD_RESET_CHALLENGE reason', () => { + AuthenticationService.resetPasswordChallenge(testOptions); + expect(SessionCommands.connect).toHaveBeenCalledWith(testOptions, WebSocketConnectReason.PASSWORD_RESET_CHALLENGE); + }); + }); + + describe('resetPassword', () => { + it('calls SessionCommands.connect with PASSWORD_RESET reason', () => { + AuthenticationService.resetPassword(testOptions); + expect(SessionCommands.connect).toHaveBeenCalledWith(testOptions, WebSocketConnectReason.PASSWORD_RESET); + }); + }); + + describe('disconnect', () => { + it('delegates to SessionCommands.disconnect', () => { + AuthenticationService.disconnect(); + expect(SessionCommands.disconnect).toHaveBeenCalled(); + }); + }); + + describe('isConnected', () => { + it('returns true when state is LOGGED_IN', () => { + expect(AuthenticationService.isConnected(StatusEnum.LOGGED_IN)).toBe(true); + }); + + it('returns false when state is DISCONNECTED', () => { + expect(AuthenticationService.isConnected(StatusEnum.DISCONNECTED)).toBe(false); + }); + + it('returns false when state is CONNECTING', () => { + expect(AuthenticationService.isConnected(StatusEnum.CONNECTING)).toBe(false); + }); + + it('returns false when state is CONNECTED', () => { + expect(AuthenticationService.isConnected(StatusEnum.CONNECTED)).toBe(false); + }); + + it('returns false when state is LOGGING_IN', () => { + expect(AuthenticationService.isConnected(StatusEnum.LOGGING_IN)).toBe(false); + }); + }); + + describe('isModerator', () => { + it('returns true when userLevel has the IsModerator bit set', () => { + expect(AuthenticationService.isModerator({ userLevel: 4 } as any)).toBe(true); + }); + + it('returns true when userLevel has IsModerator and other bits set', () => { + expect(AuthenticationService.isModerator({ userLevel: 7 } as any)).toBe(true); + }); + + it('returns false when userLevel does not have the IsModerator bit', () => { + expect(AuthenticationService.isModerator({ userLevel: 1 } as any)).toBe(false); + }); + + it('returns false for admin-only userLevel without moderator bit', () => { + expect(AuthenticationService.isModerator({ userLevel: 8 } as any)).toBe(false); + }); + }); + + describe('isAdmin', () => { + it('returns undefined (not yet implemented)', () => { + expect(AuthenticationService.isAdmin()).toBeUndefined(); + }); + }); + + describe('connectionAttemptMade', () => { + it('returns webClient.connectionAttemptMade when false', () => { + (webClient as any).connectionAttemptMade = false; + expect(AuthenticationService.connectionAttemptMade()).toBe(false); + }); + + it('returns webClient.connectionAttemptMade when true', () => { + (webClient as any).connectionAttemptMade = true; + expect(AuthenticationService.connectionAttemptMade()).toBe(true); + }); + }); +}); diff --git a/webclient/src/api/ModeratorService.spec.ts b/webclient/src/api/ModeratorService.spec.ts new file mode 100644 index 000000000..ab83bfdd2 --- /dev/null +++ b/webclient/src/api/ModeratorService.spec.ts @@ -0,0 +1,75 @@ +jest.mock('websocket', () => ({ + ModeratorCommands: { + banFromServer: jest.fn(), + getBanHistory: jest.fn(), + getWarnHistory: jest.fn(), + getWarnList: jest.fn(), + viewLogHistory: jest.fn(), + warnUser: jest.fn(), + }, +})); + +import { ModeratorService } from './ModeratorService'; +import { ModeratorCommands } from 'websocket'; +import { LogFilters } from 'types'; + +beforeEach(() => jest.clearAllMocks()); + +describe('ModeratorService', () => { + describe('banFromServer', () => { + it('delegates to ModeratorCommands.banFromServer with all arguments', () => { + ModeratorService.banFromServer(30, 'alice', '1.2.3.4', 'reason', 'visible reason', 'cid', 1); + expect(ModeratorCommands.banFromServer).toHaveBeenCalledWith( + 30, 'alice', '1.2.3.4', 'reason', 'visible reason', 'cid', 1 + ); + }); + + it('delegates with only required argument', () => { + ModeratorService.banFromServer(60); + expect(ModeratorCommands.banFromServer).toHaveBeenCalledWith( + 60, undefined, undefined, undefined, undefined, undefined, undefined + ); + }); + }); + + describe('getBanHistory', () => { + it('delegates to ModeratorCommands.getBanHistory', () => { + ModeratorService.getBanHistory('alice'); + expect(ModeratorCommands.getBanHistory).toHaveBeenCalledWith('alice'); + }); + }); + + describe('getWarnHistory', () => { + it('delegates to ModeratorCommands.getWarnHistory', () => { + ModeratorService.getWarnHistory('alice'); + expect(ModeratorCommands.getWarnHistory).toHaveBeenCalledWith('alice'); + }); + }); + + describe('getWarnList', () => { + it('delegates to ModeratorCommands.getWarnList', () => { + ModeratorService.getWarnList('mod1', 'alice', 'cid123'); + expect(ModeratorCommands.getWarnList).toHaveBeenCalledWith('mod1', 'alice', 'cid123'); + }); + }); + + describe('viewLogHistory', () => { + it('delegates to ModeratorCommands.viewLogHistory', () => { + const filters: LogFilters = { dateRange: 7, userName: 'alice' }; + ModeratorService.viewLogHistory(filters); + expect(ModeratorCommands.viewLogHistory).toHaveBeenCalledWith(filters); + }); + }); + + describe('warnUser', () => { + it('delegates to ModeratorCommands.warnUser with all arguments', () => { + ModeratorService.warnUser('alice', 'spamming', 'cid', 5); + expect(ModeratorCommands.warnUser).toHaveBeenCalledWith('alice', 'spamming', 'cid', 5); + }); + + it('delegates with only required arguments', () => { + ModeratorService.warnUser('alice', 'spamming'); + expect(ModeratorCommands.warnUser).toHaveBeenCalledWith('alice', 'spamming', undefined, undefined); + }); + }); +}); diff --git a/webclient/src/api/RoomsService.spec.ts b/webclient/src/api/RoomsService.spec.ts new file mode 100644 index 000000000..c99a5ed06 --- /dev/null +++ b/webclient/src/api/RoomsService.spec.ts @@ -0,0 +1,37 @@ +jest.mock('websocket', () => ({ + SessionCommands: { + joinRoom: jest.fn(), + }, + RoomCommands: { + leaveRoom: jest.fn(), + roomSay: jest.fn(), + }, +})); + +import { RoomsService } from './RoomsService'; +import { RoomCommands, SessionCommands } from 'websocket'; + +beforeEach(() => jest.clearAllMocks()); + +describe('RoomsService', () => { + describe('joinRoom', () => { + it('delegates to SessionCommands.joinRoom', () => { + RoomsService.joinRoom(42); + expect(SessionCommands.joinRoom).toHaveBeenCalledWith(42); + }); + }); + + describe('leaveRoom', () => { + it('delegates to RoomCommands.leaveRoom', () => { + RoomsService.leaveRoom(42); + expect(RoomCommands.leaveRoom).toHaveBeenCalledWith(42); + }); + }); + + describe('roomSay', () => { + it('delegates to RoomCommands.roomSay', () => { + RoomsService.roomSay(42, 'hello room'); + expect(RoomCommands.roomSay).toHaveBeenCalledWith(42, 'hello room'); + }); + }); +}); diff --git a/webclient/src/api/SessionService.spec.ts b/webclient/src/api/SessionService.spec.ts new file mode 100644 index 000000000..b60631448 --- /dev/null +++ b/webclient/src/api/SessionService.spec.ts @@ -0,0 +1,102 @@ +jest.mock('websocket', () => ({ + SessionCommands: { + addToBuddyList: jest.fn(), + removeFromBuddyList: jest.fn(), + addToIgnoreList: jest.fn(), + removeFromIgnoreList: jest.fn(), + accountPassword: jest.fn(), + accountEdit: jest.fn(), + accountImage: jest.fn(), + message: jest.fn(), + getUserInfo: jest.fn(), + getGamesOfUser: jest.fn(), + }, +})); + +import { SessionService } from './SessionService'; +import { SessionCommands } from 'websocket'; + +beforeEach(() => jest.clearAllMocks()); + +describe('SessionService', () => { + describe('addToBuddyList', () => { + it('delegates to SessionCommands.addToBuddyList', () => { + SessionService.addToBuddyList('alice'); + expect(SessionCommands.addToBuddyList).toHaveBeenCalledWith('alice'); + }); + }); + + describe('removeFromBuddyList', () => { + it('delegates to SessionCommands.removeFromBuddyList', () => { + SessionService.removeFromBuddyList('alice'); + expect(SessionCommands.removeFromBuddyList).toHaveBeenCalledWith('alice'); + }); + }); + + describe('addToIgnoreList', () => { + it('delegates to SessionCommands.addToIgnoreList', () => { + SessionService.addToIgnoreList('bob'); + expect(SessionCommands.addToIgnoreList).toHaveBeenCalledWith('bob'); + }); + }); + + describe('removeFromIgnoreList', () => { + it('delegates to SessionCommands.removeFromIgnoreList', () => { + SessionService.removeFromIgnoreList('bob'); + expect(SessionCommands.removeFromIgnoreList).toHaveBeenCalledWith('bob'); + }); + }); + + describe('changeAccountPassword', () => { + it('delegates to SessionCommands.accountPassword with all arguments', () => { + SessionService.changeAccountPassword('oldPw', 'newPw', 'hashedPw'); + expect(SessionCommands.accountPassword).toHaveBeenCalledWith('oldPw', 'newPw', 'hashedPw'); + }); + + it('delegates without hashedNewPassword when omitted', () => { + SessionService.changeAccountPassword('oldPw', 'newPw'); + expect(SessionCommands.accountPassword).toHaveBeenCalledWith('oldPw', 'newPw', undefined); + }); + }); + + describe('changeAccountDetails', () => { + it('delegates to SessionCommands.accountEdit with all arguments', () => { + SessionService.changeAccountDetails('pw', 'Alice', 'alice@example.com', 'US'); + expect(SessionCommands.accountEdit).toHaveBeenCalledWith('pw', 'Alice', 'alice@example.com', 'US'); + }); + + it('delegates with only required argument', () => { + SessionService.changeAccountDetails('pw'); + expect(SessionCommands.accountEdit).toHaveBeenCalledWith('pw', undefined, undefined, undefined); + }); + }); + + describe('changeAccountImage', () => { + it('delegates to SessionCommands.accountImage', () => { + const image = new Uint8Array([1, 2, 3]); + SessionService.changeAccountImage(image); + expect(SessionCommands.accountImage).toHaveBeenCalledWith(image); + }); + }); + + describe('sendDirectMessage', () => { + it('delegates to SessionCommands.message', () => { + SessionService.sendDirectMessage('alice', 'hello'); + expect(SessionCommands.message).toHaveBeenCalledWith('alice', 'hello'); + }); + }); + + describe('getUserInfo', () => { + it('delegates to SessionCommands.getUserInfo', () => { + SessionService.getUserInfo('alice'); + expect(SessionCommands.getUserInfo).toHaveBeenCalledWith('alice'); + }); + }); + + describe('getUserGames', () => { + it('delegates to SessionCommands.getGamesOfUser', () => { + SessionService.getUserGames('alice'); + expect(SessionCommands.getGamesOfUser).toHaveBeenCalledWith('alice'); + }); + }); +}); diff --git a/webclient/src/store/actions/actionReducer.spec.ts b/webclient/src/store/actions/actionReducer.spec.ts new file mode 100644 index 000000000..351995dc5 --- /dev/null +++ b/webclient/src/store/actions/actionReducer.spec.ts @@ -0,0 +1,40 @@ +import { actionReducer } from './actionReducer'; + +describe('actionReducer', () => { + it('spreads the init action onto state and starts count at 1', () => { + const result = actionReducer(undefined, { type: '@@INIT' }); + // actionReducer always spreads the action, so type reflects the dispatched action + expect(result.type).toBe('@@INIT'); + expect(result.payload).toBeNull(); + expect(result.meta).toBeNull(); + expect(result.error).toBe(false); + expect(result.count).toBe(1); + }); + + it('spreads action onto state and increments count', () => { + const result = actionReducer(undefined, { type: 'MY_ACTION', payload: { id: 1 } }); + expect(result.type).toBe('MY_ACTION'); + expect(result.payload).toEqual({ id: 1 }); + expect(result.count).toBe(1); + }); + + it('increments count on each dispatch', () => { + const state1 = actionReducer(undefined, { type: 'A' }); + const state2 = actionReducer(state1, { type: 'B' }); + const state3 = actionReducer(state2, { type: 'C' }); + expect(state3.count).toBe(3); + }); + + it('preserves existing state fields not overridden by action', () => { + const initial = actionReducer(undefined, { type: 'FIRST', payload: 'original' }); + const result = actionReducer(initial, { type: 'SECOND' }); + expect(result.type).toBe('SECOND'); + expect(result.count).toBe(2); + }); + + it('spreads action.meta and action.error from action onto state', () => { + const result = actionReducer(undefined, { type: 'ERR', meta: { source: 'api' }, error: true }); + expect(result.meta).toEqual({ source: 'api' }); + expect(result.error).toBe(true); + }); +}); diff --git a/webclient/src/store/common/SortUtil.spec.ts b/webclient/src/store/common/SortUtil.spec.ts new file mode 100644 index 000000000..6581b640c --- /dev/null +++ b/webclient/src/store/common/SortUtil.spec.ts @@ -0,0 +1,178 @@ +import { SortDirection } from 'types'; +import SortUtil from './SortUtil'; + +// ── sortByField ─────────────────────────────────────────────────────────────── + +describe('sortByField', () => { + it('sorts string field ASC alphabetically', () => { + const arr = [{ name: 'Zane' }, { name: 'Alice' }, { name: 'Bob' }]; + SortUtil.sortByField(arr, { field: 'name', order: SortDirection.ASC }); + expect(arr.map(x => x.name)).toEqual(['Alice', 'Bob', 'Zane']); + }); + + it('sorts string field DESC reverse-alphabetically', () => { + const arr = [{ name: 'Alice' }, { name: 'Zane' }, { name: 'Bob' }]; + SortUtil.sortByField(arr, { field: 'name', order: SortDirection.DESC }); + expect(arr.map(x => x.name)).toEqual(['Zane', 'Bob', 'Alice']); + }); + + it('sorts number field ASC', () => { + const arr = [{ score: 30 }, { score: 10 }, { score: 20 }]; + SortUtil.sortByField(arr, { field: 'score', order: SortDirection.ASC }); + expect(arr.map(x => x.score)).toEqual([10, 20, 30]); + }); + + it('sorts number field DESC', () => { + const arr = [{ score: 10 }, { score: 30 }, { score: 20 }]; + SortUtil.sortByField(arr, { field: 'score', order: SortDirection.DESC }); + expect(arr.map(x => x.score)).toEqual([30, 20, 10]); + }); + + it('no-ops on empty array without error', () => { + expect(() => SortUtil.sortByField([], { field: 'name', order: SortDirection.ASC })).not.toThrow(); + }); + + it('sorts with nested dot-notation field', () => { + const arr = [{ meta: { rank: 3 } }, { meta: { rank: 1 } }, { meta: { rank: 2 } }]; + SortUtil.sortByField(arr, { field: 'meta.rank', order: SortDirection.ASC }); + expect(arr.map(x => x.meta.rank)).toEqual([1, 2, 3]); + }); + + it('throws when field resolves to a non-string, non-number value', () => { + const arr = [{ data: {} }, { data: {} }]; + expect(() => SortUtil.sortByField(arr, { field: 'data', order: SortDirection.ASC })).toThrow( + 'SortField must resolve to either a string or number' + ); + }); + + it('sorts empty-string values to the bottom when sorting ASC', () => { + const arr = [{ name: '' }, { name: 'Alice' }, { name: '' }]; + SortUtil.sortByField(arr, { field: 'name', order: SortDirection.ASC }); + expect(arr[0].name).toBe('Alice'); + expect(arr[1].name).toBe(''); + expect(arr[2].name).toBe(''); + }); +}); + +// ── sortByFields ────────────────────────────────────────────────────────────── + +describe('sortByFields', () => { + it('sorts by the first key when all items have distinct first-key values', () => { + const arr = [ + { group: 'C', name: 'Zane' }, + { group: 'A', name: 'Bob' }, + { group: 'B', name: 'Alice' }, + ]; + SortUtil.sortByFields(arr, [ + { field: 'group', order: SortDirection.ASC }, + { field: 'name', order: SortDirection.ASC }, + ]); + expect(arr.map(x => x.group)).toEqual(['A', 'B', 'C']); + }); + + it('breaks ties on primary key using secondary key', () => { + const arr = [ + { group: 'A', name: 'Zane' }, + { group: 'A', name: 'Alice' }, + { group: 'B', name: 'Bob' }, + ]; + SortUtil.sortByFields(arr, [ + { field: 'group', order: SortDirection.ASC }, + { field: 'name', order: SortDirection.ASC }, + ]); + expect(arr[0]).toEqual({ group: 'A', name: 'Alice' }); + expect(arr[1]).toEqual({ group: 'A', name: 'Zane' }); + expect(arr[2]).toEqual({ group: 'B', name: 'Bob' }); + }); + + it('no-ops on empty array', () => { + expect(() => + SortUtil.sortByFields([], [{ field: 'name', order: SortDirection.ASC }]) + ).not.toThrow(); + }); + + it('sorts by number field', () => { + const arr = [{ score: 3 }, { score: 1 }, { score: 2 }]; + SortUtil.sortByFields(arr, [{ field: 'score', order: SortDirection.ASC }]); + expect(arr.map(x => x.score)).toEqual([1, 2, 3]); + }); + + it('returns 0 when all items tie on every sort key', () => { + const arr = [{ score: 5 }, { score: 5 }]; + expect(() => + SortUtil.sortByFields(arr, [{ field: 'score', order: SortDirection.ASC }]) + ).not.toThrow(); + expect(arr).toHaveLength(2); + }); + + it('throws when field resolves to a non-string, non-number value', () => { + const arr = [{ data: {} }, { data: {} }]; + expect(() => + SortUtil.sortByFields(arr, [{ field: 'data', order: SortDirection.ASC }]) + ).toThrow('SortField must resolve to either a string or number'); + }); +}); + +// ── sortUsersByField ────────────────────────────────────────────────────────── + +describe('sortUsersByField', () => { + it('sorts by userLevel DESC first, then name ASC', () => { + const users = [ + { name: 'Alice', userLevel: 1, accountageSecs: 0, privlevel: 0 }, + { name: 'Bob', userLevel: 8, accountageSecs: 0, privlevel: 0 }, + { name: 'Carol', userLevel: 1, accountageSecs: 0, privlevel: 0 }, + ]; + SortUtil.sortUsersByField(users as any, { field: 'name', order: SortDirection.ASC }); + expect(users[0].name).toBe('Bob'); + expect(users[1].name).toBe('Alice'); + expect(users[2].name).toBe('Carol'); + }); + + it('no-ops on empty array', () => { + expect(() => + SortUtil.sortUsersByField([], { field: 'name', order: SortDirection.ASC }) + ).not.toThrow(); + }); + + it('returns 0 (stable) when two users tie on both userLevel and name', () => { + const users = [ + { name: 'Alice', userLevel: 1, accountageSecs: 0, privlevel: 0 }, + { name: 'Alice', userLevel: 1, accountageSecs: 0, privlevel: 0 }, + ]; + expect(() => + SortUtil.sortUsersByField(users as any, { field: 'name', order: SortDirection.ASC }) + ).not.toThrow(); + expect(users).toHaveLength(2); + }); +}); + +// ── toggleSortBy ────────────────────────────────────────────────────────────── + +describe('toggleSortBy', () => { + it('same field + ASC → returns DESC', () => { + const result = SortUtil.toggleSortBy('name', { field: 'name', order: SortDirection.ASC }); + expect(result).toEqual({ field: 'name', order: SortDirection.DESC }); + }); + + it('same field + DESC → returns ASC', () => { + const result = SortUtil.toggleSortBy('name', { field: 'name', order: SortDirection.DESC }); + expect(result).toEqual({ field: 'name', order: SortDirection.ASC }); + }); + + it('different field → returns ASC regardless of current order', () => { + const result = SortUtil.toggleSortBy('score', { field: 'name', order: SortDirection.DESC }); + expect(result).toEqual({ field: 'score', order: SortDirection.ASC }); + }); +}); + +// ── resolveFieldChain with numeric index ───────────────────────────────────── + +describe('resolveFieldChain via sortByField (numeric index)', () => { + it('resolves numeric index in dot-notation chain', () => { + const arr = [{ items: ['c', 'b', 'a'] }, { items: ['z', 'y', 'x'] }]; + // Sort by items.0 which is the first element of the items array + SortUtil.sortByField(arr, { field: 'items.0', order: SortDirection.ASC }); + expect(arr[0].items[0]).toBe('c'); + expect(arr[1].items[0]).toBe('z'); + }); +}); diff --git a/webclient/src/store/common/SortUtil.ts b/webclient/src/store/common/SortUtil.ts index 56910259d..f6aca821d 100644 --- a/webclient/src/store/common/SortUtil.ts +++ b/webclient/src/store/common/SortUtil.ts @@ -35,17 +35,15 @@ export default class SortUtil { if (result) { return result; } - } - - if (fieldType === 'number') { + } else if (fieldType === 'number') { const result = SortUtil.numberComparator(a, b, sortBy); if (result) { return result; } + } else { + throw new Error('SortField must resolve to either a string or number'); } - - throw new Error('SortField must resolve to either a string or number'); } return 0; diff --git a/webclient/src/store/game/game.reducer.spec.ts b/webclient/src/store/game/game.reducer.spec.ts index 92e8477cf..38ed89ad1 100644 --- a/webclient/src/store/game/game.reducer.spec.ts +++ b/webclient/src/store/game/game.reducer.spec.ts @@ -383,6 +383,31 @@ describe('2C: CARD_MOVED', () => { expect(moved.name).toBe('New Name'); expect(moved.providerId).toBe('new-prov'); }); + + it('CARD_MOVED → returns newState (card removed from source) when targetZone does not exist on player', () => { + const { state } = stateWithCard(); + const result = gamesReducer(state, { + type: Types.CARD_MOVED, + gameId: 1, + playerId: 1, + data: { + cardId: 10, + cardName: '', + startPlayerId: 1, + startZone: 'hand', + position: -1, + targetPlayerId: 1, + targetZone: 'nonexistent', + x: 0, + y: 0, + newCardId: -1, + faceDown: false, + newCardProviderId: '', + }, + }); + expect(result.games[1].players[1].zones['hand'].cards).toHaveLength(0); + expect(result.games[1].players[1].zones['nonexistent']).toBeUndefined(); + }); }); // ── 2D: Card mutations ──────────────────────────────────────────────────────── diff --git a/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts b/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts new file mode 100644 index 000000000..771fda745 --- /dev/null +++ b/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts @@ -0,0 +1,82 @@ +import { + Game, + GameSortField, + Room, + SortDirection, + User, + UserPrivLevel, + UserSortField, +} from 'types'; +import { Message, RoomsState } from '../rooms.interfaces'; + +export function makeUser(overrides: Partial = {}): User { + return { + name: 'TestUser', + accountageSecs: 0, + privlevel: UserPrivLevel.NONE, + userLevel: 0, + ...overrides, + }; +} + +export function makeRoom(overrides: Partial = {}): Room { + return { + roomId: 1, + name: 'Test Room', + description: '', + gameCount: 0, + gameList: [], + gametypeList: [], + gametypeMap: {}, + autoJoin: false, + permissionlevel: 0 as any, + playerCount: 0, + privilegelevel: 0 as any, + userList: [], + order: 0, + ...overrides, + }; +} + +export function makeGame(overrides: Partial = {}): Game & { startTime: number } { + return { + gameId: 1, + roomId: 1, + description: 'Test Game', + gameType: '', + gameTypes: [], + started: false, + startTime: 0, + ...overrides, + }; +} + +export function makeMessage(overrides: Partial = {}): Message { + return { + message: 'hello', + messageType: 0, + timeReceived: 0, + ...overrides, + }; +} + +export function makeRoomsState(overrides: Partial = {}): RoomsState { + return { + rooms: { + 1: makeRoom({ roomId: 1 }), + }, + games: {}, + joinedRoomIds: {}, + joinedGameIds: {}, + messages: {}, + sortGamesBy: { + field: GameSortField.START_TIME, + order: SortDirection.DESC, + }, + sortUsersBy: { + field: UserSortField.NAME, + order: SortDirection.ASC, + }, + ...overrides, + }; +} diff --git a/webclient/src/store/rooms/rooms.actions.spec.ts b/webclient/src/store/rooms/rooms.actions.spec.ts new file mode 100644 index 000000000..542d626b9 --- /dev/null +++ b/webclient/src/store/rooms/rooms.actions.spec.ts @@ -0,0 +1,69 @@ +import { Actions } from './rooms.actions'; +import { Types } from './rooms.types'; +import { makeGame, makeMessage, makeRoom, makeUser } from './__mocks__/rooms-fixtures'; +import { GameSortField, SortDirection } from 'types'; + +describe('Actions', () => { + it('clearStore', () => { + expect(Actions.clearStore()).toEqual({ type: Types.CLEAR_STORE }); + }); + + it('updateRooms', () => { + const rooms = [makeRoom()]; + expect(Actions.updateRooms(rooms)).toEqual({ type: Types.UPDATE_ROOMS, rooms }); + }); + + it('joinRoom', () => { + const roomInfo = makeRoom({ roomId: 2 }); + expect(Actions.joinRoom(roomInfo)).toEqual({ type: Types.JOIN_ROOM, roomInfo }); + }); + + it('leaveRoom', () => { + expect(Actions.leaveRoom(3)).toEqual({ type: Types.LEAVE_ROOM, roomId: 3 }); + }); + + it('addMessage', () => { + const message = makeMessage(); + expect(Actions.addMessage(1, message)).toEqual({ type: Types.ADD_MESSAGE, roomId: 1, message }); + }); + + it('updateGames', () => { + const games = [makeGame()]; + expect(Actions.updateGames(1, games)).toEqual({ type: Types.UPDATE_GAMES, roomId: 1, games }); + }); + + it('userJoined', () => { + const user = makeUser(); + expect(Actions.userJoined(1, user)).toEqual({ type: Types.USER_JOINED, roomId: 1, user }); + }); + + it('userLeft', () => { + expect(Actions.userLeft(1, 'Alice')).toEqual({ type: Types.USER_LEFT, roomId: 1, name: 'Alice' }); + }); + + it('sortGames', () => { + expect(Actions.sortGames(1, GameSortField.START_TIME, SortDirection.ASC)).toEqual({ + type: Types.SORT_GAMES, + roomId: 1, + field: GameSortField.START_TIME, + order: SortDirection.ASC, + }); + }); + + it('removeMessages', () => { + expect(Actions.removeMessages(1, 'Alice', 3)).toEqual({ + type: Types.REMOVE_MESSAGES, + roomId: 1, + name: 'Alice', + amount: 3, + }); + }); + + it('gameCreated', () => { + expect(Actions.gameCreated(2)).toEqual({ type: Types.GAME_CREATED, roomId: 2 }); + }); + + it('joinedGame', () => { + expect(Actions.joinedGame(1, 5)).toEqual({ type: Types.JOINED_GAME, roomId: 1, gameId: 5 }); + }); +}); diff --git a/webclient/src/store/rooms/rooms.dispatch.spec.ts b/webclient/src/store/rooms/rooms.dispatch.spec.ts new file mode 100644 index 000000000..9da0f9d5c --- /dev/null +++ b/webclient/src/store/rooms/rooms.dispatch.spec.ts @@ -0,0 +1,90 @@ +jest.mock('store/store', () => ({ store: { dispatch: jest.fn() } })); +jest.mock('redux-form', () => ({ + reset: jest.fn((form) => ({ type: '@@redux-form/RESET', meta: { form } })), +})); + +import { store } from 'store/store'; +import { reset } from 'redux-form'; +import { Actions } from './rooms.actions'; +import { Dispatch } from './rooms.dispatch'; +import { makeGame, makeMessage, makeRoom, makeUser } from './__mocks__/rooms-fixtures'; +import { GameSortField, SortDirection } from 'types'; + +beforeEach(() => jest.clearAllMocks()); + +describe('Dispatch', () => { + it('clearStore dispatches Actions.clearStore()', () => { + Dispatch.clearStore(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.clearStore()); + }); + + it('updateRooms dispatches Actions.updateRooms()', () => { + const rooms = [makeRoom()]; + Dispatch.updateRooms(rooms); + expect(store.dispatch).toHaveBeenCalledWith(Actions.updateRooms(rooms)); + }); + + it('joinRoom dispatches Actions.joinRoom()', () => { + const roomInfo = makeRoom({ roomId: 2 }); + Dispatch.joinRoom(roomInfo); + expect(store.dispatch).toHaveBeenCalledWith(Actions.joinRoom(roomInfo)); + }); + + it('leaveRoom dispatches Actions.leaveRoom()', () => { + Dispatch.leaveRoom(3); + expect(store.dispatch).toHaveBeenCalledWith(Actions.leaveRoom(3)); + }); + + it('addMessage with message.name falsy → dispatches only Actions.addMessage()', () => { + const message = { ...makeMessage(), name: undefined }; + Dispatch.addMessage(1, message); + expect(store.dispatch).toHaveBeenCalledTimes(1); + expect(store.dispatch).toHaveBeenCalledWith(Actions.addMessage(1, message)); + }); + + it('addMessage with message.name truthy → dispatches reset("sayMessage") then Actions.addMessage()', () => { + const message = { ...makeMessage(), name: 'Alice' }; + Dispatch.addMessage(1, message); + expect(store.dispatch).toHaveBeenNthCalledWith(1, (reset as jest.Mock)('sayMessage')); + expect(store.dispatch).toHaveBeenNthCalledWith(2, Actions.addMessage(1, message)); + }); + + it('updateGames dispatches Actions.updateGames()', () => { + const games = [makeGame()]; + Dispatch.updateGames(1, games); + expect(store.dispatch).toHaveBeenCalledWith(Actions.updateGames(1, games)); + }); + + it('userJoined dispatches Actions.userJoined()', () => { + const user = makeUser(); + Dispatch.userJoined(1, user); + expect(store.dispatch).toHaveBeenCalledWith(Actions.userJoined(1, user)); + }); + + it('userLeft dispatches Actions.userLeft()', () => { + Dispatch.userLeft(1, 'Alice'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.userLeft(1, 'Alice')); + }); + + it('sortGames dispatches Actions.sortGames()', () => { + Dispatch.sortGames(1, GameSortField.START_TIME, SortDirection.ASC); + expect(store.dispatch).toHaveBeenCalledWith( + Actions.sortGames(1, GameSortField.START_TIME, SortDirection.ASC) + ); + }); + + it('removeMessages dispatches Actions.removeMessages()', () => { + Dispatch.removeMessages(1, 'Alice', 5); + expect(store.dispatch).toHaveBeenCalledWith(Actions.removeMessages(1, 'Alice', 5)); + }); + + it('gameCreated dispatches Actions.gameCreated()', () => { + Dispatch.gameCreated(2); + expect(store.dispatch).toHaveBeenCalledWith(Actions.gameCreated(2)); + }); + + it('joinedGame dispatches Actions.joinedGame()', () => { + Dispatch.joinedGame(1, 5); + expect(store.dispatch).toHaveBeenCalledWith(Actions.joinedGame(1, 5)); + }); +}); diff --git a/webclient/src/store/rooms/rooms.reducer.spec.ts b/webclient/src/store/rooms/rooms.reducer.spec.ts new file mode 100644 index 000000000..2555a12f2 --- /dev/null +++ b/webclient/src/store/rooms/rooms.reducer.spec.ts @@ -0,0 +1,285 @@ +import { GameSortField, SortDirection } from 'types'; +import { roomsReducer } from './rooms.reducer'; +import { Types, MAX_ROOM_MESSAGES } from './rooms.types'; +import { makeGame, makeMessage, makeRoom, makeRoomsState, makeUser } from './__mocks__/rooms-fixtures'; + +// ── Initialisation ─────────────────────────────────────────────────────────── + +describe('Initialisation', () => { + it('returns initialState when called with undefined state', () => { + const result = roomsReducer(undefined, { type: '@@INIT' }); + expect(result.rooms).toEqual({}); + expect(result.joinedRoomIds).toEqual({}); + }); + + it('CLEAR_STORE → resets to initialState', () => { + const state = makeRoomsState({ joinedRoomIds: { 1: true } }); + const result = roomsReducer(state, { type: Types.CLEAR_STORE }); + expect(result.joinedRoomIds).toEqual({}); + expect(result.rooms).toEqual({}); + }); + + it('default → returns state unchanged for unknown action', () => { + const state = makeRoomsState(); + const result = roomsReducer(state, { type: '@@UNKNOWN' }); + expect(result).toBe(state); + }); +}); + +// ── UPDATE_ROOMS ────────────────────────────────────────────────────────────── + +describe('UPDATE_ROOMS', () => { + it('merges rooms and strips gameList, gametypeList, userList from update', () => { + const state = makeRoomsState({ rooms: {} }); + const room = { ...makeRoom({ roomId: 1 }), gameList: [makeGame()], userList: [makeUser()], gametypeList: ['standard'] }; + const result = roomsReducer(state, { type: Types.UPDATE_ROOMS, rooms: [room] }); + expect(result.rooms[1]).toBeDefined(); + expect(result.rooms[1].gameList).toBeUndefined(); + expect(result.rooms[1].userList).toBeUndefined(); + expect(result.rooms[1].gametypeList).toBeUndefined(); + }); + + it('sets numeric order from array index', () => { + const state = makeRoomsState({ rooms: {} }); + const rooms = [makeRoom({ roomId: 1 }), makeRoom({ roomId: 2 })]; + const result = roomsReducer(state, { type: Types.UPDATE_ROOMS, rooms }); + expect(result.rooms[1].order).toBe(0); + expect(result.rooms[2].order).toBe(1); + }); + + it('merges into existing room entry (preserves existing fields)', () => { + const existingRoom = makeRoom({ roomId: 1, name: 'Old Name', gameList: [makeGame()] }); + const state = makeRoomsState({ rooms: { 1: existingRoom } }); + const update = makeRoom({ roomId: 1, name: 'New Name' }); + const result = roomsReducer(state, { type: Types.UPDATE_ROOMS, rooms: [update] }); + expect(result.rooms[1].name).toBe('New Name'); + expect(result.rooms[1].gameList).toEqual([makeGame()]); + }); + + it('creates new room entry for unknown roomId', () => { + const state = makeRoomsState({ rooms: {} }); + const room = makeRoom({ roomId: 99, name: 'New Room' }); + const result = roomsReducer(state, { type: Types.UPDATE_ROOMS, rooms: [room] }); + expect(result.rooms[99]).toBeDefined(); + expect(result.rooms[99].name).toBe('New Room'); + }); +}); + +// ── JOIN_ROOM ────────────────────────────────────────────────────────────────── + +describe('JOIN_ROOM', () => { + it('copies gameList and userList, sorts both, sets joinedRoomIds', () => { + const state = makeRoomsState({ rooms: {}, joinedRoomIds: {} }); + const roomInfo = makeRoom({ + roomId: 2, + gameList: [makeGame({ gameId: 1 })], + userList: [makeUser({ name: 'Zane' }), makeUser({ name: 'Alice' })], + }); + const result = roomsReducer(state, { type: Types.JOIN_ROOM, roomInfo }); + expect(result.joinedRoomIds[2]).toBe(true); + expect(result.rooms[2].userList[0].name).toBe('Alice'); + expect(result.rooms[2]).toMatchObject({ roomId: 2 }); + }); +}); + +// ── LEAVE_ROOM ──────────────────────────────────────────────────────────────── + +describe('LEAVE_ROOM', () => { + it('removes joinedRoomIds entry and messages for roomId', () => { + const state = makeRoomsState({ + joinedRoomIds: { 1: true }, + messages: { 1: [makeMessage()] }, + }); + const result = roomsReducer(state, { type: Types.LEAVE_ROOM, roomId: 1 }); + expect(result.joinedRoomIds[1]).toBeUndefined(); + expect(result.messages[1]).toBeUndefined(); + }); +}); + +// ── ADD_MESSAGE ─────────────────────────────────────────────────────────────── + +describe('ADD_MESSAGE', () => { + it('appends message with timeReceived set', () => { + const state = makeRoomsState({ messages: { 1: [] } }); + const message = makeMessage({ message: 'hello', timeReceived: 0 }); + const result = roomsReducer(state, { type: Types.ADD_MESSAGE, roomId: 1, message }); + expect(result.messages[1]).toHaveLength(1); + expect(result.messages[1][0].timeReceived).toBeGreaterThan(0); + }); + + it('creates message list for roomId when none exists', () => { + const state = makeRoomsState({ messages: {} }); + const message = makeMessage(); + const result = roomsReducer(state, { type: Types.ADD_MESSAGE, roomId: 5, message }); + expect(result.messages[5]).toHaveLength(1); + }); + + it(`shifts oldest message when list is at MAX_ROOM_MESSAGES (${MAX_ROOM_MESSAGES})`, () => { + const firstMsg = makeMessage({ message: 'first' }); + const messages = Array.from({ length: MAX_ROOM_MESSAGES }, (_, i) => + i === 0 ? firstMsg : makeMessage({ message: `msg-${i}` }) + ); + const state = makeRoomsState({ messages: { 1: messages } }); + const newMsg = makeMessage({ message: 'new' }); + const result = roomsReducer(state, { type: Types.ADD_MESSAGE, roomId: 1, message: newMsg }); + expect(result.messages[1]).toHaveLength(MAX_ROOM_MESSAGES); + expect(result.messages[1][0].message).not.toBe('first'); + expect(result.messages[1][MAX_ROOM_MESSAGES - 1].message).toBe('new'); + }); +}); + +// ── UPDATE_GAMES ────────────────────────────────────────────────────────────── + +describe('UPDATE_GAMES', () => { + it('removes closed games from gameList', () => { + const room = makeRoom({ roomId: 1, gameList: [makeGame({ gameId: 1 })] }); + const state = makeRoomsState({ rooms: { 1: room } }); + const result = roomsReducer(state, { + type: Types.UPDATE_GAMES, + roomId: 1, + games: [{ gameId: 1, closed: true }], + }); + expect(result.rooms[1].gameList).toHaveLength(0); + }); + + it('merges update into existing game', () => { + const game = makeGame({ gameId: 1, description: 'old' }); + const room = makeRoom({ roomId: 1, gameList: [game] }); + const state = makeRoomsState({ rooms: { 1: room } }); + const result = roomsReducer(state, { + type: Types.UPDATE_GAMES, + roomId: 1, + games: [{ gameId: 1, description: 'new' }], + }); + expect(result.rooms[1].gameList[0].description).toBe('new'); + }); + + it('appends new game to list and sorts', () => { + const room = makeRoom({ roomId: 1, gameList: [] }); + const state = makeRoomsState({ rooms: { 1: room } }); + const newGame = makeGame({ gameId: 99, description: 'extra' }); + const result = roomsReducer(state, { type: Types.UPDATE_GAMES, roomId: 1, games: [newGame] }); + expect(result.rooms[1].gameList).toHaveLength(1); + expect(result.rooms[1].gameList[0].gameId).toBe(99); + }); + + it('preserves existing games not included in the update', () => { + const game1 = makeGame({ gameId: 1, description: 'untouched' }); + const game2 = makeGame({ gameId: 2, description: 'old' }); + const room = makeRoom({ roomId: 1, gameList: [game1, game2] }); + const state = makeRoomsState({ rooms: { 1: room } }); + const result = roomsReducer(state, { + type: Types.UPDATE_GAMES, + roomId: 1, + games: [{ gameId: 2, description: 'new' }], + }); + expect(result.rooms[1].gameList.find(g => g.gameId === 1).description).toBe('untouched'); + expect(result.rooms[1].gameList.find(g => g.gameId === 2).description).toBe('new'); + }); + + it('returns { ...state } (not identity) when roomId is unknown', () => { + const state = makeRoomsState({ rooms: {} }); + const result = roomsReducer(state, { type: Types.UPDATE_GAMES, roomId: 999, games: [] }); + expect(result).not.toBe(state); + expect(result.rooms).toEqual(state.rooms); + }); +}); + +// ── USER_JOINED / USER_LEFT ─────────────────────────────────────────────────── + +describe('USER_JOINED', () => { + it('appends user to userList and sorts by name ASC', () => { + const room = makeRoom({ roomId: 1, userList: [makeUser({ name: 'Zane' })] }); + const state = makeRoomsState({ rooms: { 1: room } }); + const result = roomsReducer(state, { type: Types.USER_JOINED, roomId: 1, user: makeUser({ name: 'Alice' }) }); + expect(result.rooms[1].userList[0].name).toBe('Alice'); + expect(result.rooms[1].userList).toHaveLength(2); + }); +}); + +describe('USER_LEFT', () => { + it('removes user by name from userList', () => { + const room = makeRoom({ roomId: 1, userList: [makeUser({ name: 'Alice' }), makeUser({ name: 'Bob' })] }); + const state = makeRoomsState({ rooms: { 1: room } }); + const result = roomsReducer(state, { type: Types.USER_LEFT, roomId: 1, name: 'Alice' }); + expect(result.rooms[1].userList).toHaveLength(1); + expect(result.rooms[1].userList[0].name).toBe('Bob'); + }); +}); + +// ── SORT_GAMES ──────────────────────────────────────────────────────────────── + +describe('SORT_GAMES', () => { + it('resorts gameList and updates sortGamesBy on state', () => { + const games = [makeGame({ gameId: 2 }), makeGame({ gameId: 1 })]; + const room = makeRoom({ roomId: 1, gameList: games }); + const state = makeRoomsState({ rooms: { 1: room } }); + const result = roomsReducer(state, { + type: Types.SORT_GAMES, + roomId: 1, + field: GameSortField.START_TIME, + order: SortDirection.ASC, + }); + expect(result.sortGamesBy).toEqual({ field: GameSortField.START_TIME, order: SortDirection.ASC }); + }); +}); + +// ── REMOVE_MESSAGES ─────────────────────────────────────────────────────────── + +describe('REMOVE_MESSAGES', () => { + it('removes messages starting with "name:" up to amount, in reverse scan order', () => { + const msgs = [ + makeMessage({ message: 'Alice: hello' }), + makeMessage({ message: 'Bob: hi' }), + makeMessage({ message: 'Alice: world' }), + ]; + const state = makeRoomsState({ messages: { 1: msgs } }); + const result = roomsReducer(state, { type: Types.REMOVE_MESSAGES, roomId: 1, name: 'Alice', amount: 1 }); + // reverse scan: removes LAST 'Alice:' message first, stops after 1 + const remaining = result.messages[1]; + expect(remaining).toHaveLength(2); + const aliceMessages = remaining.filter(m => m.message.startsWith('Alice:')); + expect(aliceMessages).toHaveLength(1); + expect(aliceMessages[0].message).toBe('Alice: hello'); + }); + + it('removes up to amount matching messages', () => { + const msgs = [ + makeMessage({ message: 'Alice: one' }), + makeMessage({ message: 'Alice: two' }), + makeMessage({ message: 'Alice: three' }), + ]; + const state = makeRoomsState({ messages: { 1: msgs } }); + const result = roomsReducer(state, { type: Types.REMOVE_MESSAGES, roomId: 1, name: 'Alice', amount: 2 }); + const remaining = result.messages[1]; + expect(remaining).toHaveLength(1); + }); + + it('stops removing once amount is reached', () => { + const msgs = [ + makeMessage({ message: 'Alice: a' }), + makeMessage({ message: 'Alice: b' }), + makeMessage({ message: 'Alice: c' }), + ]; + const state = makeRoomsState({ messages: { 1: msgs } }); + const result = roomsReducer(state, { type: Types.REMOVE_MESSAGES, roomId: 1, name: 'Alice', amount: 1 }); + expect(result.messages[1]).toHaveLength(2); + }); +}); + +// ── JOINED_GAME ─────────────────────────────────────────────────────────────── + +describe('JOINED_GAME', () => { + it('sets joinedGameIds[roomId][gameId] = true', () => { + const state = makeRoomsState({ joinedGameIds: {} }); + const result = roomsReducer(state, { type: Types.JOINED_GAME, roomId: 1, gameId: 5 }); + expect(result.joinedGameIds[1][5]).toBe(true); + }); + + it('preserves other roomId entries in joinedGameIds', () => { + const state = makeRoomsState({ joinedGameIds: { 2: { 9: true } } }); + const result = roomsReducer(state, { type: Types.JOINED_GAME, roomId: 1, gameId: 5 }); + expect(result.joinedGameIds[2][9]).toBe(true); + expect(result.joinedGameIds[1][5]).toBe(true); + }); +}); diff --git a/webclient/src/store/rooms/rooms.selectors.spec.ts b/webclient/src/store/rooms/rooms.selectors.spec.ts new file mode 100644 index 000000000..f8c6ef4cc --- /dev/null +++ b/webclient/src/store/rooms/rooms.selectors.spec.ts @@ -0,0 +1,107 @@ +import { Selectors } from './rooms.selectors'; +import { RoomsState } from './rooms.interfaces'; +import { makeGame, makeMessage, makeRoom, makeRoomsState, makeUser } from './__mocks__/rooms-fixtures'; + +function rootState(rooms: RoomsState) { + return { rooms }; +} + +describe('Selectors', () => { + it('getRooms → returns rooms map', () => { + const state = makeRoomsState(); + expect(Selectors.getRooms(rootState(state))).toBe(state.rooms); + }); + + it('getGames → returns games map', () => { + const state = makeRoomsState({ games: { 1: { 1: makeGame() } } }); + expect(Selectors.getGames(rootState(state))).toBe(state.games); + }); + + it('getRoom → returns room matching roomId', () => { + const room = makeRoom({ roomId: 1 }); + const state = makeRoomsState({ rooms: { 1: room } }); + expect(Selectors.getRoom(rootState(state), 1)).toBe(room); + }); + + it('getRoom → returns undefined for unknown roomId', () => { + const state = makeRoomsState({ rooms: {} }); + expect(Selectors.getRoom(rootState(state), 999)).toBeUndefined(); + }); + + it('getJoinedRoomIds → returns joinedRoomIds', () => { + const joinedRoomIds = { 1: true }; + const state = makeRoomsState({ joinedRoomIds }); + expect(Selectors.getJoinedRoomIds(rootState(state))).toBe(joinedRoomIds); + }); + + it('getJoinedGameIds → returns joinedGameIds', () => { + const joinedGameIds = { 1: { 5: true } }; + const state = makeRoomsState({ joinedGameIds }); + expect(Selectors.getJoinedGameIds(rootState(state))).toBe(joinedGameIds); + }); + + it('getMessages → returns messages map', () => { + const messages = { 1: [makeMessage()] }; + const state = makeRoomsState({ messages }); + expect(Selectors.getMessages(rootState(state))).toBe(messages); + }); + + it('getSortGamesBy → returns sortGamesBy', () => { + const state = makeRoomsState(); + expect(Selectors.getSortGamesBy(rootState(state))).toBe(state.sortGamesBy); + }); + + it('getSortUsersBy → returns sortUsersBy', () => { + const state = makeRoomsState(); + expect(Selectors.getSortUsersBy(rootState(state))).toBe(state.sortUsersBy); + }); + + it('getJoinedRooms → returns only rooms whose roomId is in joinedRoomIds', () => { + const room1 = makeRoom({ roomId: 1 }); + const room2 = makeRoom({ roomId: 2 }); + const state = makeRoomsState({ + rooms: { 1: room1, 2: room2 }, + joinedRoomIds: { 1: true }, + }); + const result = Selectors.getJoinedRooms(rootState(state)); + expect(result).toHaveLength(1); + expect(result[0]).toBe(room1); + }); + + it('getJoinedRooms → returns empty array when none joined', () => { + const state = makeRoomsState({ rooms: { 1: makeRoom({ roomId: 1 }) }, joinedRoomIds: {} }); + expect(Selectors.getJoinedRooms(rootState(state))).toHaveLength(0); + }); + + it('getJoinedGames → returns only games whose gameId is in joinedGameIds for that room', () => { + const game1 = makeGame({ gameId: 1 }); + const game2 = makeGame({ gameId: 2 }); + const state = makeRoomsState({ + games: { 1: { 1: game1, 2: game2 } }, + joinedGameIds: { 1: { 1: true } }, + }); + const result = Selectors.getJoinedGames(rootState(state), 1); + expect(result).toHaveLength(1); + expect(result[0]).toBe(game1); + }); + + it('getRoomMessages → returns messages array for roomId', () => { + const messages = [makeMessage()]; + const state = makeRoomsState({ messages: { 1: messages } }); + expect(Selectors.getRoomMessages(rootState(state), 1)).toBe(messages); + }); + + it('getRoomGames → returns gameList for roomId', () => { + const games = [makeGame()]; + const room = makeRoom({ roomId: 1, gameList: games }); + const state = makeRoomsState({ rooms: { 1: room } }); + expect(Selectors.getRoomGames(rootState(state), 1)).toBe(games); + }); + + it('getRoomUsers → returns userList for roomId', () => { + const users = [makeUser()]; + const room = makeRoom({ roomId: 1, userList: users }); + const state = makeRoomsState({ rooms: { 1: room } }); + expect(Selectors.getRoomUsers(rootState(state), 1)).toBe(users); + }); +}); diff --git a/webclient/src/store/server/__mocks__/server-fixtures.ts b/webclient/src/store/server/__mocks__/server-fixtures.ts new file mode 100644 index 000000000..6ac04c121 --- /dev/null +++ b/webclient/src/store/server/__mocks__/server-fixtures.ts @@ -0,0 +1,154 @@ +import { + BanHistoryItem, + DeckList, + DeckStorageTreeItem, + LogItem, + ReplayMatch, + SortDirection, + StatusEnum, + User, + UserPrivLevel, + UserSortField, + WebSocketConnectOptions, + WarnHistoryItem, + WarnListItem, +} from 'types'; +import { ServerState } from '../server.interfaces'; + +export function makeUser(overrides: Partial = {}): User { + return { + name: 'TestUser', + accountageSecs: 0, + privlevel: UserPrivLevel.NONE, + userLevel: 0, + ...overrides, + }; +} + +export function makeLogItem(overrides: Partial = {}): LogItem { + return { + message: '', + senderId: '', + senderIp: '', + senderName: '', + targetId: '', + targetName: '', + targetType: '', + time: '', + ...overrides, + }; +} + +export function makeBanHistoryItem(overrides: Partial = {}): BanHistoryItem { + return { + adminId: '', + adminName: '', + banTime: '', + banLength: '', + banReason: '', + visibleReason: '', + ...overrides, + }; +} + +export function makeWarnHistoryItem(overrides: Partial = {}): WarnHistoryItem { + return { + userName: '', + adminName: '', + reason: '', + timeOf: '', + ...overrides, + }; +} + +export function makeWarnListItem(overrides: Partial = {}): WarnListItem { + return { + warning: '', + userName: '', + userClientid: '', + ...overrides, + }; +} + +export function makeDeckTreeItem(overrides: Partial = {}): DeckStorageTreeItem { + return { + id: 1, + name: 'item', + file: { creationTime: 0 }, + folder: null, + ...overrides, + }; +} + +export function makeDeckList(overrides: Partial = {}): DeckList { + return { + root: { items: [] }, + ...overrides, + }; +} + +export function makeReplayMatch(overrides: Partial = {}): ReplayMatch { + return { + gameId: 1, + roomName: 'Test Room', + timeStarted: 0, + length: 0, + gameName: 'Test Game', + playerNames: [], + doNotHide: false, + replayList: [], + ...overrides, + }; +} + +export function makeConnectOptions(overrides: Partial = {}): WebSocketConnectOptions { + return { + host: 'localhost', + port: '4747', + userName: 'user', + password: 'pass', + ...overrides, + }; +} + +export function makeServerState(overrides: Partial = {}): ServerState { + return { + initialized: false, + buddyList: [], + ignoreList: [], + status: { + state: StatusEnum.DISCONNECTED, + description: null, + }, + info: { + message: null, + name: null, + version: null, + }, + logs: { + room: [], + game: [], + chat: [], + }, + user: null, + users: [], + sortUsersBy: { + field: UserSortField.NAME, + order: SortDirection.ASC, + }, + connectOptions: {}, + messages: {}, + userInfo: {}, + notifications: [], + serverShutdown: null, + banUser: '', + banHistory: {}, + warnHistory: {}, + warnListOptions: [], + warnUser: '', + adminNotes: {}, + replays: [], + backendDecks: null, + ...overrides, + }; +} diff --git a/webclient/src/store/server/server.actions.spec.ts b/webclient/src/store/server/server.actions.spec.ts new file mode 100644 index 000000000..7d61717fb --- /dev/null +++ b/webclient/src/store/server/server.actions.spec.ts @@ -0,0 +1,356 @@ +import { Actions } from './server.actions'; +import { Types } from './server.types'; +import { + makeBanHistoryItem, + makeConnectOptions, + makeDeckList, + makeDeckTreeItem, + makeReplayMatch, + makeUser, + makeWarnHistoryItem, + makeWarnListItem, +} from './__mocks__/server-fixtures'; + +describe('Actions', () => { + it('initialized', () => { + expect(Actions.initialized()).toEqual({ type: Types.INITIALIZED }); + }); + + it('clearStore', () => { + expect(Actions.clearStore()).toEqual({ type: Types.CLEAR_STORE }); + }); + + it('loginSuccessful', () => { + const options = makeConnectOptions(); + expect(Actions.loginSuccessful(options)).toEqual({ type: Types.LOGIN_SUCCESSFUL, options }); + }); + + it('loginFailed', () => { + expect(Actions.loginFailed()).toEqual({ type: Types.LOGIN_FAILED }); + }); + + it('connectionClosed', () => { + expect(Actions.connectionClosed(3)).toEqual({ type: Types.CONNECTION_CLOSED, reason: 3 }); + }); + + it('connectionFailed', () => { + expect(Actions.connectionFailed()).toEqual({ type: Types.CONNECTION_FAILED }); + }); + + it('testConnectionSuccessful', () => { + expect(Actions.testConnectionSuccessful()).toEqual({ type: Types.TEST_CONNECTION_SUCCESSFUL }); + }); + + it('testConnectionFailed', () => { + expect(Actions.testConnectionFailed()).toEqual({ type: Types.TEST_CONNECTION_FAILED }); + }); + + it('serverMessage', () => { + expect(Actions.serverMessage('hello')).toEqual({ type: Types.SERVER_MESSAGE, message: 'hello' }); + }); + + it('updateBuddyList', () => { + const list = [makeUser()]; + expect(Actions.updateBuddyList(list)).toEqual({ type: Types.UPDATE_BUDDY_LIST, buddyList: list }); + }); + + it('addToBuddyList', () => { + const user = makeUser(); + expect(Actions.addToBuddyList(user)).toEqual({ type: Types.ADD_TO_BUDDY_LIST, user }); + }); + + it('removeFromBuddyList', () => { + expect(Actions.removeFromBuddyList('Alice')).toEqual({ type: Types.REMOVE_FROM_BUDDY_LIST, userName: 'Alice' }); + }); + + it('updateIgnoreList', () => { + const list = [makeUser()]; + expect(Actions.updateIgnoreList(list)).toEqual({ type: Types.UPDATE_IGNORE_LIST, ignoreList: list }); + }); + + it('addToIgnoreList', () => { + const user = makeUser(); + expect(Actions.addToIgnoreList(user)).toEqual({ type: Types.ADD_TO_IGNORE_LIST, user }); + }); + + it('removeFromIgnoreList', () => { + expect(Actions.removeFromIgnoreList('Bob')).toEqual({ type: Types.REMOVE_FROM_IGNORE_LIST, userName: 'Bob' }); + }); + + it('updateInfo', () => { + const info = { name: 'Servatrice', version: '2.0' }; + expect(Actions.updateInfo(info)).toEqual({ type: Types.UPDATE_INFO, info }); + }); + + it('updateStatus', () => { + const status = { state: 2, description: 'connected' }; + expect(Actions.updateStatus(status)).toEqual({ type: Types.UPDATE_STATUS, status }); + }); + + it('updateUser', () => { + const user = makeUser(); + expect(Actions.updateUser(user)).toEqual({ type: Types.UPDATE_USER, user }); + }); + + it('updateUsers', () => { + const users = [makeUser()]; + expect(Actions.updateUsers(users)).toEqual({ type: Types.UPDATE_USERS, users }); + }); + + it('userJoined', () => { + const user = makeUser(); + expect(Actions.userJoined(user)).toEqual({ type: Types.USER_JOINED, user }); + }); + + it('userLeft', () => { + expect(Actions.userLeft('Carol')).toEqual({ type: Types.USER_LEFT, name: 'Carol' }); + }); + + it('viewLogs', () => { + const logs = { room: [], game: [], chat: [] }; + expect(Actions.viewLogs(logs)).toEqual({ type: Types.VIEW_LOGS, logs }); + }); + + it('clearLogs', () => { + expect(Actions.clearLogs()).toEqual({ type: Types.CLEAR_LOGS }); + }); + + it('registrationRequiresEmail', () => { + expect(Actions.registrationRequiresEmail()).toEqual({ type: Types.REGISTRATION_REQUIRES_EMAIL }); + }); + + it('registrationSuccess', () => { + expect(Actions.registrationSuccess()).toEqual({ type: Types.REGISTRATION_SUCCES }); + }); + + it('registrationFailed', () => { + expect(Actions.registrationFailed('err')).toEqual({ type: Types.REGISTRATION_FAILED, error: 'err' }); + }); + + it('registrationEmailError', () => { + expect(Actions.registrationEmailError('bad email')).toEqual({ type: Types.REGISTRATION_EMAIL_ERROR, error: 'bad email' }); + }); + + it('registrationPasswordError', () => { + expect(Actions.registrationPasswordError('bad pw')).toEqual({ type: Types.REGISTRATION_PASSWORD_ERROR, error: 'bad pw' }); + }); + + it('registrationUserNameError', () => { + expect(Actions.registrationUserNameError('bad name')).toEqual({ type: Types.REGISTRATION_USERNAME_ERROR, error: 'bad name' }); + }); + + it('accountAwaitingActivation', () => { + const options = makeConnectOptions(); + expect(Actions.accountAwaitingActivation(options)).toEqual({ type: Types.ACCOUNT_AWAITING_ACTIVATION, options }); + }); + + it('accountActivationSuccess', () => { + expect(Actions.accountActivationSuccess()).toEqual({ type: Types.ACCOUNT_ACTIVATION_SUCCESS }); + }); + + it('accountActivationFailed', () => { + expect(Actions.accountActivationFailed()).toEqual({ type: Types.ACCOUNT_ACTIVATION_FAILED }); + }); + + it('resetPassword', () => { + expect(Actions.resetPassword()).toEqual({ type: Types.RESET_PASSWORD_REQUESTED }); + }); + + it('resetPasswordFailed', () => { + expect(Actions.resetPasswordFailed()).toEqual({ type: Types.RESET_PASSWORD_FAILED }); + }); + + it('resetPasswordChallenge', () => { + expect(Actions.resetPasswordChallenge()).toEqual({ type: Types.RESET_PASSWORD_CHALLENGE }); + }); + + it('resetPasswordSuccess', () => { + expect(Actions.resetPasswordSuccess()).toEqual({ type: Types.RESET_PASSWORD_SUCCESS }); + }); + + it('adjustMod', () => { + expect(Actions.adjustMod('Dan', true, false)).toEqual({ + type: Types.ADJUST_MOD, + userName: 'Dan', + shouldBeMod: true, + shouldBeJudge: false, + }); + }); + + it('reloadConfig', () => { + expect(Actions.reloadConfig()).toEqual({ type: Types.RELOAD_CONFIG }); + }); + + it('shutdownServer', () => { + expect(Actions.shutdownServer()).toEqual({ type: Types.SHUTDOWN_SERVER }); + }); + + it('updateServerMessage', () => { + expect(Actions.updateServerMessage()).toEqual({ type: Types.UPDATE_SERVER_MESSAGE }); + }); + + it('accountPasswordChange', () => { + expect(Actions.accountPasswordChange()).toEqual({ type: Types.ACCOUNT_PASSWORD_CHANGE }); + }); + + it('accountEditChanged', () => { + const user = makeUser(); + expect(Actions.accountEditChanged(user)).toEqual({ type: Types.ACCOUNT_EDIT_CHANGED, user }); + }); + + it('accountImageChanged', () => { + const user = makeUser(); + expect(Actions.accountImageChanged(user)).toEqual({ type: Types.ACCOUNT_IMAGE_CHANGED, user }); + }); + + it('directMessageSent', () => { + expect(Actions.directMessageSent('Eve', 'hi')).toEqual({ + type: Types.DIRECT_MESSAGE_SENT, + userName: 'Eve', + message: 'hi', + }); + }); + + it('getUserInfo', () => { + const userInfo = makeUser({ name: 'Frank' }); + expect(Actions.getUserInfo(userInfo)).toEqual({ type: Types.GET_USER_INFO, userInfo }); + }); + + it('notifyUser', () => { + const notification = { type: 1, warningReason: '', customTitle: '', customContent: '' }; + expect(Actions.notifyUser(notification)).toEqual({ type: Types.NOTIFY_USER, notification }); + }); + + it('serverShutdown', () => { + const data = { reason: 'maintenance', minutes: 5 }; + expect(Actions.serverShutdown(data)).toEqual({ type: Types.SERVER_SHUTDOWN, data }); + }); + + it('userMessage', () => { + const messageData = { senderName: 'Alice', receiverName: 'Bob', message: 'hey' }; + expect(Actions.userMessage(messageData)).toEqual({ type: Types.USER_MESSAGE, messageData }); + }); + + it('addToList', () => { + expect(Actions.addToList('buddyList', 'Grace')).toEqual({ + type: Types.ADD_TO_LIST, + list: 'buddyList', + userName: 'Grace', + }); + }); + + it('removeFromList', () => { + expect(Actions.removeFromList('buddyList', 'Hank')).toEqual({ + type: Types.REMOVE_FROM_LIST, + list: 'buddyList', + userName: 'Hank', + }); + }); + + it('banFromServer', () => { + expect(Actions.banFromServer('Ira')).toEqual({ type: Types.BAN_FROM_SERVER, userName: 'Ira' }); + }); + + it('banHistory', () => { + const history = [makeBanHistoryItem()]; + expect(Actions.banHistory('Ira', history)).toEqual({ type: Types.BAN_HISTORY, userName: 'Ira', banHistory: history }); + }); + + it('warnHistory', () => { + const history = [makeWarnHistoryItem()]; + expect(Actions.warnHistory('Jack', history)).toEqual({ type: Types.WARN_HISTORY, userName: 'Jack', warnHistory: history }); + }); + + it('warnListOptions', () => { + const list = [makeWarnListItem()]; + expect(Actions.warnListOptions(list)).toEqual({ type: Types.WARN_LIST_OPTIONS, warnList: list }); + }); + + it('warnUser', () => { + expect(Actions.warnUser('Kelly')).toEqual({ type: Types.WARN_USER, userName: 'Kelly' }); + }); + + it('grantReplayAccess', () => { + expect(Actions.grantReplayAccess(7, 'Moe')).toEqual({ + type: Types.GRANT_REPLAY_ACCESS, + replayId: 7, + moderatorName: 'Moe', + }); + }); + + it('forceActivateUser', () => { + expect(Actions.forceActivateUser('Ned', 'Moe')).toEqual({ + type: Types.FORCE_ACTIVATE_USER, + usernameToActivate: 'Ned', + moderatorName: 'Moe', + }); + }); + + it('getAdminNotes', () => { + expect(Actions.getAdminNotes('Ned', 'some notes')).toEqual({ + type: Types.GET_ADMIN_NOTES, + userName: 'Ned', + notes: 'some notes', + }); + }); + + it('updateAdminNotes', () => { + expect(Actions.updateAdminNotes('Ned', 'updated notes')).toEqual({ + type: Types.UPDATE_ADMIN_NOTES, + userName: 'Ned', + notes: 'updated notes', + }); + }); + + it('replayList', () => { + const list = [makeReplayMatch()]; + expect(Actions.replayList(list)).toEqual({ type: Types.REPLAY_LIST, matchList: list }); + }); + + it('replayAdded', () => { + const match = makeReplayMatch(); + expect(Actions.replayAdded(match)).toEqual({ type: Types.REPLAY_ADDED, matchInfo: match }); + }); + + it('replayModifyMatch', () => { + expect(Actions.replayModifyMatch(5, true)).toEqual({ + type: Types.REPLAY_MODIFY_MATCH, + gameId: 5, + doNotHide: true, + }); + }); + + it('replayDeleteMatch', () => { + expect(Actions.replayDeleteMatch(5)).toEqual({ type: Types.REPLAY_DELETE_MATCH, gameId: 5 }); + }); + + it('backendDecks', () => { + const deckList = makeDeckList(); + expect(Actions.backendDecks(deckList)).toEqual({ type: Types.BACKEND_DECKS, deckList }); + }); + + it('deckNewDir', () => { + expect(Actions.deckNewDir('a/b', 'newFolder')).toEqual({ + type: Types.DECK_NEW_DIR, + path: 'a/b', + dirName: 'newFolder', + }); + }); + + it('deckDelDir', () => { + expect(Actions.deckDelDir('a/b')).toEqual({ type: Types.DECK_DEL_DIR, path: 'a/b' }); + }); + + it('deckUpload', () => { + const treeItem = makeDeckTreeItem(); + expect(Actions.deckUpload('a/b', treeItem)).toEqual({ + type: Types.DECK_UPLOAD, + path: 'a/b', + treeItem, + }); + }); + + it('deckDelete', () => { + expect(Actions.deckDelete(42)).toEqual({ type: Types.DECK_DELETE, deckId: 42 }); + }); +}); diff --git a/webclient/src/store/server/server.dispatch.spec.ts b/webclient/src/store/server/server.dispatch.spec.ts new file mode 100644 index 000000000..f6bab848d --- /dev/null +++ b/webclient/src/store/server/server.dispatch.spec.ts @@ -0,0 +1,388 @@ +jest.mock('store/store', () => ({ store: { dispatch: jest.fn() } })); +jest.mock('redux-form', () => ({ + reset: jest.fn((form) => ({ type: '@@redux-form/RESET', meta: { form } })), +})); + +import { store } from 'store/store'; +import { reset } from 'redux-form'; +import { Actions } from './server.actions'; +import { Dispatch } from './server.dispatch'; +import { + makeBanHistoryItem, + makeConnectOptions, + makeDeckList, + makeDeckTreeItem, + makeReplayMatch, + makeUser, + makeWarnHistoryItem, + makeWarnListItem, +} from './__mocks__/server-fixtures'; + +beforeEach(() => jest.clearAllMocks()); + +describe('Dispatch', () => { + it('initialized dispatches Actions.initialized()', () => { + Dispatch.initialized(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.initialized()); + }); + + it('clearStore dispatches Actions.clearStore()', () => { + Dispatch.clearStore(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.clearStore()); + }); + + it('loginSuccessful dispatches Actions.loginSuccessful()', () => { + const options = makeConnectOptions(); + Dispatch.loginSuccessful(options); + expect(store.dispatch).toHaveBeenCalledWith(Actions.loginSuccessful(options)); + }); + + it('loginFailed dispatches Actions.loginFailed()', () => { + Dispatch.loginFailed(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.loginFailed()); + }); + + it('connectionClosed dispatches Actions.connectionClosed()', () => { + Dispatch.connectionClosed(3); + expect(store.dispatch).toHaveBeenCalledWith(Actions.connectionClosed(3)); + }); + + it('connectionFailed dispatches Actions.connectionFailed()', () => { + Dispatch.connectionFailed(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.connectionFailed()); + }); + + it('testConnectionSuccessful dispatches Actions.testConnectionSuccessful()', () => { + Dispatch.testConnectionSuccessful(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.testConnectionSuccessful()); + }); + + it('testConnectionFailed dispatches Actions.testConnectionFailed()', () => { + Dispatch.testConnectionFailed(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.testConnectionFailed()); + }); + + it('updateBuddyList dispatches Actions.updateBuddyList()', () => { + const list = [makeUser()]; + Dispatch.updateBuddyList(list); + expect(store.dispatch).toHaveBeenCalledWith(Actions.updateBuddyList(list)); + }); + + it('addToBuddyList dispatches reset("addToBuddies") then Actions.addToBuddyList()', () => { + const user = makeUser(); + Dispatch.addToBuddyList(user); + expect(store.dispatch).toHaveBeenNthCalledWith(1, (reset as jest.Mock)('addToBuddies')); + expect(store.dispatch).toHaveBeenNthCalledWith(2, Actions.addToBuddyList(user)); + }); + + it('removeFromBuddyList dispatches Actions.removeFromBuddyList()', () => { + Dispatch.removeFromBuddyList('Alice'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.removeFromBuddyList('Alice')); + }); + + it('updateIgnoreList dispatches Actions.updateIgnoreList()', () => { + const list = [makeUser()]; + Dispatch.updateIgnoreList(list); + expect(store.dispatch).toHaveBeenCalledWith(Actions.updateIgnoreList(list)); + }); + + it('addToIgnoreList dispatches reset("addToIgnore") then Actions.addToIgnoreList()', () => { + const user = makeUser(); + Dispatch.addToIgnoreList(user); + expect(store.dispatch).toHaveBeenNthCalledWith(1, (reset as jest.Mock)('addToIgnore')); + expect(store.dispatch).toHaveBeenNthCalledWith(2, Actions.addToIgnoreList(user)); + }); + + it('removeFromIgnoreList dispatches Actions.removeFromIgnoreList()', () => { + Dispatch.removeFromIgnoreList('Bob'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.removeFromIgnoreList('Bob')); + }); + + it('updateInfo dispatches Actions.updateInfo({ name, version })', () => { + Dispatch.updateInfo('Servatrice', '2.9'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.updateInfo({ name: 'Servatrice', version: '2.9' })); + }); + + it('updateStatus dispatches Actions.updateStatus({ state, description })', () => { + Dispatch.updateStatus(2, 'ok'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.updateStatus({ state: 2, description: 'ok' })); + }); + + it('updateUser dispatches Actions.updateUser()', () => { + const user = makeUser(); + Dispatch.updateUser(user); + expect(store.dispatch).toHaveBeenCalledWith(Actions.updateUser(user)); + }); + + it('updateUsers dispatches Actions.updateUsers()', () => { + const users = [makeUser()]; + Dispatch.updateUsers(users); + expect(store.dispatch).toHaveBeenCalledWith(Actions.updateUsers(users)); + }); + + it('userJoined dispatches Actions.userJoined()', () => { + const user = makeUser(); + Dispatch.userJoined(user); + expect(store.dispatch).toHaveBeenCalledWith(Actions.userJoined(user)); + }); + + it('userLeft dispatches Actions.userLeft()', () => { + Dispatch.userLeft('Carol'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.userLeft('Carol')); + }); + + it('viewLogs dispatches Actions.viewLogs()', () => { + const logs = { room: [], game: [], chat: [] }; + Dispatch.viewLogs(logs); + expect(store.dispatch).toHaveBeenCalledWith(Actions.viewLogs(logs)); + }); + + it('clearLogs dispatches Actions.clearLogs()', () => { + Dispatch.clearLogs(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.clearLogs()); + }); + + it('serverMessage dispatches Actions.serverMessage()', () => { + Dispatch.serverMessage('Welcome!'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.serverMessage('Welcome!')); + }); + + it('registrationRequiresEmail dispatches correctly', () => { + Dispatch.registrationRequiresEmail(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.registrationRequiresEmail()); + }); + + it('registrationSuccess dispatches correctly', () => { + Dispatch.registrationSuccess(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.registrationSuccess()); + }); + + it('registrationFailed dispatches correctly', () => { + Dispatch.registrationFailed('err'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.registrationFailed('err')); + }); + + it('registrationEmailError dispatches correctly', () => { + Dispatch.registrationEmailError('bad'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.registrationEmailError('bad')); + }); + + it('registrationPasswordError dispatches correctly', () => { + Dispatch.registrationPasswordError('weak'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.registrationPasswordError('weak')); + }); + + it('registrationUserNameError dispatches correctly', () => { + Dispatch.registrationUserNameError('taken'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.registrationUserNameError('taken')); + }); + + it('accountAwaitingActivation dispatches correctly', () => { + const options = makeConnectOptions(); + Dispatch.accountAwaitingActivation(options); + expect(store.dispatch).toHaveBeenCalledWith(Actions.accountAwaitingActivation(options)); + }); + + it('accountActivationSuccess dispatches correctly', () => { + Dispatch.accountActivationSuccess(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.accountActivationSuccess()); + }); + + it('accountActivationFailed dispatches correctly', () => { + Dispatch.accountActivationFailed(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.accountActivationFailed()); + }); + + it('resetPassword dispatches correctly', () => { + Dispatch.resetPassword(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.resetPassword()); + }); + + it('resetPasswordFailed dispatches correctly', () => { + Dispatch.resetPasswordFailed(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.resetPasswordFailed()); + }); + + it('resetPasswordChallenge dispatches correctly', () => { + Dispatch.resetPasswordChallenge(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.resetPasswordChallenge()); + }); + + it('resetPasswordSuccess dispatches correctly', () => { + Dispatch.resetPasswordSuccess(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.resetPasswordSuccess()); + }); + + it('adjustMod dispatches Actions.adjustMod()', () => { + Dispatch.adjustMod('Dan', true, false); + expect(store.dispatch).toHaveBeenCalledWith(Actions.adjustMod('Dan', true, false)); + }); + + it('reloadConfig dispatches correctly', () => { + Dispatch.reloadConfig(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.reloadConfig()); + }); + + it('shutdownServer dispatches correctly', () => { + Dispatch.shutdownServer(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.shutdownServer()); + }); + + it('updateServerMessage dispatches correctly', () => { + Dispatch.updateServerMessage(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.updateServerMessage()); + }); + + it('accountPasswordChange dispatches correctly', () => { + Dispatch.accountPasswordChange(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.accountPasswordChange()); + }); + + it('accountEditChanged dispatches correctly', () => { + const user = makeUser(); + Dispatch.accountEditChanged(user); + expect(store.dispatch).toHaveBeenCalledWith(Actions.accountEditChanged(user)); + }); + + it('accountImageChanged dispatches correctly', () => { + const user = makeUser(); + Dispatch.accountImageChanged(user); + expect(store.dispatch).toHaveBeenCalledWith(Actions.accountImageChanged(user)); + }); + + it('directMessageSent dispatches correctly', () => { + Dispatch.directMessageSent('Eve', 'hi'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.directMessageSent('Eve', 'hi')); + }); + + it('getUserInfo dispatches correctly', () => { + const userInfo = makeUser({ name: 'Frank' }); + Dispatch.getUserInfo(userInfo); + expect(store.dispatch).toHaveBeenCalledWith(Actions.getUserInfo(userInfo)); + }); + + it('notifyUser dispatches correctly', () => { + const notification = { type: 1, warningReason: '', customTitle: '', customContent: '' }; + Dispatch.notifyUser(notification); + expect(store.dispatch).toHaveBeenCalledWith(Actions.notifyUser(notification)); + }); + + it('serverShutdown dispatches correctly', () => { + const data = { reason: 'maintenance', minutes: 5 }; + Dispatch.serverShutdown(data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.serverShutdown(data)); + }); + + it('userMessage dispatches correctly', () => { + const messageData = { senderName: 'Alice', receiverName: 'Bob', message: 'hey' }; + Dispatch.userMessage(messageData); + expect(store.dispatch).toHaveBeenCalledWith(Actions.userMessage(messageData)); + }); + + it('addToList dispatches correctly', () => { + Dispatch.addToList('buddyList', 'Grace'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.addToList('buddyList', 'Grace')); + }); + + it('removeFromList dispatches correctly', () => { + Dispatch.removeFromList('buddyList', 'Hank'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.removeFromList('buddyList', 'Hank')); + }); + + it('banFromServer dispatches correctly', () => { + Dispatch.banFromServer('Ira'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.banFromServer('Ira')); + }); + + it('banHistory dispatches correctly', () => { + const history = [makeBanHistoryItem()]; + Dispatch.banHistory('Ira', history); + expect(store.dispatch).toHaveBeenCalledWith(Actions.banHistory('Ira', history)); + }); + + it('warnHistory dispatches correctly', () => { + const history = [makeWarnHistoryItem()]; + Dispatch.warnHistory('Jack', history); + expect(store.dispatch).toHaveBeenCalledWith(Actions.warnHistory('Jack', history)); + }); + + it('warnListOptions dispatches correctly', () => { + const list = [makeWarnListItem()]; + Dispatch.warnListOptions(list); + expect(store.dispatch).toHaveBeenCalledWith(Actions.warnListOptions(list)); + }); + + it('warnUser dispatches correctly', () => { + Dispatch.warnUser('Kelly'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.warnUser('Kelly')); + }); + + it('grantReplayAccess dispatches correctly', () => { + Dispatch.grantReplayAccess(7, 'Moe'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.grantReplayAccess(7, 'Moe')); + }); + + it('forceActivateUser dispatches correctly', () => { + Dispatch.forceActivateUser('Ned', 'Moe'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.forceActivateUser('Ned', 'Moe')); + }); + + it('getAdminNotes dispatches correctly', () => { + Dispatch.getAdminNotes('Ned', 'notes'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.getAdminNotes('Ned', 'notes')); + }); + + it('updateAdminNotes dispatches correctly', () => { + Dispatch.updateAdminNotes('Ned', 'updated'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.updateAdminNotes('Ned', 'updated')); + }); + + it('replayList dispatches correctly', () => { + const list = [makeReplayMatch()]; + Dispatch.replayList(list); + expect(store.dispatch).toHaveBeenCalledWith(Actions.replayList(list)); + }); + + it('replayAdded dispatches correctly', () => { + const match = makeReplayMatch(); + Dispatch.replayAdded(match); + expect(store.dispatch).toHaveBeenCalledWith(Actions.replayAdded(match)); + }); + + it('replayModifyMatch dispatches correctly', () => { + Dispatch.replayModifyMatch(5, true); + expect(store.dispatch).toHaveBeenCalledWith(Actions.replayModifyMatch(5, true)); + }); + + it('replayDeleteMatch dispatches correctly', () => { + Dispatch.replayDeleteMatch(5); + expect(store.dispatch).toHaveBeenCalledWith(Actions.replayDeleteMatch(5)); + }); + + it('backendDecks dispatches correctly', () => { + const deckList = makeDeckList(); + Dispatch.backendDecks(deckList); + expect(store.dispatch).toHaveBeenCalledWith(Actions.backendDecks(deckList)); + }); + + it('deckNewDir dispatches correctly', () => { + Dispatch.deckNewDir('a/b', 'newFolder'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.deckNewDir('a/b', 'newFolder')); + }); + + it('deckDelDir dispatches correctly', () => { + Dispatch.deckDelDir('a/b'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.deckDelDir('a/b')); + }); + + it('deckUpload dispatches correctly', () => { + const treeItem = makeDeckTreeItem(); + Dispatch.deckUpload('a/b', treeItem); + expect(store.dispatch).toHaveBeenCalledWith(Actions.deckUpload('a/b', treeItem)); + }); + + it('deckDelete dispatches correctly', () => { + Dispatch.deckDelete(42); + expect(store.dispatch).toHaveBeenCalledWith(Actions.deckDelete(42)); + }); +}); diff --git a/webclient/src/store/server/server.reducer.spec.ts b/webclient/src/store/server/server.reducer.spec.ts new file mode 100644 index 000000000..e6ba27680 --- /dev/null +++ b/webclient/src/store/server/server.reducer.spec.ts @@ -0,0 +1,526 @@ +import { StatusEnum, UserLevelFlag } from 'types'; +import { serverReducer } from './server.reducer'; +import { Types } from './server.types'; +import { + makeBanHistoryItem, + makeConnectOptions, + makeDeckList, + makeDeckTreeItem, + makeLogItem, + makeReplayMatch, + makeServerState, + makeUser, + makeWarnHistoryItem, + makeWarnListItem, +} from './__mocks__/server-fixtures'; + +// ── Initialisation ─────────────────────────────────────────────────────────── + +describe('Initialisation', () => { + it('returns initialState when called with undefined state', () => { + const result = serverReducer(undefined, { type: '@@INIT' }); + expect(result.initialized).toBe(false); + expect(result.buddyList).toEqual([]); + expect(result.status.state).toBe(StatusEnum.DISCONNECTED); + }); + + it('INITIALIZED → resets to initialState with initialized: true', () => { + const state = makeServerState({ banUser: 'someone', initialized: false }); + const result = serverReducer(state, { type: Types.INITIALIZED }); + expect(result.initialized).toBe(true); + expect(result.banUser).toBe(''); + expect(result.buddyList).toEqual([]); + }); + + it('CLEAR_STORE → resets to initialState but preserves status', () => { + const status = { state: StatusEnum.LOGGED_IN, description: 'logged in' }; + const state = makeServerState({ status, banUser: 'someone' }); + const result = serverReducer(state, { type: Types.CLEAR_STORE }); + expect(result.banUser).toBe(''); + expect(result.status).toEqual(status); + expect(result.initialized).toBe(false); + }); + + it('default → returns state unchanged for unknown action', () => { + const state = makeServerState(); + const result = serverReducer(state, { type: '@@UNKNOWN' }); + expect(result).toBe(state); + }); +}); + +// ── Account & Connection ───────────────────────────────────────────────────── + +describe('Account & Connection', () => { + it('ACCOUNT_AWAITING_ACTIVATION → sets connectOptions from action.options', () => { + const options = makeConnectOptions(); + const state = makeServerState(); + const result = serverReducer(state, { type: Types.ACCOUNT_AWAITING_ACTIVATION, options }); + expect(result.connectOptions).toEqual(options); + }); + + it('ACCOUNT_ACTIVATION_SUCCESS → clears connectOptions to {}', () => { + const state = makeServerState({ connectOptions: makeConnectOptions() }); + const result = serverReducer(state, { type: Types.ACCOUNT_ACTIVATION_SUCCESS }); + expect(result.connectOptions).toEqual({}); + }); + + it('ACCOUNT_ACTIVATION_FAILED → clears connectOptions to {}', () => { + const state = makeServerState({ connectOptions: makeConnectOptions() }); + const result = serverReducer(state, { type: Types.ACCOUNT_ACTIVATION_FAILED }); + expect(result.connectOptions).toEqual({}); + }); +}); + +// ── Server Info & Status ────────────────────────────────────────────────────── + +describe('Server Info & Status', () => { + it('SERVER_MESSAGE → merges message into state.info', () => { + const state = makeServerState({ info: { message: null, name: 'Old', version: '1.0' } }); + const result = serverReducer(state, { type: Types.SERVER_MESSAGE, message: 'Welcome!' }); + expect(result.info.message).toBe('Welcome!'); + expect(result.info.name).toBe('Old'); + expect(result.info.version).toBe('1.0'); + }); + + it('UPDATE_INFO → merges name and version into state.info (not message)', () => { + const state = makeServerState({ info: { message: 'hi', name: null, version: null } }); + const result = serverReducer(state, { + type: Types.UPDATE_INFO, + info: { name: 'Servatrice', version: '2.9.0' }, + }); + expect(result.info.name).toBe('Servatrice'); + expect(result.info.version).toBe('2.9.0'); + expect(result.info.message).toBe('hi'); + }); + + it('UPDATE_STATUS → replaces state.status entirely', () => { + const state = makeServerState(); + const status = { state: StatusEnum.LOGGED_IN, description: 'ok' }; + const result = serverReducer(state, { type: Types.UPDATE_STATUS, status }); + expect(result.status).toEqual(status); + }); +}); + +// ── User ────────────────────────────────────────────────────────────────────── + +describe('User', () => { + it('UPDATE_USER → merges action.user into state.user', () => { + const state = makeServerState({ user: makeUser({ name: 'Alice', userLevel: 1 }) }); + const result = serverReducer(state, { + type: Types.UPDATE_USER, + user: { userLevel: 8 }, + }); + expect(result.user.name).toBe('Alice'); + expect(result.user.userLevel).toBe(8); + }); + + it('ACCOUNT_EDIT_CHANGED → merges action.user into state.user', () => { + const state = makeServerState({ user: makeUser({ name: 'Alice' }) }); + const result = serverReducer(state, { type: Types.ACCOUNT_EDIT_CHANGED, user: { realName: 'Alice Smith' } }); + expect(result.user.realName).toBe('Alice Smith'); + expect(result.user.name).toBe('Alice'); + }); + + it('ACCOUNT_IMAGE_CHANGED → merges action.user into state.user', () => { + const state = makeServerState({ user: makeUser({ name: 'Alice' }) }); + const result = serverReducer(state, { type: Types.ACCOUNT_IMAGE_CHANGED, user: { country: 'US' } }); + expect(result.user.country).toBe('US'); + }); +}); + +// ── Users List ──────────────────────────────────────────────────────────────── + +describe('Users List', () => { + it('UPDATE_USERS → replaces users list and sorts by name ASC', () => { + const state = makeServerState(); + const users = [makeUser({ name: 'Zane' }), makeUser({ name: 'Alice' })]; + const result = serverReducer(state, { type: Types.UPDATE_USERS, users }); + expect(result.users[0].name).toBe('Alice'); + expect(result.users[1].name).toBe('Zane'); + }); + + it('USER_JOINED → appends user and sorts', () => { + const state = makeServerState({ users: [makeUser({ name: 'Zane' })] }); + const result = serverReducer(state, { type: Types.USER_JOINED, user: makeUser({ name: 'Alice' }) }); + expect(result.users[0].name).toBe('Alice'); + expect(result.users[1].name).toBe('Zane'); + }); + + it('USER_LEFT → removes user by name', () => { + const state = makeServerState({ users: [makeUser({ name: 'Alice' }), makeUser({ name: 'Bob' })] }); + const result = serverReducer(state, { type: Types.USER_LEFT, name: 'Alice' }); + expect(result.users).toHaveLength(1); + expect(result.users[0].name).toBe('Bob'); + }); +}); + +// ── Buddy & Ignore Lists ────────────────────────────────────────────────────── + +describe('Buddy List', () => { + it('UPDATE_BUDDY_LIST → replaces and sorts buddy list', () => { + const state = makeServerState(); + const buddyList = [makeUser({ name: 'Zane' }), makeUser({ name: 'Alice' })]; + const result = serverReducer(state, { type: Types.UPDATE_BUDDY_LIST, buddyList }); + expect(result.buddyList[0].name).toBe('Alice'); + }); + + it('ADD_TO_BUDDY_LIST → appends user and sorts', () => { + const state = makeServerState({ buddyList: [makeUser({ name: 'Zane' })] }); + const result = serverReducer(state, { type: Types.ADD_TO_BUDDY_LIST, user: makeUser({ name: 'Alice' }) }); + expect(result.buddyList[0].name).toBe('Alice'); + expect(result.buddyList).toHaveLength(2); + }); + + it('REMOVE_FROM_BUDDY_LIST → removes user by name', () => { + const state = makeServerState({ buddyList: [makeUser({ name: 'Alice' }), makeUser({ name: 'Bob' })] }); + const result = serverReducer(state, { type: Types.REMOVE_FROM_BUDDY_LIST, userName: 'Alice' }); + expect(result.buddyList).toHaveLength(1); + expect(result.buddyList[0].name).toBe('Bob'); + }); +}); + +describe('Ignore List', () => { + it('UPDATE_IGNORE_LIST → replaces and sorts ignore list', () => { + const state = makeServerState(); + const ignoreList = [makeUser({ name: 'Zane' }), makeUser({ name: 'Alice' })]; + const result = serverReducer(state, { type: Types.UPDATE_IGNORE_LIST, ignoreList }); + expect(result.ignoreList[0].name).toBe('Alice'); + }); + + it('ADD_TO_IGNORE_LIST → appends user and sorts', () => { + const state = makeServerState({ ignoreList: [makeUser({ name: 'Zane' })] }); + const result = serverReducer(state, { type: Types.ADD_TO_IGNORE_LIST, user: makeUser({ name: 'Alice' }) }); + expect(result.ignoreList[0].name).toBe('Alice'); + expect(result.ignoreList).toHaveLength(2); + }); + + it('REMOVE_FROM_IGNORE_LIST → removes user by name', () => { + const state = makeServerState({ ignoreList: [makeUser({ name: 'Alice' }), makeUser({ name: 'Bob' })] }); + const result = serverReducer(state, { type: Types.REMOVE_FROM_IGNORE_LIST, userName: 'Alice' }); + expect(result.ignoreList).toHaveLength(1); + expect(result.ignoreList[0].name).toBe('Bob'); + }); +}); + +// ── Logs ───────────────────────────────────────────────────────────────────── + +describe('Logs', () => { + it('VIEW_LOGS → replaces logs entirely', () => { + const logs = { room: [makeLogItem()], game: [], chat: [] }; + const state = makeServerState(); + const result = serverReducer(state, { type: Types.VIEW_LOGS, logs }); + expect(result.logs).toEqual(logs); + }); + + it('CLEAR_LOGS → resets logs to empty arrays', () => { + const state = makeServerState({ logs: { room: [makeLogItem()], game: [], chat: [] } }); + const result = serverReducer(state, { type: Types.CLEAR_LOGS }); + expect(result.logs.room).toEqual([]); + expect(result.logs.game).toEqual([]); + expect(result.logs.chat).toEqual([]); + }); +}); + +// ── Messaging ───────────────────────────────────────────────────────────────── + +describe('Messaging', () => { + it('USER_MESSAGE → uses receiverName as key when current user is sender', () => { + const state = makeServerState({ user: makeUser({ name: 'Alice' }), messages: {} }); + const messageData = { senderName: 'Alice', receiverName: 'Bob', message: 'hi' }; + const result = serverReducer(state, { type: Types.USER_MESSAGE, messageData }); + expect(result.messages['Bob']).toHaveLength(1); + expect(result.messages['Bob'][0]).toBe(messageData); + }); + + it('USER_MESSAGE → uses senderName as key when current user is receiver', () => { + const state = makeServerState({ user: makeUser({ name: 'Bob' }), messages: {} }); + const messageData = { senderName: 'Alice', receiverName: 'Bob', message: 'yo' }; + const result = serverReducer(state, { type: Types.USER_MESSAGE, messageData }); + expect(result.messages['Alice']).toHaveLength(1); + expect(result.messages['Alice'][0]).toBe(messageData); + }); + + it('USER_MESSAGE → appends to existing messages for that user', () => { + const existingMsg = { senderName: 'Alice', receiverName: 'Bob', message: 'first' }; + const state = makeServerState({ + user: makeUser({ name: 'Bob' }), + messages: { Alice: [existingMsg] }, + }); + const newMsg = { senderName: 'Alice', receiverName: 'Bob', message: 'second' }; + const result = serverReducer(state, { type: Types.USER_MESSAGE, messageData: newMsg }); + expect(result.messages['Alice']).toHaveLength(2); + }); +}); + +// ── User Info & Notifications ───────────────────────────────────────────────── + +describe('User Info & Notifications', () => { + it('GET_USER_INFO → adds userInfo keyed by name', () => { + const userInfo = makeUser({ name: 'Eve' }); + const state = makeServerState(); + const result = serverReducer(state, { type: Types.GET_USER_INFO, userInfo }); + expect(result.userInfo['Eve']).toBe(userInfo); + }); + + it('NOTIFY_USER → appends notification to list', () => { + const state = makeServerState({ notifications: [] }); + const notification = { type: 1, warningReason: '', customTitle: '', customContent: '' }; + const result = serverReducer(state, { type: Types.NOTIFY_USER, notification }); + expect(result.notifications).toHaveLength(1); + expect(result.notifications[0]).toBe(notification); + }); + + it('SERVER_SHUTDOWN → sets serverShutdown to action.data', () => { + const data = { reason: 'maintenance', minutes: 10 }; + const state = makeServerState(); + const result = serverReducer(state, { type: Types.SERVER_SHUTDOWN, data }); + expect(result.serverShutdown).toBe(data); + }); +}); + +// ── Moderation ──────────────────────────────────────────────────────────────── + +describe('Moderation', () => { + it('BAN_FROM_SERVER → sets banUser', () => { + const state = makeServerState(); + const result = serverReducer(state, { type: Types.BAN_FROM_SERVER, userName: 'Frank' }); + expect(result.banUser).toBe('Frank'); + }); + + it('BAN_HISTORY → adds banHistory keyed by userName', () => { + const history = [makeBanHistoryItem()]; + const state = makeServerState(); + const result = serverReducer(state, { type: Types.BAN_HISTORY, userName: 'Frank', banHistory: history }); + expect(result.banHistory['Frank']).toBe(history); + }); + + it('WARN_HISTORY → adds warnHistory keyed by userName', () => { + const history = [makeWarnHistoryItem()]; + const state = makeServerState(); + const result = serverReducer(state, { type: Types.WARN_HISTORY, userName: 'Grace', warnHistory: history }); + expect(result.warnHistory['Grace']).toBe(history); + }); + + it('WARN_LIST_OPTIONS → replaces warnListOptions', () => { + const list = [makeWarnListItem()]; + const state = makeServerState(); + const result = serverReducer(state, { type: Types.WARN_LIST_OPTIONS, warnList: list }); + expect(result.warnListOptions).toBe(list); + }); + + it('WARN_USER → sets warnUser', () => { + const state = makeServerState(); + const result = serverReducer(state, { type: Types.WARN_USER, userName: 'Hank' }); + expect(result.warnUser).toBe('Hank'); + }); + + it('GET_ADMIN_NOTES → adds adminNotes keyed by userName', () => { + const state = makeServerState(); + const result = serverReducer(state, { type: Types.GET_ADMIN_NOTES, userName: 'Ira', notes: 'note1' }); + expect(result.adminNotes['Ira']).toBe('note1'); + }); + + it('UPDATE_ADMIN_NOTES → updates adminNotes keyed by userName', () => { + const state = makeServerState({ adminNotes: { Ira: 'old' } }); + const result = serverReducer(state, { type: Types.UPDATE_ADMIN_NOTES, userName: 'Ira', notes: 'new' }); + expect(result.adminNotes['Ira']).toBe('new'); + }); +}); + +// ── ADJUST_MOD ──────────────────────────────────────────────────────────────── + +describe('ADJUST_MOD', () => { + const baseUserLevel = UserLevelFlag.IsUser | UserLevelFlag.IsRegistered | UserLevelFlag.IsModerator | UserLevelFlag.IsJudge; + + it('shouldBeMod=true, shouldBeJudge=true → keeps IsModerator and IsJudge bits', () => { + const state = makeServerState({ users: [makeUser({ name: 'Dan', userLevel: baseUserLevel })] }); + const result = serverReducer(state, { type: Types.ADJUST_MOD, userName: 'Dan', shouldBeMod: true, shouldBeJudge: true }); + // IsModerator(4) | IsJudge(16) + expect(result.users[0].userLevel).toBe(20); + }); + + it('shouldBeMod=true, shouldBeJudge=false → keeps only IsModerator bit', () => { + const state = makeServerState({ users: [makeUser({ name: 'Dan', userLevel: baseUserLevel })] }); + const result = serverReducer(state, { type: Types.ADJUST_MOD, userName: 'Dan', shouldBeMod: true, shouldBeJudge: false }); + // IsModerator(4) + expect(result.users[0].userLevel).toBe(4); + }); + + it('shouldBeMod=false, shouldBeJudge=true → keeps only IsJudge bit', () => { + const state = makeServerState({ users: [makeUser({ name: 'Dan', userLevel: baseUserLevel })] }); + const result = serverReducer(state, { type: Types.ADJUST_MOD, userName: 'Dan', shouldBeMod: false, shouldBeJudge: true }); + // IsJudge(16) + expect(result.users[0].userLevel).toBe(16); + }); + + it('shouldBeMod=false, shouldBeJudge=false → clears both bits', () => { + const state = makeServerState({ users: [makeUser({ name: 'Dan', userLevel: baseUserLevel })] }); + const result = serverReducer(state, { type: Types.ADJUST_MOD, userName: 'Dan', shouldBeMod: false, shouldBeJudge: false }); + expect(result.users[0].userLevel).toBe(0); + }); + + it('non-matching users are left unchanged', () => { + const alice = makeUser({ name: 'Alice', userLevel: 7 }); + const state = makeServerState({ users: [alice, makeUser({ name: 'Dan', userLevel: baseUserLevel })] }); + const result = serverReducer(state, { type: Types.ADJUST_MOD, userName: 'Dan', shouldBeMod: false, shouldBeJudge: false }); + expect(result.users.find(u => u.name === 'Alice').userLevel).toBe(7); + }); +}); + +// ── Replays ─────────────────────────────────────────────────────────────────── + +describe('Replays', () => { + it('REPLAY_LIST → replaces replays list', () => { + const matchList = [makeReplayMatch({ gameId: 10 })]; + const state = makeServerState({ replays: [makeReplayMatch({ gameId: 99 })] }); + const result = serverReducer(state, { type: Types.REPLAY_LIST, matchList }); + expect(result.replays).toHaveLength(1); + expect(result.replays[0].gameId).toBe(10); + }); + + it('REPLAY_ADDED → appends matchInfo to replays', () => { + const existing = makeReplayMatch({ gameId: 1 }); + const added = makeReplayMatch({ gameId: 2 }); + const state = makeServerState({ replays: [existing] }); + const result = serverReducer(state, { type: Types.REPLAY_ADDED, matchInfo: added }); + expect(result.replays).toHaveLength(2); + expect(result.replays[1]).toBe(added); + }); + + it('REPLAY_MODIFY_MATCH → updates doNotHide for matching gameId', () => { + const state = makeServerState({ replays: [makeReplayMatch({ gameId: 5, doNotHide: false })] }); + const result = serverReducer(state, { type: Types.REPLAY_MODIFY_MATCH, gameId: 5, doNotHide: true }); + expect(result.replays[0].doNotHide).toBe(true); + }); + + it('REPLAY_MODIFY_MATCH → leaves non-matching replays unchanged', () => { + const r1 = makeReplayMatch({ gameId: 1, doNotHide: false }); + const r2 = makeReplayMatch({ gameId: 2, doNotHide: false }); + const state = makeServerState({ replays: [r1, r2] }); + const result = serverReducer(state, { type: Types.REPLAY_MODIFY_MATCH, gameId: 1, doNotHide: true }); + expect(result.replays[1].doNotHide).toBe(false); + }); + + it('REPLAY_DELETE_MATCH → removes replay by gameId', () => { + const state = makeServerState({ replays: [makeReplayMatch({ gameId: 5 }), makeReplayMatch({ gameId: 6 })] }); + const result = serverReducer(state, { type: Types.REPLAY_DELETE_MATCH, gameId: 5 }); + expect(result.replays).toHaveLength(1); + expect(result.replays[0].gameId).toBe(6); + }); +}); + +// ── Deck Storage ────────────────────────────────────────────────────────────── + +describe('Deck Storage', () => { + it('BACKEND_DECKS → sets backendDecks', () => { + const deckList = makeDeckList(); + const state = makeServerState(); + const result = serverReducer(state, { type: Types.BACKEND_DECKS, deckList }); + expect(result.backendDecks).toBe(deckList); + }); + + it('DECK_UPLOAD with null backendDecks → returns state unchanged', () => { + const state = makeServerState({ backendDecks: null }); + const result = serverReducer(state, { type: Types.DECK_UPLOAD, path: '', treeItem: makeDeckTreeItem() }); + expect(result).toBe(state); + }); + + it('DECK_UPLOAD with flat path → appends item to root', () => { + const state = makeServerState({ backendDecks: makeDeckList() }); + const item = makeDeckTreeItem({ name: 'deck.cod' }); + const result = serverReducer(state, { type: Types.DECK_UPLOAD, path: '', treeItem: item }); + expect(result.backendDecks.root.items).toHaveLength(1); + expect(result.backendDecks.root.items[0]).toBe(item); + }); + + it('DECK_UPLOAD with nested path → inserts into matching subfolder', () => { + const subfolder = { id: 0, name: 'myDecks', file: null, folder: { items: [] } }; + const state = makeServerState({ backendDecks: { root: { items: [subfolder] } } }); + const item = makeDeckTreeItem({ name: 'new.cod' }); + const result = serverReducer(state, { type: Types.DECK_UPLOAD, path: 'myDecks', treeItem: item }); + const folder = result.backendDecks.root.items.find(i => i.name === 'myDecks'); + expect(folder.folder.items).toHaveLength(1); + expect(folder.folder.items[0]).toBe(item); + }); + + it('DECK_UPLOAD with non-existent intermediate folder → creates folder and inserts', () => { + const state = makeServerState({ backendDecks: makeDeckList() }); + const item = makeDeckTreeItem({ name: 'deck.cod' }); + const result = serverReducer(state, { type: Types.DECK_UPLOAD, path: 'newFolder', treeItem: item }); + expect(result.backendDecks.root.items).toHaveLength(1); + expect(result.backendDecks.root.items[0].name).toBe('newFolder'); + expect(result.backendDecks.root.items[0].folder.items[0]).toBe(item); + }); + + it('DECK_DELETE with null backendDecks → returns state unchanged', () => { + const state = makeServerState({ backendDecks: null }); + const result = serverReducer(state, { type: Types.DECK_DELETE, deckId: 1 }); + expect(result).toBe(state); + }); + + it('DECK_DELETE → removes item by id from tree', () => { + const item = makeDeckTreeItem({ id: 7 }); + const state = makeServerState({ backendDecks: { root: { items: [item] } } }); + const result = serverReducer(state, { type: Types.DECK_DELETE, deckId: 7 }); + expect(result.backendDecks.root.items).toHaveLength(0); + }); + + it('DECK_DELETE → recursively removes item nested inside a subfolder', () => { + const nested = makeDeckTreeItem({ id: 9, name: 'nested.cod' }); + const subfolder = { id: 0, name: 'sub', file: null, folder: { items: [nested] } }; + const state = makeServerState({ backendDecks: { root: { items: [subfolder] } } }); + const result = serverReducer(state, { type: Types.DECK_DELETE, deckId: 9 }); + expect(result.backendDecks.root.items[0].folder.items).toHaveLength(0); + }); + + it('DECK_NEW_DIR with null backendDecks → returns state unchanged', () => { + const state = makeServerState({ backendDecks: null }); + const result = serverReducer(state, { type: Types.DECK_NEW_DIR, path: '', dirName: 'newDir' }); + expect(result).toBe(state); + }); + + it('DECK_NEW_DIR at root → appends folder to root items', () => { + const state = makeServerState({ backendDecks: makeDeckList() }); + const result = serverReducer(state, { type: Types.DECK_NEW_DIR, path: '', dirName: 'myDir' }); + expect(result.backendDecks.root.items).toHaveLength(1); + expect(result.backendDecks.root.items[0].name).toBe('myDir'); + expect(result.backendDecks.root.items[0].folder).toEqual({ items: [] }); + }); + + it('DECK_NEW_DIR nested → inserts folder inside matching subfolder', () => { + const subfolder = { id: 0, name: 'parent', file: null, folder: { items: [] } }; + const state = makeServerState({ backendDecks: { root: { items: [subfolder] } } }); + const result = serverReducer(state, { type: Types.DECK_NEW_DIR, path: 'parent', dirName: 'child' }); + const parent = result.backendDecks.root.items.find(i => i.name === 'parent'); + expect(parent.folder.items).toHaveLength(1); + expect(parent.folder.items[0].name).toBe('child'); + }); + + it('DECK_DEL_DIR with null backendDecks → returns state unchanged', () => { + const state = makeServerState({ backendDecks: null }); + const result = serverReducer(state, { type: Types.DECK_DEL_DIR, path: 'myDir' }); + expect(result).toBe(state); + }); + + it('DECK_DEL_DIR → removes folder from root by name', () => { + const subfolder = { id: 0, name: 'myDir', file: null, folder: { items: [] } }; + const state = makeServerState({ backendDecks: { root: { items: [subfolder] } } }); + const result = serverReducer(state, { type: Types.DECK_DEL_DIR, path: 'myDir' }); + expect(result.backendDecks.root.items).toHaveLength(0); + }); + + it('DECK_DEL_DIR → returns deck tree unchanged when path is empty', () => { + const subfolder = { id: 0, name: 'keep', file: null, folder: { items: [] } }; + const state = makeServerState({ backendDecks: { root: { items: [subfolder] } } }); + const result = serverReducer(state, { type: Types.DECK_DEL_DIR, path: '' }); + expect(result.backendDecks.root.items).toHaveLength(1); + }); + + it('DECK_DEL_DIR → recursively removes nested subfolder via multi-segment path', () => { + const child = { id: 0, name: 'child', file: null, folder: { items: [] } }; + const parent = { id: 0, name: 'parent', file: null, folder: { items: [child] } }; + const state = makeServerState({ backendDecks: { root: { items: [parent] } } }); + const result = serverReducer(state, { type: Types.DECK_DEL_DIR, path: 'parent/child' }); + expect(result.backendDecks.root.items[0].folder.items).toHaveLength(0); + }); +}); diff --git a/webclient/src/store/server/server.selectors.spec.ts b/webclient/src/store/server/server.selectors.spec.ts new file mode 100644 index 000000000..711513150 --- /dev/null +++ b/webclient/src/store/server/server.selectors.spec.ts @@ -0,0 +1,98 @@ +import { Selectors } from './server.selectors'; +import { ServerState } from './server.interfaces'; +import { + makeDeckList, + makeReplayMatch, + makeServerState, + makeUser, +} from './__mocks__/server-fixtures'; +import { StatusEnum } from 'types'; + +function rootState(server: ServerState) { + return { server }; +} + +describe('Selectors', () => { + it('getInitialized → returns initialized flag', () => { + const state = makeServerState({ initialized: true }); + expect(Selectors.getInitialized(rootState(state))).toBe(true); + }); + + it('getConnectOptions → returns connectOptions', () => { + const connectOptions = { host: 'localhost', port: '4747' }; + const state = makeServerState({ connectOptions }); + expect(Selectors.getConnectOptions(rootState(state))).toBe(connectOptions); + }); + + it('getMessage → returns info.message', () => { + const state = makeServerState({ info: { message: 'Welcome!', name: null, version: null } }); + expect(Selectors.getMessage(rootState(state))).toBe('Welcome!'); + }); + + it('getName → returns info.name', () => { + const state = makeServerState({ info: { message: null, name: 'Servatrice', version: null } }); + expect(Selectors.getName(rootState(state))).toBe('Servatrice'); + }); + + it('getVersion → returns info.version', () => { + const state = makeServerState({ info: { message: null, name: null, version: '2.9.0' } }); + expect(Selectors.getVersion(rootState(state))).toBe('2.9.0'); + }); + + it('getDescription → returns status.description', () => { + const state = makeServerState({ status: { state: StatusEnum.CONNECTED, description: 'ok' } }); + expect(Selectors.getDescription(rootState(state))).toBe('ok'); + }); + + it('getState → returns status.state', () => { + const state = makeServerState({ status: { state: StatusEnum.LOGGED_IN, description: null } }); + expect(Selectors.getState(rootState(state))).toBe(StatusEnum.LOGGED_IN); + }); + + it('getUser → returns user', () => { + const user = makeUser({ name: 'Alice' }); + const state = makeServerState({ user }); + expect(Selectors.getUser(rootState(state))).toBe(user); + }); + + it('getUsers → returns users array', () => { + const users = [makeUser(), makeUser({ name: 'Bob' })]; + const state = makeServerState({ users }); + expect(Selectors.getUsers(rootState(state))).toBe(users); + }); + + it('getLogs → returns logs object', () => { + const logs = { room: [], game: [], chat: [] }; + const state = makeServerState({ logs }); + expect(Selectors.getLogs(rootState(state))).toBe(logs); + }); + + it('getBuddyList → returns buddyList', () => { + const buddyList = [makeUser({ name: 'Carol' })]; + const state = makeServerState({ buddyList }); + expect(Selectors.getBuddyList(rootState(state))).toBe(buddyList); + }); + + it('getIgnoreList → returns ignoreList', () => { + const ignoreList = [makeUser({ name: 'Dave' })]; + const state = makeServerState({ ignoreList }); + expect(Selectors.getIgnoreList(rootState(state))).toBe(ignoreList); + }); + + it('getReplays → returns replays', () => { + const replays = [makeReplayMatch()]; + const state = makeServerState({ replays }); + expect(Selectors.getReplays(rootState(state))).toBe(replays); + }); + + it('getBackendDecks → returns backendDecks', () => { + const backendDecks = makeDeckList(); + const state = makeServerState({ backendDecks }); + expect(Selectors.getBackendDecks(rootState(state))).toBe(backendDecks); + }); + + it('getBackendDecks → returns null when not set', () => { + const state = makeServerState({ backendDecks: null }); + expect(Selectors.getBackendDecks(rootState(state))).toBeNull(); + }); +}); From c3ae4cffd6e9182f37920479c27e3d222e08b9a2 Mon Sep 17 00:00:00 2001 From: seavor Date: Sun, 12 Apr 2026 13:58:51 -0500 Subject: [PATCH 06/38] Fix various issues --- .../src/forms/RegisterForm/RegisterForm.tsx | 2 +- .../src/store/game/__mocks__/fixtures.ts | 1 + webclient/src/store/game/game.interfaces.ts | 1 + .../store/server/__mocks__/server-fixtures.ts | 1 + .../src/store/server/server.actions.spec.ts | 15 ++--- webclient/src/store/server/server.actions.ts | 10 +--- .../src/store/server/server.dispatch.spec.ts | 11 ++-- webclient/src/store/server/server.dispatch.ts | 8 +-- .../src/store/server/server.interfaces.ts | 3 +- .../src/store/server/server.reducer.spec.ts | 56 +++++++++++++++---- webclient/src/store/server/server.reducer.ts | 18 +++++- webclient/src/store/server/server.types.ts | 5 +- webclient/src/websocket/WebClient.ts | 2 + .../__mocks__/sessionCommandMocks.ts | 1 - .../src/websocket/commands/session/message.ts | 7 +-- .../session/sessionCommands-simple.spec.ts | 5 -- .../events/session/connectionClosed.ts | 6 +- .../persistence/SessionPersistence.spec.ts | 32 ++++++----- .../persistence/SessionPersistence.ts | 20 ++++--- .../services/ProtobufService.spec.ts | 27 +-------- .../src/websocket/services/ProtobufService.ts | 20 +------ 21 files changed, 130 insertions(+), 121 deletions(-) diff --git a/webclient/src/forms/RegisterForm/RegisterForm.tsx b/webclient/src/forms/RegisterForm/RegisterForm.tsx index f5f4d9174..086aec0df 100644 --- a/webclient/src/forms/RegisterForm/RegisterForm.tsx +++ b/webclient/src/forms/RegisterForm/RegisterForm.tsx @@ -38,7 +38,7 @@ const RegisterForm = ({ onSubmit }: RegisterFormProps) => { useReduxEffect(() => { openToast() - }, ServerTypes.REGISTRATION_SUCCES); + }, ServerTypes.REGISTRATION_SUCCESS); useReduxEffect(({ error }) => { setEmailError(error); diff --git a/webclient/src/store/game/__mocks__/fixtures.ts b/webclient/src/store/game/__mocks__/fixtures.ts index ca19cdadd..19df4d398 100644 --- a/webclient/src/store/game/__mocks__/fixtures.ts +++ b/webclient/src/store/game/__mocks__/fixtures.ts @@ -100,6 +100,7 @@ export function makeGameEntry(overrides: Partial = {}): GameEntry { localPlayerId: 1, spectator: false, judge: false, + resuming: false, started: false, activePlayerId: 0, activePhase: 0, diff --git a/webclient/src/store/game/game.interfaces.ts b/webclient/src/store/game/game.interfaces.ts index 2e5e28325..c7ee6749b 100644 --- a/webclient/src/store/game/game.interfaces.ts +++ b/webclient/src/store/game/game.interfaces.ts @@ -17,6 +17,7 @@ export interface GameEntry { localPlayerId: number; spectator: boolean; judge: boolean; + resuming: boolean; started: boolean; activePlayerId: number; activePhase: number; diff --git a/webclient/src/store/server/__mocks__/server-fixtures.ts b/webclient/src/store/server/__mocks__/server-fixtures.ts index 6ac04c121..404424f6d 100644 --- a/webclient/src/store/server/__mocks__/server-fixtures.ts +++ b/webclient/src/store/server/__mocks__/server-fixtures.ts @@ -149,6 +149,7 @@ export function makeServerState(overrides: Partial = {}): ServerSta adminNotes: {}, replays: [], backendDecks: null, + gamesOfUser: {}, ...overrides, }; } diff --git a/webclient/src/store/server/server.actions.spec.ts b/webclient/src/store/server/server.actions.spec.ts index 7d61717fb..4d4220854 100644 --- a/webclient/src/store/server/server.actions.spec.ts +++ b/webclient/src/store/server/server.actions.spec.ts @@ -120,7 +120,7 @@ describe('Actions', () => { }); it('registrationSuccess', () => { - expect(Actions.registrationSuccess()).toEqual({ type: Types.REGISTRATION_SUCCES }); + expect(Actions.registrationSuccess()).toEqual({ type: Types.REGISTRATION_SUCCESS }); }); it('registrationFailed', () => { @@ -203,14 +203,6 @@ describe('Actions', () => { expect(Actions.accountImageChanged(user)).toEqual({ type: Types.ACCOUNT_IMAGE_CHANGED, user }); }); - it('directMessageSent', () => { - expect(Actions.directMessageSent('Eve', 'hi')).toEqual({ - type: Types.DIRECT_MESSAGE_SENT, - userName: 'Eve', - message: 'hi', - }); - }); - it('getUserInfo', () => { const userInfo = makeUser({ name: 'Frank' }); expect(Actions.getUserInfo(userInfo)).toEqual({ type: Types.GET_USER_INFO, userInfo }); @@ -353,4 +345,9 @@ describe('Actions', () => { it('deckDelete', () => { expect(Actions.deckDelete(42)).toEqual({ type: Types.DECK_DELETE, deckId: 42 }); }); + + it('gamesOfUser', () => { + const games = [{ gameId: 1 }] as any; + expect(Actions.gamesOfUser('alice', games)).toEqual({ type: Types.GAMES_OF_USER, userName: 'alice', games }); + }); }); diff --git a/webclient/src/store/server/server.actions.ts b/webclient/src/store/server/server.actions.ts index 8af5adb75..e72021889 100644 --- a/webclient/src/store/server/server.actions.ts +++ b/webclient/src/store/server/server.actions.ts @@ -1,4 +1,4 @@ -import { DeckList, DeckStorageTreeItem, ReplayMatch, WebSocketConnectOptions } from 'types'; +import { DeckList, DeckStorageTreeItem, Game, ReplayMatch, WebSocketConnectOptions } from 'types'; import { Types } from './server.types'; export const Actions = { @@ -91,7 +91,7 @@ export const Actions = { type: Types.REGISTRATION_REQUIRES_EMAIL, }), registrationSuccess: () => ({ - type: Types.REGISTRATION_SUCCES, + type: Types.REGISTRATION_SUCCESS, }), registrationFailed: (error) => ({ type: Types.REGISTRATION_FAILED, @@ -157,11 +157,6 @@ export const Actions = { type: Types.ACCOUNT_IMAGE_CHANGED, user, }), - directMessageSent: (userName, message) => ({ - type: Types.DIRECT_MESSAGE_SENT, - userName, - message, - }), getUserInfo: (userInfo) => ({ type: Types.GET_USER_INFO, userInfo, @@ -239,4 +234,5 @@ export const Actions = { deckDelDir: (path: string) => ({ type: Types.DECK_DEL_DIR, path }), deckUpload: (path: string, treeItem: DeckStorageTreeItem) => ({ type: Types.DECK_UPLOAD, path, treeItem }), deckDelete: (deckId: number) => ({ type: Types.DECK_DELETE, deckId }), + gamesOfUser: (userName: string, games: Game[]) => ({ type: Types.GAMES_OF_USER, userName, games }), } diff --git a/webclient/src/store/server/server.dispatch.spec.ts b/webclient/src/store/server/server.dispatch.spec.ts index f6bab848d..56f26966f 100644 --- a/webclient/src/store/server/server.dispatch.spec.ts +++ b/webclient/src/store/server/server.dispatch.spec.ts @@ -250,11 +250,6 @@ describe('Dispatch', () => { expect(store.dispatch).toHaveBeenCalledWith(Actions.accountImageChanged(user)); }); - it('directMessageSent dispatches correctly', () => { - Dispatch.directMessageSent('Eve', 'hi'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.directMessageSent('Eve', 'hi')); - }); - it('getUserInfo dispatches correctly', () => { const userInfo = makeUser({ name: 'Frank' }); Dispatch.getUserInfo(userInfo); @@ -385,4 +380,10 @@ describe('Dispatch', () => { Dispatch.deckDelete(42); expect(store.dispatch).toHaveBeenCalledWith(Actions.deckDelete(42)); }); + + it('gamesOfUser dispatches correctly', () => { + const games = [{ gameId: 1 }] as any; + Dispatch.gamesOfUser('alice', games); + expect(store.dispatch).toHaveBeenCalledWith(Actions.gamesOfUser('alice', games)); + }); }); diff --git a/webclient/src/store/server/server.dispatch.ts b/webclient/src/store/server/server.dispatch.ts index 3d3b6d360..ac747f165 100644 --- a/webclient/src/store/server/server.dispatch.ts +++ b/webclient/src/store/server/server.dispatch.ts @@ -1,7 +1,7 @@ import { reset } from 'redux-form'; import { Actions } from './server.actions'; import { store } from 'store'; -import { DeckList, DeckStorageTreeItem, ReplayMatch, WebSocketConnectOptions } from 'types'; +import { DeckList, DeckStorageTreeItem, Game, ReplayMatch, WebSocketConnectOptions } from 'types'; export const Dispatch = { initialized: () => { @@ -141,9 +141,6 @@ export const Dispatch = { accountImageChanged: (user) => { store.dispatch(Actions.accountImageChanged(user)); }, - directMessageSent: (userName, message) => { - store.dispatch(Actions.directMessageSent(userName, message)); - }, getUserInfo: (userInfo) => { store.dispatch(Actions.getUserInfo(userInfo)); }, @@ -216,4 +213,7 @@ export const Dispatch = { deckDelete: (deckId: number) => { store.dispatch(Actions.deckDelete(deckId)); }, + gamesOfUser: (userName: string, games: Game[]) => { + store.dispatch(Actions.gamesOfUser(userName, games)); + }, } diff --git a/webclient/src/store/server/server.interfaces.ts b/webclient/src/store/server/server.interfaces.ts index 97e9d99da..b0932d0ea 100644 --- a/webclient/src/store/server/server.interfaces.ts +++ b/webclient/src/store/server/server.interfaces.ts @@ -1,5 +1,5 @@ import { - WarnHistoryItem, BanHistoryItem, DeckList, LogItem, ReplayMatch, SortBy, User, UserSortField, WebSocketConnectOptions, WarnListItem + WarnHistoryItem, BanHistoryItem, DeckList, Game, LogItem, ReplayMatch, SortBy, User, UserSortField, WebSocketConnectOptions, WarnListItem } from 'types'; import { NotifyUserData, ServerShutdownData, UserMessageData } from 'websocket/events/session/interfaces'; @@ -72,6 +72,7 @@ export interface ServerState { adminNotes: { [userName: string]: string }; replays: ReplayMatch[]; backendDecks: DeckList | null; + gamesOfUser: { [userName: string]: Game[] }; } export interface ServerStateStatus { diff --git a/webclient/src/store/server/server.reducer.spec.ts b/webclient/src/store/server/server.reducer.spec.ts index e6ba27680..3645c299e 100644 --- a/webclient/src/store/server/server.reducer.spec.ts +++ b/webclient/src/store/server/server.reducer.spec.ts @@ -332,31 +332,39 @@ describe('Moderation', () => { describe('ADJUST_MOD', () => { const baseUserLevel = UserLevelFlag.IsUser | UserLevelFlag.IsRegistered | UserLevelFlag.IsModerator | UserLevelFlag.IsJudge; - it('shouldBeMod=true, shouldBeJudge=true → keeps IsModerator and IsJudge bits', () => { + it('shouldBeMod=true, shouldBeJudge=true → sets both bits, preserves IsUser|IsRegistered', () => { const state = makeServerState({ users: [makeUser({ name: 'Dan', userLevel: baseUserLevel })] }); const result = serverReducer(state, { type: Types.ADJUST_MOD, userName: 'Dan', shouldBeMod: true, shouldBeJudge: true }); - // IsModerator(4) | IsJudge(16) - expect(result.users[0].userLevel).toBe(20); + // IsUser(1) | IsRegistered(2) | IsModerator(4) | IsJudge(16) = 23 + expect(result.users[0].userLevel).toBe(23); }); - it('shouldBeMod=true, shouldBeJudge=false → keeps only IsModerator bit', () => { + it('shouldBeMod=true, shouldBeJudge=false → sets IsModerator, clears IsJudge, preserves others', () => { const state = makeServerState({ users: [makeUser({ name: 'Dan', userLevel: baseUserLevel })] }); const result = serverReducer(state, { type: Types.ADJUST_MOD, userName: 'Dan', shouldBeMod: true, shouldBeJudge: false }); - // IsModerator(4) - expect(result.users[0].userLevel).toBe(4); + // IsUser(1) | IsRegistered(2) | IsModerator(4) = 7 + expect(result.users[0].userLevel).toBe(7); }); - it('shouldBeMod=false, shouldBeJudge=true → keeps only IsJudge bit', () => { + it('shouldBeMod=false, shouldBeJudge=true → clears IsModerator, sets IsJudge, preserves others', () => { const state = makeServerState({ users: [makeUser({ name: 'Dan', userLevel: baseUserLevel })] }); const result = serverReducer(state, { type: Types.ADJUST_MOD, userName: 'Dan', shouldBeMod: false, shouldBeJudge: true }); - // IsJudge(16) - expect(result.users[0].userLevel).toBe(16); + // IsUser(1) | IsRegistered(2) | IsJudge(16) = 19 + expect(result.users[0].userLevel).toBe(19); }); - it('shouldBeMod=false, shouldBeJudge=false → clears both bits', () => { + it('shouldBeMod=false, shouldBeJudge=false → clears both bits, preserves IsUser|IsRegistered', () => { const state = makeServerState({ users: [makeUser({ name: 'Dan', userLevel: baseUserLevel })] }); const result = serverReducer(state, { type: Types.ADJUST_MOD, userName: 'Dan', shouldBeMod: false, shouldBeJudge: false }); - expect(result.users[0].userLevel).toBe(0); + // IsUser(1) | IsRegistered(2) = 3 + expect(result.users[0].userLevel).toBe(3); + }); + + it('shouldBeMod=true on IsUser|IsRegistered only → produces 7, not 4', () => { + const state = makeServerState({ users: [makeUser({ name: 'Dan', userLevel: UserLevelFlag.IsUser | UserLevelFlag.IsRegistered })] }); + const result = serverReducer(state, { type: Types.ADJUST_MOD, userName: 'Dan', shouldBeMod: true, shouldBeJudge: false }); + // IsUser(1) | IsRegistered(2) | IsModerator(4) = 7 + expect(result.users[0].userLevel).toBe(7); }); it('non-matching users are left unchanged', () => { @@ -524,3 +532,29 @@ describe('Deck Storage', () => { expect(result.backendDecks.root.items[0].folder.items).toHaveLength(0); }); }); + +// ── GAMES_OF_USER ───────────────────────────────────────────────────────────── + +describe('GAMES_OF_USER', () => { + it('stores games keyed by userName', () => { + const games = [{ gameId: 5, roomId: 1 }] as any; + const state = makeServerState(); + const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', games }); + expect(result.gamesOfUser['alice']).toBe(games); + }); + + it('overwrites previous games for same user', () => { + const old = [{ gameId: 1 }] as any; + const fresh = [{ gameId: 2 }] as any; + const state = makeServerState({ gamesOfUser: { alice: old } }); + const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', games: fresh }); + expect(result.gamesOfUser['alice']).toBe(fresh); + }); + + it('does not affect other users\' entries', () => { + const bobGames = [{ gameId: 3 }] as any; + const state = makeServerState({ gamesOfUser: { bob: bobGames } }); + const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', games: [] }); + expect(result.gamesOfUser['bob']).toBe(bobGames); + }); +}); diff --git a/webclient/src/store/server/server.reducer.ts b/webclient/src/store/server/server.reducer.ts index a63418eeb..aaa525977 100644 --- a/webclient/src/store/server/server.reducer.ts +++ b/webclient/src/store/server/server.reducer.ts @@ -93,6 +93,7 @@ const initialState: ServerState = { adminNotes: {}, replays: [], backendDecks: null, + gamesOfUser: {}, }; export const serverReducer = (state = initialState, action: any) => { @@ -401,11 +402,12 @@ export const serverReducer = (state = initialState, action: any) => { if (user.name !== userName) { return user; } - const judgeFlag = shouldBeJudge ? UserLevelFlag.IsJudge : UserLevelFlag.IsNothing; - const modFlag = shouldBeMod ? UserLevelFlag.IsModerator : UserLevelFlag.IsNothing; + let newLevel = user.userLevel; + newLevel = shouldBeMod ? (newLevel | UserLevelFlag.IsModerator) : (newLevel & ~UserLevelFlag.IsModerator); + newLevel = shouldBeJudge ? (newLevel | UserLevelFlag.IsJudge) : (newLevel & ~UserLevelFlag.IsJudge); return { ...user, - userLevel: user.userLevel & (judgeFlag | modFlag) + userLevel: newLevel, } }) }; @@ -475,6 +477,16 @@ export const serverReducer = (state = initialState, action: any) => { }, }; } + case Types.GAMES_OF_USER: { + const { userName, games } = action; + return { + ...state, + gamesOfUser: { + ...state.gamesOfUser, + [userName]: games, + }, + }; + } default: return state; } diff --git a/webclient/src/store/server/server.types.ts b/webclient/src/store/server/server.types.ts index fb4249011..ab1dd3f1d 100644 --- a/webclient/src/store/server/server.types.ts +++ b/webclient/src/store/server/server.types.ts @@ -23,7 +23,7 @@ export const Types = { VIEW_LOGS: '[Server] View Logs', CLEAR_LOGS: '[Server] Clear Logs', REGISTRATION_REQUIRES_EMAIL: '[Server] Registration Requires Email', - REGISTRATION_SUCCES: '[Server] Registration Success', + REGISTRATION_SUCCESS: '[Server] Registration Success', REGISTRATION_FAILED: '[Server] Registration Failed', REGISTRATION_EMAIL_ERROR: '[Server] Registration Email Error', REGISTRATION_PASSWORD_ERROR: '[Server] Registration Password Error', @@ -42,7 +42,6 @@ export const Types = { ACCOUNT_PASSWORD_CHANGE: '[Server] Account Password Change', ACCOUNT_EDIT_CHANGED: '[Server] Account Edit Changed', ACCOUNT_IMAGE_CHANGED: '[Server] Account Image Changed', - DIRECT_MESSAGE_SENT: '[Server] Direct Message Sent', GET_USER_INFO: '[Server] Get User Info', NOTIFY_USER: '[Server] Notify User', SERVER_SHUTDOWN: '[Server] Server Shutdown', @@ -69,4 +68,6 @@ export const Types = { DECK_DEL_DIR: '[Server] Deck Del Dir', DECK_UPLOAD: '[Server] Deck Upload', DECK_DELETE: '[Server] Deck Delete', + // User games + GAMES_OF_USER: '[Server] Games Of User', }; diff --git a/webclient/src/websocket/WebClient.ts b/webclient/src/websocket/WebClient.ts index 1dae24d0c..4a471b0e5 100644 --- a/webclient/src/websocket/WebClient.ts +++ b/webclient/src/websocket/WebClient.ts @@ -3,6 +3,7 @@ import { StatusEnum, WebSocketConnectOptions } from 'types'; import { ProtobufService } from './services/ProtobufService'; import { WebSocketService } from './services/WebSocketService'; +import { GameDispatch } from 'store'; import { RoomPersistence, SessionPersistence } from './persistence'; export class WebClient { @@ -79,6 +80,7 @@ export class WebClient { } private clearStores() { + GameDispatch.clearStore(); RoomPersistence.clearStore(); SessionPersistence.clearStore(); } diff --git a/webclient/src/websocket/__mocks__/sessionCommandMocks.ts b/webclient/src/websocket/__mocks__/sessionCommandMocks.ts index 7b28335b7..71f84953c 100644 --- a/webclient/src/websocket/__mocks__/sessionCommandMocks.ts +++ b/webclient/src/websocket/__mocks__/sessionCommandMocks.ts @@ -79,7 +79,6 @@ export function makeSessionPersistenceMock() { accountActivationSuccess: jest.fn(), accountActivationFailed: jest.fn(), updateStatus: jest.fn(), - directMessageSent: jest.fn(), addToList: jest.fn(), removeFromList: jest.fn(), deleteServerDeck: jest.fn(), diff --git a/webclient/src/websocket/commands/session/message.ts b/webclient/src/websocket/commands/session/message.ts index 075fc3c4b..b6bde9cac 100644 --- a/webclient/src/websocket/commands/session/message.ts +++ b/webclient/src/websocket/commands/session/message.ts @@ -1,10 +1,5 @@ import { BackendService } from '../../services/BackendService'; -import { SessionPersistence } from '../../persistence'; export function message(userName: string, message: string): void { - BackendService.sendSessionCommand('Command_Message', { userName, message }, { - onSuccess: () => { - SessionPersistence.directMessageSent(userName, message); - }, - }); + BackendService.sendSessionCommand('Command_Message', { userName, message }, {}); } diff --git a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts index f6af910db..bd9a6830b 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts @@ -302,11 +302,6 @@ describe('message', () => { ); }); - it('calls directMessageSent on success', () => { - message('bob', 'hi'); - invokeOnSuccess(); - expect(SessionPersistence.directMessageSent).toHaveBeenCalledWith('bob', 'hi'); - }); }); describe('ping', () => { diff --git a/webclient/src/websocket/events/session/connectionClosed.ts b/webclient/src/websocket/events/session/connectionClosed.ts index 227113059..40f75c013 100644 --- a/webclient/src/websocket/events/session/connectionClosed.ts +++ b/webclient/src/websocket/events/session/connectionClosed.ts @@ -3,7 +3,7 @@ import { ProtoController } from '../../services/ProtoController'; import { updateStatus } from '../../commands/session'; import { ConnectionClosedData } from './interfaces'; -export function connectionClosed({ reason, reasonStr }: ConnectionClosedData): void { +export function connectionClosed({ reason, reasonStr, endTime }: ConnectionClosedData): void { let message: string; // @TODO (5) @@ -19,7 +19,9 @@ export function connectionClosed({ reason, reasonStr }: ConnectionClosedData): v message = 'There are too many concurrent connections from your address'; break; case CloseReason.BANNED: - message = 'You are banned'; + message = endTime > 0 + ? `You are banned until ${new Date(endTime * 1000).toLocaleString()}` + : 'You are banned'; break; case CloseReason.DEMOTED: message = 'You were demoted'; diff --git a/webclient/src/websocket/persistence/SessionPersistence.spec.ts b/webclient/src/websocket/persistence/SessionPersistence.spec.ts index ff83f7684..7a35c58a9 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.spec.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.spec.ts @@ -37,7 +37,6 @@ jest.mock('store', () => ({ accountPasswordChange: jest.fn(), accountEditChanged: jest.fn(), accountImageChanged: jest.fn(), - directMessageSent: jest.fn(), getUserInfo: jest.fn(), notifyUser: jest.fn(), serverShutdown: jest.fn(), @@ -53,6 +52,7 @@ jest.mock('store', () => ({ replayAdded: jest.fn(), replayModifyMatch: jest.fn(), replayDeleteMatch: jest.fn(), + gamesOfUser: jest.fn(), }, GameDispatch: { gameJoined: jest.fn(), @@ -68,6 +68,7 @@ jest.mock('../utils/NormalizeService', () => ({ __esModule: true, default: { normalizeBannedUserError: jest.fn((r: string, t: number) => `banned:${r}:${t}`), + normalizeGameObject: jest.fn(), }, })); @@ -291,28 +292,33 @@ describe('SessionPersistence', () => { expect(ServerDispatch.accountImageChanged).toHaveBeenCalledWith({ avatarBmp: buf }); }); - it('directMessageSent passes userName and message', () => { - SessionPersistence.directMessageSent('bob', 'hi'); - expect(ServerDispatch.directMessageSent).toHaveBeenCalledWith('bob', 'hi'); - }); - it('getUserInfo passes userInfo', () => { const user = { name: 'u' } as any; SessionPersistence.getUserInfo(user); expect(ServerDispatch.getUserInfo).toHaveBeenCalledWith(user); }); - it('getGamesOfUser logs to console', () => { - const spy = jest.spyOn(console, 'log').mockImplementation(() => {}); - SessionPersistence.getGamesOfUser('user1', {}); - expect(spy).toHaveBeenCalled(); - spy.mockRestore(); + it('getGamesOfUser normalizes game list and dispatches gamesOfUser', () => { + const gt = { gameTypeId: 1, description: 'Standard' }; + const room = { gametypeList: [gt] }; + const game = { gameId: 5, roomId: 1, gameTypes: [1], description: 'My Game', started: false }; + SessionPersistence.getGamesOfUser('alice', { roomList: [room], gameList: [game] }); + expect(NormalizeService.normalizeGameObject).toHaveBeenCalledWith(game, { 1: 'Standard' }); + expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', [game]); + }); + + it('getGamesOfUser handles empty response', () => { + SessionPersistence.getGamesOfUser('alice', {}); + expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', []); }); it('gameJoined dispatches via GameDispatch.gameJoined', () => { const gameInfo = { gameId: 10, roomId: 2, description: 'test', started: false }; - SessionPersistence.gameJoined({ gameInfo, hostId: 3, playerId: 4, spectator: false, judge: false } as any); - expect(GameDispatch.gameJoined).toHaveBeenCalledWith(10, expect.objectContaining({ gameId: 10, hostId: 3, localPlayerId: 4 })); + SessionPersistence.gameJoined({ gameInfo, hostId: 3, playerId: 4, spectator: false, judge: false, resuming: true } as any); + expect(GameDispatch.gameJoined).toHaveBeenCalledWith( + 10, + expect.objectContaining({ gameId: 10, hostId: 3, localPlayerId: 4, resuming: true }) + ); }); it('notifyUser passes notification', () => { diff --git a/webclient/src/websocket/persistence/SessionPersistence.ts b/webclient/src/websocket/persistence/SessionPersistence.ts index 960a844f6..10fcc0539 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.ts @@ -167,21 +167,26 @@ export class SessionPersistence { ServerDispatch.accountImageChanged({ avatarBmp }); } - static directMessageSent(userName: string, message: string): void { - ServerDispatch.directMessageSent(userName, message); - } - static getUserInfo(userInfo: User) { ServerDispatch.getUserInfo(userInfo); } static getGamesOfUser(userName: string, response: any): void { - // Response_GetGamesOfUser contains a gameList field — log for now until game layer is complete - console.log('getGamesOfUser', userName, response); + const gametypeMap: Record = {}; + (response.roomList || []).forEach((room: any) => { + (room.gametypeList || []).forEach((gt: any) => { + gametypeMap[gt.gameTypeId] = gt.description; + }); + }); + const games = (response.gameList || []).map((game: any) => { + NormalizeService.normalizeGameObject(game, gametypeMap); + return game; + }); + ServerDispatch.gamesOfUser(userName, games); } static gameJoined(gameJoinedData: GameJoinedData): void { - const { gameInfo, hostId, playerId, spectator, judge } = gameJoinedData; + const { gameInfo, hostId, playerId, spectator, judge, resuming } = gameJoinedData; const gameEntry: GameEntry = { gameId: gameInfo.gameId, roomId: gameInfo.roomId, @@ -190,6 +195,7 @@ export class SessionPersistence { localPlayerId: playerId, spectator, judge, + resuming, started: gameInfo.started, activePlayerId: -1, activePhase: -1, diff --git a/webclient/src/websocket/services/ProtobufService.spec.ts b/webclient/src/websocket/services/ProtobufService.spec.ts index 618f643a5..c03173020 100644 --- a/webclient/src/websocket/services/ProtobufService.spec.ts +++ b/webclient/src/websocket/services/ProtobufService.spec.ts @@ -10,7 +10,6 @@ jest.mock('../commands/session', () => ({ })); jest.mock('../events', () => ({ - CommonEvents: { '.Event_Common.ext': jest.fn() }, GameEvents: { '.Event_Game.ext': jest.fn() }, RoomEvents: { '.Event_Room.ext': jest.fn() }, SessionEvents: { '.Event_Session.ext': jest.fn() }, @@ -21,7 +20,7 @@ jest.mock('../WebClient'); import { ProtobufService } from './ProtobufService'; import { ProtoController } from './ProtoController'; import { ping as sessionPing } from '../commands/session'; -import { GameEvents, CommonEvents } from '../events'; +import { GameEvents } from '../events'; let mockSocket: any; let mockWebClient: any; @@ -321,17 +320,6 @@ describe('ProtobufService', () => { }); }); - describe('processCommonEvent', () => { - it('delegates to processEvent with CommonEvents', () => { - const service = new ProtobufService(mockWebClient); - const processEvent = jest.spyOn(service as any, 'processEvent'); - const response = { '.Event_Common.ext': { data: 1 } }; - const raw = { extra: true }; - (service as any).processCommonEvent(response, raw); - expect(processEvent).toHaveBeenCalledWith(response, CommonEvents, raw); - }); - }); - describe('processGameEvent', () => { it('returns early when container has no eventList', () => { const service = new ProtobufService(mockWebClient); @@ -354,19 +342,6 @@ describe('ProtobufService', () => { expect(gameEventHandler).toHaveBeenCalledWith(payload, expect.objectContaining({ gameId: 42, playerId: 5 })); }); - it('falls back to CommonEvents handler when no GameEvents key matches', () => { - const service = new ProtobufService(mockWebClient); - const commonEventHandler = (CommonEvents as any)['.Event_Common.ext'] as jest.Mock; - const payload = { commonData: 2 }; - (service as any).processGameEvent({ - gameId: 7, - context: null, - secondsElapsed: 0, - forcedByJudge: 0, - eventList: [{ '.Event_Common.ext': payload, playerId: 3 }], - }, {}); - expect(commonEventHandler).toHaveBeenCalledWith(payload, expect.objectContaining({ gameId: 7, playerId: 3 })); - }); }); describe('processEvent', () => { diff --git a/webclient/src/websocket/services/ProtobufService.ts b/webclient/src/websocket/services/ProtobufService.ts index 626e65913..1be84d81d 100644 --- a/webclient/src/websocket/services/ProtobufService.ts +++ b/webclient/src/websocket/services/ProtobufService.ts @@ -1,4 +1,4 @@ -import { CommonEvents, GameEvents, RoomEvents, SessionEvents } from '../events'; +import { GameEvents, RoomEvents, SessionEvents } from '../events'; import { WebClient } from '../WebClient'; import { SessionCommands } from 'websocket'; import { ProtoController } from './ProtoController'; @@ -119,10 +119,6 @@ export class ProtobufService { } } - private processCommonEvent(response: any, raw: any) { - this.processEvent(response, CommonEvents, raw); - } - private processRoomEvent(response: any, raw: any) { this.processEvent(response, RoomEvents, raw); } @@ -147,25 +143,13 @@ export class ProtobufService { forcedByJudge: forcedByJudge ?? 0, }; - // Try registered game event handlers first, then common event handlers - let handled = false; for (const key of Object.keys(GameEvents)) { const payload = event[key]; if (payload !== undefined && payload !== null) { (GameEvents[key] as Function)(payload, meta); - handled = true; break; } } - if (!handled) { - for (const key of Object.keys(CommonEvents)) { - const payload = event[key]; - if (payload !== undefined && payload !== null) { - (CommonEvents[key] as Function)(payload, meta); - break; - } - } - } } } @@ -173,7 +157,7 @@ export class ProtobufService { for (const event in events) { const payload = response[event]; - if (payload) { + if (payload !== undefined && payload !== null) { events[event](payload, raw); return; } From 559a3ff1f47a7fe71f9f318c7cf00c28b2f83df7 Mon Sep 17 00:00:00 2001 From: seavor Date: Sun, 12 Apr 2026 15:21:29 -0500 Subject: [PATCH 07/38] harden implementations --- webclient/src/containers/Login/Login.tsx | 12 +++-- .../store/server/__mocks__/server-fixtures.ts | 1 - .../src/store/server/server.interfaces.ts | 3 +- .../src/store/server/server.reducer.spec.ts | 16 +++--- webclient/src/store/server/server.reducer.ts | 13 +---- .../src/store/server/server.selectors.spec.ts | 6 --- .../src/store/server/server.selectors.ts | 1 - webclient/src/types/game.ts | 15 +++++- .../commands/game/gameCommands.spec.ts | 17 +++++++ .../src/websocket/commands/game/index.ts | 2 + .../src/websocket/commands/game/judge.ts | 8 +++ .../src/websocket/commands/game/unconcede.ts | 5 ++ .../src/websocket/commands/session/index.ts | 2 + .../src/websocket/commands/session/login.ts | 3 +- .../commands/session/replayGetCode.ts | 10 ++++ .../commands/session/replaySubmitCode.ts | 12 +++++ .../session/sessionCommands-complex.spec.ts | 16 ++++++ .../session/sessionCommands-simple.spec.ts | 49 +++++++++++++++++++ .../events/session/connectionClosed.ts | 2 +- .../events/session/sessionEvents.spec.ts | 33 +++++++++++++ .../persistence/RoomPersistence.spec.ts | 10 ++++ .../websocket/persistence/RoomPersistence.ts | 4 ++ .../services/ProtobufService.spec.ts | 19 +++++++ .../websocket/utils/sanitizeHtml.util.spec.ts | 17 +++++++ .../src/websocket/utils/sanitizeHtml.util.ts | 1 + 25 files changed, 240 insertions(+), 37 deletions(-) create mode 100644 webclient/src/websocket/commands/game/judge.ts create mode 100644 webclient/src/websocket/commands/game/unconcede.ts create mode 100644 webclient/src/websocket/commands/session/replayGetCode.ts create mode 100644 webclient/src/websocket/commands/session/replaySubmitCode.ts diff --git a/webclient/src/containers/Login/Login.tsx b/webclient/src/containers/Login/Login.tsx index fe008375c..965078759 100644 --- a/webclient/src/containers/Login/Login.tsx +++ b/webclient/src/containers/Login/Login.tsx @@ -64,11 +64,13 @@ const Root = styled('div')(({ theme }) => ({ } })); -const Login = ({ state, description, connectOptions }: LoginProps) => { +const Login = ({ state, description }: LoginProps) => { const { t } = useTranslation(); const isConnected = AuthenticationService.isConnected(state); + const [pendingActivationOptions, setPendingActivationOptions] = useState(null); + const [rememberLogin, setRememberLogin] = useState(null); const [dialogState, setDialogState] = useState({ passwordResetRequestDialog: false, @@ -97,9 +99,11 @@ const Login = ({ state, description, connectOptions }: LoginProps) => { useReduxEffect(() => { accountActivatedToast.openToast() closeActivateAccountDialog(); + setPendingActivationOptions(null); }, ServerTypes.ACCOUNT_ACTIVATION_SUCCESS, []); - useReduxEffect(() => { + useReduxEffect(({ options }) => { + setPendingActivationOptions(options); closeRegistrationDialog(); openActivateAccountDialog(); }, ServerTypes.ACCOUNT_AWAITING_ACTIVATION, []); @@ -161,7 +165,7 @@ const Login = ({ state, description, connectOptions }: LoginProps) => { const handleAccountActivationDialogSubmit = ({ token }) => { AuthenticationService.activateAccount({ - ...connectOptions, + ...pendingActivationOptions, token, }); }; @@ -348,13 +352,11 @@ const Login = ({ state, description, connectOptions }: LoginProps) => { interface LoginProps { state: number; description: string; - connectOptions: WebSocketConnectOptions; } const mapStateToProps = state => ({ state: ServerSelectors.getState(state), description: ServerSelectors.getDescription(state), - connectOptions: ServerSelectors.getConnectOptions(state), }); export default connect(mapStateToProps)(Login); diff --git a/webclient/src/store/server/__mocks__/server-fixtures.ts b/webclient/src/store/server/__mocks__/server-fixtures.ts index 404424f6d..9a7580365 100644 --- a/webclient/src/store/server/__mocks__/server-fixtures.ts +++ b/webclient/src/store/server/__mocks__/server-fixtures.ts @@ -136,7 +136,6 @@ export function makeServerState(overrides: Partial = {}): ServerSta field: UserSortField.NAME, order: SortDirection.ASC, }, - connectOptions: {}, messages: {}, userInfo: {}, notifications: [], diff --git a/webclient/src/store/server/server.interfaces.ts b/webclient/src/store/server/server.interfaces.ts index b0932d0ea..4a9d63509 100644 --- a/webclient/src/store/server/server.interfaces.ts +++ b/webclient/src/store/server/server.interfaces.ts @@ -1,5 +1,5 @@ import { - WarnHistoryItem, BanHistoryItem, DeckList, Game, LogItem, ReplayMatch, SortBy, User, UserSortField, WebSocketConnectOptions, WarnListItem + WarnHistoryItem, BanHistoryItem, DeckList, Game, LogItem, ReplayMatch, SortBy, User, UserSortField, WarnListItem } from 'types'; import { NotifyUserData, ServerShutdownData, UserMessageData } from 'websocket/events/session/interfaces'; @@ -51,7 +51,6 @@ export interface ServerState { user: User; users: User[]; sortUsersBy: ServerStateSortUsersBy; - connectOptions: WebSocketConnectOptions; messages: { [userName: string]: UserMessageData[]; } diff --git a/webclient/src/store/server/server.reducer.spec.ts b/webclient/src/store/server/server.reducer.spec.ts index 3645c299e..c501fbe0a 100644 --- a/webclient/src/store/server/server.reducer.spec.ts +++ b/webclient/src/store/server/server.reducer.spec.ts @@ -51,23 +51,23 @@ describe('Initialisation', () => { // ── Account & Connection ───────────────────────────────────────────────────── describe('Account & Connection', () => { - it('ACCOUNT_AWAITING_ACTIVATION → sets connectOptions from action.options', () => { + it('ACCOUNT_AWAITING_ACTIVATION → returns state unchanged', () => { const options = makeConnectOptions(); const state = makeServerState(); const result = serverReducer(state, { type: Types.ACCOUNT_AWAITING_ACTIVATION, options }); - expect(result.connectOptions).toEqual(options); + expect(result).toBe(state); }); - it('ACCOUNT_ACTIVATION_SUCCESS → clears connectOptions to {}', () => { - const state = makeServerState({ connectOptions: makeConnectOptions() }); + it('ACCOUNT_ACTIVATION_SUCCESS → returns state unchanged', () => { + const state = makeServerState(); const result = serverReducer(state, { type: Types.ACCOUNT_ACTIVATION_SUCCESS }); - expect(result.connectOptions).toEqual({}); + expect(result).toBe(state); }); - it('ACCOUNT_ACTIVATION_FAILED → clears connectOptions to {}', () => { - const state = makeServerState({ connectOptions: makeConnectOptions() }); + it('ACCOUNT_ACTIVATION_FAILED → returns state unchanged', () => { + const state = makeServerState(); const result = serverReducer(state, { type: Types.ACCOUNT_ACTIVATION_FAILED }); - expect(result.connectOptions).toEqual({}); + expect(result).toBe(state); }); }); diff --git a/webclient/src/store/server/server.reducer.ts b/webclient/src/store/server/server.reducer.ts index aaa525977..f5ad57e7c 100644 --- a/webclient/src/store/server/server.reducer.ts +++ b/webclient/src/store/server/server.reducer.ts @@ -80,7 +80,6 @@ const initialState: ServerState = { field: UserSortField.NAME, order: SortDirection.ASC }, - connectOptions: {}, messages: {}, userInfo: {}, notifications: [], @@ -105,19 +104,11 @@ export const serverReducer = (state = initialState, action: any) => { } } case Types.ACCOUNT_AWAITING_ACTIVATION: { - return { - ...state, - connectOptions: { - ...action.options - } - } + return state; } case Types.ACCOUNT_ACTIVATION_FAILED: case Types.ACCOUNT_ACTIVATION_SUCCESS: { - return { - ...state, - connectOptions: {} - } + return state; } case Types.CLEAR_STORE: { return { diff --git a/webclient/src/store/server/server.selectors.spec.ts b/webclient/src/store/server/server.selectors.spec.ts index 711513150..9adcdbaca 100644 --- a/webclient/src/store/server/server.selectors.spec.ts +++ b/webclient/src/store/server/server.selectors.spec.ts @@ -18,12 +18,6 @@ describe('Selectors', () => { expect(Selectors.getInitialized(rootState(state))).toBe(true); }); - it('getConnectOptions → returns connectOptions', () => { - const connectOptions = { host: 'localhost', port: '4747' }; - const state = makeServerState({ connectOptions }); - expect(Selectors.getConnectOptions(rootState(state))).toBe(connectOptions); - }); - it('getMessage → returns info.message', () => { const state = makeServerState({ info: { message: 'Welcome!', name: null, version: null } }); expect(Selectors.getMessage(rootState(state))).toBe('Welcome!'); diff --git a/webclient/src/store/server/server.selectors.ts b/webclient/src/store/server/server.selectors.ts index fa9f82297..4506699cf 100644 --- a/webclient/src/store/server/server.selectors.ts +++ b/webclient/src/store/server/server.selectors.ts @@ -6,7 +6,6 @@ interface State { export const Selectors = { getInitialized: ({ server }: State) => server.initialized, - getConnectOptions: ({ server }: State) => server.connectOptions, getMessage: ({ server }: State) => server.info.message, getName: ({ server }: State) => server.info.name, getVersion: ({ server }: State) => server.info.version, diff --git a/webclient/src/types/game.ts b/webclient/src/types/game.ts index 4961dc92c..74ad18381 100644 --- a/webclient/src/types/game.ts +++ b/webclient/src/types/game.ts @@ -305,11 +305,24 @@ export interface ReverseTurnData { * Contains per-container metadata from GameEventContainer. * Not stored in Redux — transient routing metadata only. */ +export interface GameEventContext { + '.Context_ReadyStart.ext'?: {}; + '.Context_Concede.ext'?: {}; + '.Context_DeckSelect.ext'?: {}; + '.Context_UndoDraw.ext'?: {}; + '.Context_MoveCard.ext'?: {}; + '.Context_Mulligan.ext'?: {}; + '.Context_PingChanged.ext'?: {}; + '.Context_ConnectionStateChanged.ext'?: {}; + '.Context_SetSideboardLock.ext'?: {}; + '.Context_Unconcede.ext'?: {}; +} + export interface GameEventMeta { gameId: number; playerId: number; /** Raw protobuf GameEventContext object. Not stored in Redux. */ - context: any; + context: GameEventContext | null; secondsElapsed: number; /** Proto type is uint32. Non-zero means the action was forced by a judge. */ forcedByJudge: number; diff --git a/webclient/src/websocket/commands/game/gameCommands.spec.ts b/webclient/src/websocket/commands/game/gameCommands.spec.ts index 6d802136d..8d9597be9 100644 --- a/webclient/src/websocket/commands/game/gameCommands.spec.ts +++ b/webclient/src/websocket/commands/game/gameCommands.spec.ts @@ -30,6 +30,8 @@ import { setSideboardLock } from './setSideboardLock'; import { setSideboardPlan } from './setSideboardPlan'; import { shuffle } from './shuffle'; import { undoDraw } from './undoDraw'; +import { unconcede } from './unconcede'; +import { judge } from './judge'; jest.mock('../../services/BackendService', () => ({ BackendService: { sendGameCommand: jest.fn() }, @@ -197,4 +199,19 @@ describe('Game commands — delegate to BackendService.sendGameCommand', () => { undoDraw(gameId); expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_UndoDraw', {}); }); + + it('unconcede sends Command_Unconcede with empty object', () => { + unconcede(gameId); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_Unconcede', {}); + }); + + it('judge sends Command_Judge with targetId and wrapped gameCommand array', () => { + const targetId = 3; + const innerGameCommand = { '.Command_DrawCards.ext': { numberOfCards: 2 } }; + judge(gameId, targetId, innerGameCommand); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_Judge', { + targetId, + gameCommand: [innerGameCommand], + }); + }); }); diff --git a/webclient/src/websocket/commands/game/index.ts b/webclient/src/websocket/commands/game/index.ts index 8978c938c..e91555f30 100644 --- a/webclient/src/websocket/commands/game/index.ts +++ b/webclient/src/websocket/commands/game/index.ts @@ -3,6 +3,8 @@ export { kickFromGame } from './kickFromGame'; export { gameSay } from './gameSay'; export { readyStart } from './readyStart'; export { concede } from './concede'; +export { unconcede } from './unconcede'; +export { judge } from './judge'; export { nextTurn } from './nextTurn'; export { setActivePhase } from './setActivePhase'; export { reverseTurn } from './reverseTurn'; diff --git a/webclient/src/websocket/commands/game/judge.ts b/webclient/src/websocket/commands/game/judge.ts new file mode 100644 index 000000000..1b9f22d14 --- /dev/null +++ b/webclient/src/websocket/commands/game/judge.ts @@ -0,0 +1,8 @@ +import { BackendService } from '../../services/BackendService'; + +export function judge(gameId: number, targetId: number, innerGameCommand: any): void { + BackendService.sendGameCommand(gameId, 'Command_Judge', { + targetId, + gameCommand: [innerGameCommand], + }); +} diff --git a/webclient/src/websocket/commands/game/unconcede.ts b/webclient/src/websocket/commands/game/unconcede.ts new file mode 100644 index 000000000..b724aee03 --- /dev/null +++ b/webclient/src/websocket/commands/game/unconcede.ts @@ -0,0 +1,5 @@ +import { BackendService } from '../../services/BackendService'; + +export function unconcede(gameId: number): void { + BackendService.sendGameCommand(gameId, 'Command_Unconcede', {}); +} diff --git a/webclient/src/websocket/commands/session/index.ts b/webclient/src/websocket/commands/session/index.ts index 74d0d062c..a6d3d884b 100644 --- a/webclient/src/websocket/commands/session/index.ts +++ b/webclient/src/websocket/commands/session/index.ts @@ -24,7 +24,9 @@ export * from './ping'; export * from './register'; export * from './removeFromList'; export * from './replayDeleteMatch'; +export * from './replayGetCode'; export * from './replayList'; export * from './replayModifyMatch'; +export * from './replaySubmitCode'; export * from './requestPasswordSalt'; export * from './updateStatus'; diff --git a/webclient/src/websocket/commands/session/login.ts b/webclient/src/websocket/commands/session/login.ts index 6f3ec5ef5..c98128917 100644 --- a/webclient/src/websocket/commands/session/login.ts +++ b/webclient/src/websocket/commands/session/login.ts @@ -44,7 +44,8 @@ export function login(options: WebSocketConnectOptions, passwordSalt?: string): SessionPersistence.updateBuddyList(buddyList); SessionPersistence.updateIgnoreList(ignoreList); SessionPersistence.updateUser(userInfo); - SessionPersistence.loginSuccessful(loginConfig); + const { password: _password, ...safeConfig } = loginConfig; + SessionPersistence.loginSuccessful(safeConfig); listUsers(); listRooms(); diff --git a/webclient/src/websocket/commands/session/replayGetCode.ts b/webclient/src/websocket/commands/session/replayGetCode.ts new file mode 100644 index 000000000..1e0557d1c --- /dev/null +++ b/webclient/src/websocket/commands/session/replayGetCode.ts @@ -0,0 +1,10 @@ +import { BackendService } from '../../services/BackendService'; + +export function replayGetCode(gameId: number, onCodeReceived: (code: string) => void): void { + BackendService.sendSessionCommand('Command_ReplayGetCode', { gameId }, { + responseName: 'Response_ReplayGetCode', + onSuccess: (response) => { + onCodeReceived(response.replayCode); + }, + }); +} diff --git a/webclient/src/websocket/commands/session/replaySubmitCode.ts b/webclient/src/websocket/commands/session/replaySubmitCode.ts new file mode 100644 index 000000000..ad1896e57 --- /dev/null +++ b/webclient/src/websocket/commands/session/replaySubmitCode.ts @@ -0,0 +1,12 @@ +import { BackendService } from '../../services/BackendService'; + +export function replaySubmitCode( + replayCode: string, + onSuccess?: () => void, + onError?: (responseCode: number) => void, +): void { + BackendService.sendSessionCommand('Command_ReplaySubmitCode', { replayCode }, { + onSuccess, + onError, + }); +} diff --git a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts index edcbf6983..1d61c8a36 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts @@ -163,6 +163,22 @@ describe('login', () => { expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.LOGGED_IN, 'Logged in.'); }); + it('onSuccess does NOT pass plaintext password to loginSuccessful', () => { + login({ userName: 'alice', password: 'secret' } as any); + const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } }; + invokeOnSuccess(loginResp, { responseCode: 0, '.Response_Login.ext': loginResp }); + const calledWith = (SessionPersistence.loginSuccessful as jest.Mock).mock.calls[0][0]; + expect(calledWith).not.toHaveProperty('password'); + }); + + it('onSuccess passes hashedPassword to loginSuccessful when salt is used', () => { + login({ userName: 'alice', password: 'pw' } as any, 'salt'); + const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } }; + invokeOnSuccess(loginResp, { responseCode: 0, '.Response_Login.ext': loginResp }); + const calledWith = (SessionPersistence.loginSuccessful as jest.Mock).mock.calls[0][0]; + expect(calledWith).toHaveProperty('hashedPassword', 'hashed_pw'); + }); + it('onResponseCode RespClientUpdateRequired calls onLoginError', () => { login({ userName: 'alice', password: 'pw' } as any); invokeResponseCode(1); diff --git a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts index bd9a6830b..723bdbee9 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts @@ -423,3 +423,52 @@ describe('removeFromList / removeFromBuddyList / removeFromIgnoreList', () => { expect(SessionPersistence.removeFromList).toHaveBeenCalledWith('buddy', 'alice'); }); }); + +describe('replayGetCode', () => { + const { replayGetCode } = jest.requireActual('./replayGetCode'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_ReplayGetCode with gameId and responseName', () => { + replayGetCode(42, jest.fn()); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_ReplayGetCode', + { gameId: 42 }, + expect.objectContaining({ responseName: 'Response_ReplayGetCode' }) + ); + }); + + it('calls onCodeReceived with replayCode on success', () => { + const onCodeReceived = jest.fn(); + replayGetCode(42, onCodeReceived); + invokeOnSuccess({ replayCode: 'abc123-xyz' }); + expect(onCodeReceived).toHaveBeenCalledWith('abc123-xyz'); + }); +}); + +describe('replaySubmitCode', () => { + const { replaySubmitCode } = jest.requireActual('./replaySubmitCode'); + beforeEach(() => jest.clearAllMocks()); + + it('sends Command_ReplaySubmitCode with replayCode', () => { + replaySubmitCode('42-abc123'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_ReplaySubmitCode', + { replayCode: '42-abc123' }, + expect.any(Object) + ); + }); + + it('forwards onSuccess callback', () => { + const onSuccess = jest.fn(); + replaySubmitCode('42-abc123', onSuccess); + invokeOnSuccess(); + expect(onSuccess).toHaveBeenCalled(); + }); + + it('forwards onError callback', () => { + const onError = jest.fn(); + replaySubmitCode('42-abc123', undefined, onError); + invokeCallback('onError', 404); + expect(onError).toHaveBeenCalledWith(404); + }); +}); diff --git a/webclient/src/websocket/events/session/connectionClosed.ts b/webclient/src/websocket/events/session/connectionClosed.ts index 40f75c013..796f3ab80 100644 --- a/webclient/src/websocket/events/session/connectionClosed.ts +++ b/webclient/src/websocket/events/session/connectionClosed.ts @@ -19,7 +19,7 @@ export function connectionClosed({ reason, reasonStr, endTime }: ConnectionClose message = 'There are too many concurrent connections from your address'; break; case CloseReason.BANNED: - message = endTime > 0 + message = typeof endTime === 'number' && endTime > 0 && Number.isFinite(endTime) ? `You are banned until ${new Date(endTime * 1000).toLocaleString()}` : 'You are banned'; break; diff --git a/webclient/src/websocket/events/session/sessionEvents.spec.ts b/webclient/src/websocket/events/session/sessionEvents.spec.ts index 35ad9bcaa..f6f39957b 100644 --- a/webclient/src/websocket/events/session/sessionEvents.spec.ts +++ b/webclient/src/websocket/events/session/sessionEvents.spec.ts @@ -322,6 +322,39 @@ describe('connectionClosed', () => { connectionClosed({ reason: 7 } as any); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'Unknown reason'); }); + + it('BANNED with valid positive endTime → shows formatted date', () => { + connectionClosed({ reason: 2, endTime: 1700000000 } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalledWith( + expect.anything(), + expect.stringContaining('You are banned until') + ); + }); + + it('BANNED with endTime = 0 → shows generic banned message', () => { + connectionClosed({ reason: 2, endTime: 0 } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); + }); + + it('BANNED with endTime = -1 → shows generic banned message', () => { + connectionClosed({ reason: 2, endTime: -1 } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); + }); + + it('BANNED with endTime = NaN → shows generic banned message', () => { + connectionClosed({ reason: 2, endTime: NaN } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); + }); + + it('BANNED with endTime = Infinity → shows generic banned message', () => { + connectionClosed({ reason: 2, endTime: Infinity } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); + }); + + it('BANNED with reasonStr → uses reasonStr regardless of endTime', () => { + connectionClosed({ reason: 2, endTime: 0, reasonStr: 'custom ban reason' } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'custom ban reason'); + }); }); // ---------------------------------------------------------------- diff --git a/webclient/src/websocket/persistence/RoomPersistence.spec.ts b/webclient/src/websocket/persistence/RoomPersistence.spec.ts index 40874928b..1130ec890 100644 --- a/webclient/src/websocket/persistence/RoomPersistence.spec.ts +++ b/webclient/src/websocket/persistence/RoomPersistence.spec.ts @@ -80,6 +80,16 @@ describe('RoomPersistence', () => { RoomPersistence.updateGames(1, [game]); expect(NormalizeService.normalizeGameObject).not.toHaveBeenCalled(); }); + + it('returns without error when gameList is empty', () => { + expect(() => RoomPersistence.updateGames(1, [])).not.toThrow(); + expect(RoomsDispatch.updateGames).not.toHaveBeenCalled(); + }); + + it('returns without error when gameList is null', () => { + expect(() => RoomPersistence.updateGames(1, null as any)).not.toThrow(); + expect(RoomsDispatch.updateGames).not.toHaveBeenCalled(); + }); }); it('addMessage normalizes message and dispatches', () => { diff --git a/webclient/src/websocket/persistence/RoomPersistence.ts b/webclient/src/websocket/persistence/RoomPersistence.ts index a69e79338..20ed62066 100644 --- a/webclient/src/websocket/persistence/RoomPersistence.ts +++ b/webclient/src/websocket/persistence/RoomPersistence.ts @@ -21,6 +21,10 @@ export class RoomPersistence { } static updateGames(roomId: number, gameList: Game[]) { + if (!gameList?.length) { + return; + } + const game = gameList[0]; if (!game.gameType) { diff --git a/webclient/src/websocket/services/ProtobufService.spec.ts b/webclient/src/websocket/services/ProtobufService.spec.ts index c03173020..0050f645c 100644 --- a/webclient/src/websocket/services/ProtobufService.spec.ts +++ b/webclient/src/websocket/services/ProtobufService.spec.ts @@ -258,6 +258,25 @@ describe('ProtobufService', () => { expect((service as any).pendingCommands[1]).toBeUndefined(); }); + it('resolves pending command when response cmdId is a protobufjs Long object', () => { + const service = new ProtobufService(mockWebClient); + const cb = jest.fn(); + (service as any).cmdId = 1; + (service as any).pendingCommands[1] = cb; + + // Simulate protobufjs decoding cmdId as a Long object (low=1, high=0) + const longCmdId = { low: 1, high: 0, unsigned: false, toString: () => '1' }; + const response = { cmdId: longCmdId }; + ProtoController.root.ServerMessage.decode = jest.fn().mockReturnValue({ + messageType: ProtoController.root.ServerMessage.MessageType.RESPONSE, + response, + }); + + service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); + expect(cb).toHaveBeenCalledWith(response); + expect((service as any).pendingCommands[1]).toBeUndefined(); + }); + it('routes ROOM_EVENT message', () => { const service = new ProtobufService(mockWebClient); const processRoomEvent = jest.spyOn(service as any, 'processRoomEvent'); diff --git a/webclient/src/websocket/utils/sanitizeHtml.util.spec.ts b/webclient/src/websocket/utils/sanitizeHtml.util.spec.ts index cea755d98..86c547548 100644 --- a/webclient/src/websocket/utils/sanitizeHtml.util.spec.ts +++ b/webclient/src/websocket/utils/sanitizeHtml.util.spec.ts @@ -52,4 +52,21 @@ describe('sanitizeHtml', () => { const result = sanitizeHtml('xss'); expect(result).not.toContain('javascript:'); }); + + it('preserves src and alt on img tags', () => { + const result = sanitizeHtml('test'); + expect(result).toContain('src="http://example.com/img.png"'); + expect(result).toContain('alt="test"'); + }); + + it('strips javascript: scheme from img src', () => { + const result = sanitizeHtml(''); + expect(result).not.toContain('src="javascript:'); + }); + + it('strips onerror from img while keeping safe src', () => { + const result = sanitizeHtml(''); + expect(result).not.toContain('onerror'); + expect(result).toContain('src="http://example.com/img.png"'); + }); }); diff --git a/webclient/src/websocket/utils/sanitizeHtml.util.ts b/webclient/src/websocket/utils/sanitizeHtml.util.ts index e5321213b..886a56e48 100644 --- a/webclient/src/websocket/utils/sanitizeHtml.util.ts +++ b/webclient/src/websocket/utils/sanitizeHtml.util.ts @@ -5,6 +5,7 @@ export function sanitizeHtml(msg: string): string { allowedTags: ['br', 'a', 'img', 'center', 'b', 'font'], allowedAttributes: { '*': ['href', 'color', 'rel', 'target'], + 'img': ['src', 'alt'], }, allowedSchemes: ['http', 'https', 'ftp'], transformTags: { From 98ce317ee1915ed6b20f36742dd7aaeab9bbe07b Mon Sep 17 00:00:00 2001 From: seavor Date: Sun, 12 Apr 2026 15:55:00 -0500 Subject: [PATCH 08/38] remove naked password from redux layer --- .../websocket/commands/session/activate.ts | 4 +- .../commands/session/forgotPasswordReset.ts | 4 +- .../src/websocket/commands/session/login.ts | 9 +- .../websocket/commands/session/register.ts | 9 +- .../commands/session/requestPasswordSalt.ts | 8 +- .../session/sessionCommands-complex.spec.ts | 133 ++++++++++-------- .../events/session/serverIdentification.ts | 16 +-- .../events/session/sessionEvents.spec.ts | 75 ++++++---- 8 files changed, 151 insertions(+), 107 deletions(-) diff --git a/webclient/src/websocket/commands/session/activate.ts b/webclient/src/websocket/commands/session/activate.ts index 4cd0e8c4e..6fd909ca8 100644 --- a/webclient/src/websocket/commands/session/activate.ts +++ b/webclient/src/websocket/commands/session/activate.ts @@ -8,7 +8,7 @@ import { SessionPersistence } from '../../persistence'; import { disconnect, login, updateStatus } from './'; -export function activate(options: WebSocketConnectOptions, passwordSalt?: string): void { +export function activate(options: WebSocketConnectOptions, password?: string, passwordSalt?: string): void { const { userName, token } = options as unknown as AccountActivationParams; BackendService.sendSessionCommand('Command_Activate', { @@ -19,7 +19,7 @@ export function activate(options: WebSocketConnectOptions, passwordSalt?: string onResponseCode: { [ProtoController.root.Response.ResponseCode.RespActivationAccepted]: () => { SessionPersistence.accountActivationSuccess(); - login(options, passwordSalt); + login(options, password, passwordSalt); }, }, onError: () => { diff --git a/webclient/src/websocket/commands/session/forgotPasswordReset.ts b/webclient/src/websocket/commands/session/forgotPasswordReset.ts index d9a775816..21e5842f6 100644 --- a/webclient/src/websocket/commands/session/forgotPasswordReset.ts +++ b/webclient/src/websocket/commands/session/forgotPasswordReset.ts @@ -8,8 +8,8 @@ import { hashPassword } from '../../utils'; import { disconnect, updateStatus } from '.'; -export function forgotPasswordReset(options: WebSocketConnectOptions, passwordSalt?: string): void { - const { userName, token, newPassword } = options as unknown as ForgotPasswordResetParams; +export function forgotPasswordReset(options: WebSocketConnectOptions, newPassword?: string, passwordSalt?: string): void { + const { userName, token } = options as unknown as ForgotPasswordResetParams; const params: any = { ...webClient.clientConfig, diff --git a/webclient/src/websocket/commands/session/login.ts b/webclient/src/websocket/commands/session/login.ts index c98128917..adbd45b5d 100644 --- a/webclient/src/websocket/commands/session/login.ts +++ b/webclient/src/websocket/commands/session/login.ts @@ -12,8 +12,8 @@ import { updateStatus, } from './'; -export function login(options: WebSocketConnectOptions, passwordSalt?: string): void { - const { userName, password, hashedPassword } = options; +export function login(options: WebSocketConnectOptions, password?: string, passwordSalt?: string): void { + const { userName, hashedPassword } = options; const loginConfig: any = { ...webClient.clientConfig, @@ -71,7 +71,10 @@ export function login(options: WebSocketConnectOptions, passwordSalt?: string): onLoginError('Login failed: server error'), [ResponseCode.RespAccountNotActivated]: () => onLoginError('Login failed: account not activated', - () => SessionPersistence.accountAwaitingActivation(options) + () => { + const { password: _p, newPassword: _np, ...safeOptions } = options; + SessionPersistence.accountAwaitingActivation(safeOptions); + } ), }, onError: (responseCode) => diff --git a/webclient/src/websocket/commands/session/register.ts b/webclient/src/websocket/commands/session/register.ts index a25b85868..31bd37a09 100644 --- a/webclient/src/websocket/commands/session/register.ts +++ b/webclient/src/websocket/commands/session/register.ts @@ -9,8 +9,8 @@ import { hashPassword } from '../../utils'; import { login, disconnect, updateStatus } from './'; -export function register(options: WebSocketConnectOptions, passwordSalt?: string): void { - const { userName, password, email, country, realName } = options as ServerRegisterParams; +export function register(options: WebSocketConnectOptions, password?: string, passwordSalt?: string): void { + const { userName, email, country, realName } = options as ServerRegisterParams; const params: any = { ...webClient.clientConfig, @@ -37,12 +37,13 @@ export function register(options: WebSocketConnectOptions, passwordSalt?: string BackendService.sendSessionCommand('Command_Register', params, { onResponseCode: { [ResponseCode.RespRegistrationAccepted]: () => { - login(options, passwordSalt); + login(options, password, passwordSalt); SessionPersistence.registrationSuccess(); }, [ResponseCode.RespRegistrationAcceptedNeedsActivation]: () => { updateStatus(StatusEnum.DISCONNECTED, 'Registration accepted, awaiting activation'); - SessionPersistence.accountAwaitingActivation(options); + const { password: _p, newPassword: _np, ...safeOptions } = options; + SessionPersistence.accountAwaitingActivation(safeOptions); disconnect(); }, [ResponseCode.RespUserAlreadyExists]: () => onRegistrationError( diff --git a/webclient/src/websocket/commands/session/requestPasswordSalt.ts b/webclient/src/websocket/commands/session/requestPasswordSalt.ts index a3d1fc05c..e3635da70 100644 --- a/webclient/src/websocket/commands/session/requestPasswordSalt.ts +++ b/webclient/src/websocket/commands/session/requestPasswordSalt.ts @@ -14,7 +14,7 @@ import { updateStatus } from './'; -export function requestPasswordSalt(options: WebSocketConnectOptions): void { +export function requestPasswordSalt(options: WebSocketConnectOptions, password?: string, newPassword?: string): void { const { userName } = options as RequestPasswordSaltParams; const onFailure = () => { @@ -41,13 +41,13 @@ export function requestPasswordSalt(options: WebSocketConnectOptions): void { switch (options.reason) { case WebSocketConnectReason.ACTIVATE_ACCOUNT: - activate(options, passwordSalt); + activate(options, password, passwordSalt); break; case WebSocketConnectReason.PASSWORD_RESET: - forgotPasswordReset(options, passwordSalt); + forgotPasswordReset(options, newPassword, passwordSalt); break; default: - login(options, passwordSalt); + login(options, password, passwordSalt); } }, onResponseCode: { diff --git a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts index 1d61c8a36..97a163081 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts @@ -124,7 +124,7 @@ describe('login', () => { const { login } = jest.requireActual('./login'); it('sends Command_Login with plain password when no salt', () => { - login({ userName: 'alice', password: 'pw' } as any); + login({ userName: 'alice' } as any, 'pw'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( 'Command_Login', expect.objectContaining({ userName: 'alice', password: 'pw' }), @@ -133,7 +133,7 @@ describe('login', () => { }); it('sends Command_Login with hashedPassword when salt is given', () => { - login({ userName: 'alice', password: 'pw' } as any, 'salt'); + login({ userName: 'alice' } as any, 'pw', 'salt'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( 'Command_Login', expect.objectContaining({ hashedPassword: 'hashed_pw' }), @@ -142,7 +142,7 @@ describe('login', () => { }); it('uses options.hashedPassword if provided', () => { - login({ userName: 'alice', password: 'pw', hashedPassword: 'pre_hashed' } as any, 'salt'); + login({ userName: 'alice', hashedPassword: 'pre_hashed' } as any, 'pw', 'salt'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( 'Command_Login', expect.objectContaining({ hashedPassword: 'pre_hashed' }), @@ -151,7 +151,7 @@ describe('login', () => { }); it('onSuccess dispatches buddy/ignore/user and calls listUsers/listRooms', () => { - login({ userName: 'alice', password: 'pw' } as any); + login({ userName: 'alice' } as any, 'pw'); const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } }; invokeOnSuccess(loginResp, { responseCode: 0, '.Response_Login.ext': loginResp }); expect(SessionPersistence.updateBuddyList).toHaveBeenCalledWith([]); @@ -164,7 +164,7 @@ describe('login', () => { }); it('onSuccess does NOT pass plaintext password to loginSuccessful', () => { - login({ userName: 'alice', password: 'secret' } as any); + login({ userName: 'alice' } as any, 'secret'); const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } }; invokeOnSuccess(loginResp, { responseCode: 0, '.Response_Login.ext': loginResp }); const calledWith = (SessionPersistence.loginSuccessful as jest.Mock).mock.calls[0][0]; @@ -172,7 +172,7 @@ describe('login', () => { }); it('onSuccess passes hashedPassword to loginSuccessful when salt is used', () => { - login({ userName: 'alice', password: 'pw' } as any, 'salt'); + login({ userName: 'alice' } as any, 'pw', 'salt'); const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } }; invokeOnSuccess(loginResp, { responseCode: 0, '.Response_Login.ext': loginResp }); const calledWith = (SessionPersistence.loginSuccessful as jest.Mock).mock.calls[0][0]; @@ -180,63 +180,65 @@ describe('login', () => { }); it('onResponseCode RespClientUpdateRequired calls onLoginError', () => { - login({ userName: 'alice', password: 'pw' } as any); + login({ userName: 'alice' } as any, 'pw'); invokeResponseCode(1); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); }); it('onResponseCode RespWrongPassword', () => { - login({ userName: 'alice', password: 'pw' } as any); + login({ userName: 'alice' } as any, 'pw'); invokeResponseCode(2); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespUsernameInvalid', () => { - login({ userName: 'alice', password: 'pw' } as any); + login({ userName: 'alice' } as any, 'pw'); invokeResponseCode(3); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespWouldOverwriteOldSession', () => { - login({ userName: 'alice', password: 'pw' } as any); + login({ userName: 'alice' } as any, 'pw'); invokeResponseCode(4); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespUserIsBanned', () => { - login({ userName: 'alice', password: 'pw' } as any); + login({ userName: 'alice' } as any, 'pw'); invokeResponseCode(5); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespRegistrationRequired', () => { - login({ userName: 'alice', password: 'pw' } as any); + login({ userName: 'alice' } as any, 'pw'); invokeResponseCode(6); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespClientIdRequired', () => { - login({ userName: 'alice', password: 'pw' } as any); + login({ userName: 'alice' } as any, 'pw'); invokeResponseCode(7); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespContextError', () => { - login({ userName: 'alice', password: 'pw' } as any); + login({ userName: 'alice' } as any, 'pw'); invokeResponseCode(8); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); - it('onResponseCode RespAccountNotActivated calls accountAwaitingActivation', () => { - login({ userName: 'alice', password: 'pw' } as any); + it('onResponseCode RespAccountNotActivated calls accountAwaitingActivation without password in options', () => { + login({ userName: 'alice', password: 'leaked' } as any, 'pw'); invokeResponseCode(9); - expect(SessionPersistence.accountAwaitingActivation).toHaveBeenCalled(); + expect(SessionPersistence.accountAwaitingActivation).toHaveBeenCalledWith( + expect.not.objectContaining({ password: expect.anything() }) + ); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onError calls onLoginError with unknown error message', () => { - login({ userName: 'alice', password: 'pw' } as any); + login({ userName: 'alice' } as any, 'pw'); invokeOnError(999); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); @@ -249,7 +251,7 @@ describe('register', () => { const { register } = jest.requireActual('./register'); it('sends Command_Register with plain password when no salt', () => { - register({ userName: 'alice', password: 'pw', email: 'a@b.com', country: 'US', realName: 'Al' } as any); + register({ userName: 'alice', email: 'a@b.com', country: 'US', realName: 'Al' } as any, 'pw'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( 'Command_Register', expect.objectContaining({ userName: 'alice', password: 'pw' }), @@ -258,7 +260,7 @@ describe('register', () => { }); it('uses hashedPassword when salt is provided', () => { - register({ userName: 'alice', password: 'pw' } as any, 'salt'); + register({ userName: 'alice' } as any, 'pw', 'salt'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( 'Command_Register', expect.objectContaining({ hashedPassword: 'hashed_pw' }), @@ -267,76 +269,78 @@ describe('register', () => { }); it('RespRegistrationAccepted calls login without salt and registrationSuccess', () => { - register({ userName: 'alice', password: 'pw' } as any); + register({ userName: 'alice' } as any, 'pw'); invokeResponseCode(10); - expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), undefined); + expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'pw', undefined); expect(SessionPersistence.registrationSuccess).toHaveBeenCalled(); }); it('RespRegistrationAccepted forwards salt to login', () => { - register({ userName: 'alice', password: 'pw' } as any, 'mySalt'); + register({ userName: 'alice' } as any, 'pw', 'mySalt'); invokeResponseCode(10); - expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'mySalt'); + expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'pw', 'mySalt'); expect(SessionPersistence.registrationSuccess).toHaveBeenCalled(); }); - it('RespRegistrationAcceptedNeedsActivation calls accountAwaitingActivation', () => { - register({ userName: 'alice', password: 'pw' } as any); + it('RespRegistrationAcceptedNeedsActivation calls accountAwaitingActivation without password in options', () => { + register({ userName: 'alice', password: 'leaked' } as any, 'pw'); invokeResponseCode(11); - expect(SessionPersistence.accountAwaitingActivation).toHaveBeenCalled(); + expect(SessionPersistence.accountAwaitingActivation).toHaveBeenCalledWith( + expect.not.objectContaining({ password: expect.anything() }) + ); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); }); it('RespUserAlreadyExists calls registrationUserNameError', () => { - register({ userName: 'alice', password: 'pw' } as any); + register({ userName: 'alice' } as any, 'pw'); invokeResponseCode(12); expect(SessionPersistence.registrationUserNameError).toHaveBeenCalled(); }); it('RespUsernameInvalid calls registrationUserNameError', () => { - register({ userName: 'alice', password: 'pw' } as any); + register({ userName: 'alice' } as any, 'pw'); invokeResponseCode(3); expect(SessionPersistence.registrationUserNameError).toHaveBeenCalled(); }); it('RespPasswordTooShort calls registrationPasswordError', () => { - register({ userName: 'alice', password: 'pw' } as any); + register({ userName: 'alice' } as any, 'pw'); invokeResponseCode(13); expect(SessionPersistence.registrationPasswordError).toHaveBeenCalled(); }); it('RespEmailRequiredToRegister calls registrationRequiresEmail', () => { - register({ userName: 'alice', password: 'pw' } as any); + register({ userName: 'alice' } as any, 'pw'); invokeResponseCode(14); expect(SessionPersistence.registrationRequiresEmail).toHaveBeenCalled(); }); it('RespEmailBlackListed calls registrationEmailError', () => { - register({ userName: 'alice', password: 'pw' } as any); + register({ userName: 'alice' } as any, 'pw'); invokeResponseCode(15); expect(SessionPersistence.registrationEmailError).toHaveBeenCalled(); }); it('RespTooManyRequests calls registrationEmailError', () => { - register({ userName: 'alice', password: 'pw' } as any); + register({ userName: 'alice' } as any, 'pw'); invokeResponseCode(16); expect(SessionPersistence.registrationEmailError).toHaveBeenCalled(); }); it('RespRegistrationDisabled calls registrationFailed', () => { - register({ userName: 'alice', password: 'pw' } as any); + register({ userName: 'alice' } as any, 'pw'); invokeResponseCode(17); expect(SessionPersistence.registrationFailed).toHaveBeenCalled(); }); it('RespUserIsBanned calls registrationFailed with raw.reasonStr and raw.endTime', () => { - register({ userName: 'alice', password: 'pw' } as any); + register({ userName: 'alice' } as any, 'pw'); invokeResponseCode(5, { reasonStr: 'bad user', endTime: 9999 }); expect(SessionPersistence.registrationFailed).toHaveBeenCalledWith('bad user', 9999); }); it('onError calls registrationFailed', () => { - register({ userName: 'alice', password: 'pw' } as any); + register({ userName: 'alice' } as any, 'pw'); invokeOnError(); expect(SessionPersistence.registrationFailed).toHaveBeenCalled(); }); @@ -348,16 +352,25 @@ describe('register', () => { describe('activate', () => { const { activate } = jest.requireActual('./activate'); - it('sends Command_Activate', () => { - activate({ userName: 'alice', token: 'tok' } as any); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_Activate', expect.any(Object), expect.any(Object)); + it('sends Command_Activate with userName and token, not password', () => { + activate({ userName: 'alice', token: 'tok' } as any, 'pw'); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_Activate', + expect.objectContaining({ userName: 'alice', token: 'tok' }), + expect.any(Object) + ); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + 'Command_Activate', + expect.not.objectContaining({ password: expect.anything() }), + expect.any(Object) + ); }); - it('RespActivationAccepted calls accountActivationSuccess and login with salt', () => { - activate({ userName: 'alice', token: 'tok' } as any, 'salt'); + it('RespActivationAccepted calls accountActivationSuccess and forwards password+salt to login', () => { + activate({ userName: 'alice', token: 'tok' } as any, 'pw', 'salt'); invokeResponseCode(18); expect(SessionPersistence.accountActivationSuccess).toHaveBeenCalled(); - expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'salt'); + expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'pw', 'salt'); }); it('onError calls accountActivationFailed and disconnect', () => { @@ -438,7 +451,7 @@ describe('forgotPasswordReset', () => { const { forgotPasswordReset } = jest.requireActual('./forgotPasswordReset'); it('sends Command_ForgotPasswordReset with plain newPassword when no salt', () => { - forgotPasswordReset({ userName: 'alice', token: 'tok', newPassword: 'newpw' } as any); + forgotPasswordReset({ userName: 'alice', token: 'tok' } as any, 'newpw'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( 'Command_ForgotPasswordReset', expect.objectContaining({ newPassword: 'newpw' }), @@ -447,7 +460,7 @@ describe('forgotPasswordReset', () => { }); it('sends hashed new password when salt provided', () => { - forgotPasswordReset({ userName: 'alice', token: 'tok', newPassword: 'newpw' } as any, 'salt'); + forgotPasswordReset({ userName: 'alice', token: 'tok' } as any, 'newpw', 'salt'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( 'Command_ForgotPasswordReset', expect.objectContaining({ hashedNewPassword: 'hashed_pw' }), @@ -456,14 +469,14 @@ describe('forgotPasswordReset', () => { }); it('onSuccess calls resetPasswordSuccess and disconnect', () => { - forgotPasswordReset({ userName: 'alice', token: 'tok', newPassword: 'newpw' } as any); + forgotPasswordReset({ userName: 'alice', token: 'tok' } as any, 'newpw'); invokeOnSuccess(); expect(SessionPersistence.resetPasswordSuccess).toHaveBeenCalled(); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); }); it('onError calls resetPasswordFailed and disconnect', () => { - forgotPasswordReset({ userName: 'alice', token: 'tok', newPassword: 'newpw' } as any); + forgotPasswordReset({ userName: 'alice', token: 'tok' } as any, 'newpw'); invokeOnError(); expect(SessionPersistence.resetPasswordFailed).toHaveBeenCalled(); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); @@ -477,53 +490,53 @@ describe('requestPasswordSalt', () => { const { requestPasswordSalt } = jest.requireActual('./requestPasswordSalt'); it('sends Command_RequestPasswordSalt', () => { - requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any); + requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any, 'pw'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_RequestPasswordSalt', expect.any(Object), expect.any(Object)); }); - it('onSuccess with LOGIN reason calls login', () => { - requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any); + it('onSuccess with LOGIN reason forwards password+salt to login', () => { + requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any, 'pw'); const resp = { passwordSalt: 'salt123' }; invokeOnSuccess(resp, { responseCode: 0, '.Response_PasswordSalt.ext': resp }); - expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'salt123'); + expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'pw', 'salt123'); }); - it('onSuccess with ACTIVATE_ACCOUNT reason calls activate', () => { - requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.ACTIVATE_ACCOUNT } as any); + it('onSuccess with ACTIVATE_ACCOUNT reason forwards password+salt to activate', () => { + requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.ACTIVATE_ACCOUNT } as any, 'pw'); const resp = { passwordSalt: 'salt123' }; invokeOnSuccess(resp, { responseCode: 0, '.Response_PasswordSalt.ext': resp }); - expect(SessionIndexMocks.activate).toHaveBeenCalledWith(expect.any(Object), 'salt123'); + expect(SessionIndexMocks.activate).toHaveBeenCalledWith(expect.any(Object), 'pw', 'salt123'); }); - it('onSuccess with PASSWORD_RESET reason calls forgotPasswordReset', () => { - requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.PASSWORD_RESET } as any); + it('onSuccess with PASSWORD_RESET reason forwards newPassword+salt to forgotPasswordReset', () => { + requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.PASSWORD_RESET } as any, undefined, 'newpw'); const resp = { passwordSalt: 'salt123' }; invokeOnSuccess(resp, { responseCode: 0, '.Response_PasswordSalt.ext': resp }); - expect(SessionIndexMocks.forgotPasswordReset).toHaveBeenCalled(); + expect(SessionIndexMocks.forgotPasswordReset).toHaveBeenCalledWith(expect.any(Object), 'newpw', 'salt123'); }); it('onResponseCode RespRegistrationRequired calls updateStatus and disconnect', () => { - requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any); + requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any, 'pw'); invokeResponseCode(6); expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, expect.any(String)); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); }); it('onResponseCode RespRegistrationRequired with ACTIVATE_ACCOUNT calls accountActivationFailed', () => { - requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.ACTIVATE_ACCOUNT } as any); + requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.ACTIVATE_ACCOUNT } as any, 'pw'); invokeResponseCode(6); expect(SessionPersistence.accountActivationFailed).toHaveBeenCalled(); }); it('onError calls updateStatus DISCONNECTED and disconnect', () => { - requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any); + requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any, 'pw'); invokeOnError(); expect(SessionIndexMocks.updateStatus).toHaveBeenCalled(); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); }); it('onError with PASSWORD_RESET reason calls resetPasswordFailed', () => { - requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.PASSWORD_RESET } as any); + requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.PASSWORD_RESET } as any, undefined, 'newpw'); invokeOnError(); expect(SessionPersistence.resetPasswordFailed).toHaveBeenCalled(); }); diff --git a/webclient/src/websocket/events/session/serverIdentification.ts b/webclient/src/websocket/events/session/serverIdentification.ts index 87ae79453..594d26e6d 100644 --- a/webclient/src/websocket/events/session/serverIdentification.ts +++ b/webclient/src/websocket/events/session/serverIdentification.ts @@ -25,26 +25,26 @@ export function serverIdentification(info: ServerIdentificationData): void { } const getPasswordSalt = passwordSaltSupported(serverOptions); - const connectOptions = { ...webClient.options }; + const { password, newPassword, ...connectOptions } = webClient.options; switch (connectOptions.reason) { case WebSocketConnectReason.LOGIN: updateStatus(StatusEnum.LOGGING_IN, 'Logging In...'); if (getPasswordSalt) { - requestPasswordSalt(connectOptions); + requestPasswordSalt(connectOptions, password); } else { - login(connectOptions); + login(connectOptions, password); } break; case WebSocketConnectReason.REGISTER: const passwordSalt = getPasswordSalt ? generateSalt() : null; - register(connectOptions, passwordSalt); + register(connectOptions, password, passwordSalt); break; case WebSocketConnectReason.ACTIVATE_ACCOUNT: if (getPasswordSalt) { - requestPasswordSalt(connectOptions); + requestPasswordSalt(connectOptions, password); } else { - activate(connectOptions); + activate(connectOptions, password); } break; case WebSocketConnectReason.PASSWORD_RESET_REQUEST: @@ -55,9 +55,9 @@ export function serverIdentification(info: ServerIdentificationData): void { break; case WebSocketConnectReason.PASSWORD_RESET: if (getPasswordSalt) { - requestPasswordSalt(connectOptions); + requestPasswordSalt(connectOptions, undefined, newPassword); } else { - forgotPasswordReset(connectOptions); + forgotPasswordReset(connectOptions, newPassword); } break; default: diff --git a/webclient/src/websocket/events/session/sessionEvents.spec.ts b/webclient/src/websocket/events/session/sessionEvents.spec.ts index f6f39957b..9f8047890 100644 --- a/webclient/src/websocket/events/session/sessionEvents.spec.ts +++ b/webclient/src/websocket/events/session/sessionEvents.spec.ts @@ -374,46 +374,66 @@ describe('serverIdentification', () => { expect(SessionCmds.disconnect).toHaveBeenCalled(); }); - it('LOGIN reason without salt → calls login', () => { - (webClient as any).options = { reason: WebSocketConnectReason.LOGIN }; + it('LOGIN reason without salt → calls login with password as separate param', () => { + (webClient as any).options = { reason: WebSocketConnectReason.LOGIN, password: 'secret' }; (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0); serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); - expect(SessionCmds.login).toHaveBeenCalled(); + expect(SessionCmds.login).toHaveBeenCalledWith( + expect.not.objectContaining({ password: expect.anything() }), + 'secret' + ); }); - it('LOGIN reason with salt → calls requestPasswordSalt', () => { - (webClient as any).options = { reason: WebSocketConnectReason.LOGIN }; + it('LOGIN reason with salt → calls requestPasswordSalt with password as separate param', () => { + (webClient as any).options = { reason: WebSocketConnectReason.LOGIN, password: 'secret' }; (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(1); serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any); - expect(SessionCmds.requestPasswordSalt).toHaveBeenCalled(); + expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith( + expect.not.objectContaining({ password: expect.anything() }), + 'secret' + ); }); - it('REGISTER reason without salt → calls register with null salt', () => { - (webClient as any).options = { reason: WebSocketConnectReason.REGISTER }; + it('REGISTER reason without salt → calls register with password and null salt', () => { + (webClient as any).options = { reason: WebSocketConnectReason.REGISTER, password: 'secret' }; (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0); serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); - expect(SessionCmds.register).toHaveBeenCalledWith(expect.any(Object), null); + expect(SessionCmds.register).toHaveBeenCalledWith( + expect.not.objectContaining({ password: expect.anything() }), + 'secret', + null + ); }); - it('REGISTER reason with salt → calls register with generated salt', () => { - (webClient as any).options = { reason: WebSocketConnectReason.REGISTER }; + it('REGISTER reason with salt → calls register with password and generated salt', () => { + (webClient as any).options = { reason: WebSocketConnectReason.REGISTER, password: 'secret' }; (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(1); serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any); - expect(SessionCmds.register).toHaveBeenCalledWith(expect.any(Object), 'newSalt'); + expect(SessionCmds.register).toHaveBeenCalledWith( + expect.not.objectContaining({ password: expect.anything() }), + 'secret', + 'newSalt' + ); }); - it('ACTIVATE_ACCOUNT reason without salt → calls activate', () => { - (webClient as any).options = { reason: WebSocketConnectReason.ACTIVATE_ACCOUNT }; + it('ACTIVATE_ACCOUNT reason without salt → calls activate with password as separate param', () => { + (webClient as any).options = { reason: WebSocketConnectReason.ACTIVATE_ACCOUNT, password: 'secret' }; (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0); serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); - expect(SessionCmds.activate).toHaveBeenCalled(); + expect(SessionCmds.activate).toHaveBeenCalledWith( + expect.not.objectContaining({ password: expect.anything() }), + 'secret' + ); }); - it('ACTIVATE_ACCOUNT reason with salt → calls requestPasswordSalt', () => { - (webClient as any).options = { reason: WebSocketConnectReason.ACTIVATE_ACCOUNT }; + it('ACTIVATE_ACCOUNT reason with salt → calls requestPasswordSalt with password as separate param', () => { + (webClient as any).options = { reason: WebSocketConnectReason.ACTIVATE_ACCOUNT, password: 'secret' }; (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(1); serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any); - expect(SessionCmds.requestPasswordSalt).toHaveBeenCalled(); + expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith( + expect.not.objectContaining({ password: expect.anything() }), + 'secret' + ); }); it('PASSWORD_RESET_REQUEST reason → calls forgotPasswordRequest', () => { @@ -428,18 +448,25 @@ describe('serverIdentification', () => { expect(SessionCmds.forgotPasswordChallenge).toHaveBeenCalled(); }); - it('PASSWORD_RESET reason without salt → calls forgotPasswordReset', () => { - (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET }; + it('PASSWORD_RESET reason without salt → calls forgotPasswordReset with newPassword as separate param', () => { + (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET, newPassword: 'newpw' }; (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0); serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); - expect(SessionCmds.forgotPasswordReset).toHaveBeenCalled(); + expect(SessionCmds.forgotPasswordReset).toHaveBeenCalledWith( + expect.not.objectContaining({ newPassword: expect.anything() }), + 'newpw' + ); }); - it('PASSWORD_RESET reason with salt → calls requestPasswordSalt', () => { - (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET }; + it('PASSWORD_RESET reason with salt → calls requestPasswordSalt with newPassword as separate param', () => { + (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET, newPassword: 'newpw' }; (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(1); serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any); - expect(SessionCmds.requestPasswordSalt).toHaveBeenCalled(); + expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith( + expect.not.objectContaining({ newPassword: expect.anything() }), + undefined, + 'newpw' + ); }); it('unknown reason → updateStatus DISCONNECTED and disconnect', () => { From 68e22d22bf1f2cbd4ab063b12689bebc15349c11 Mon Sep 17 00:00:00 2001 From: seavor Date: Sun, 12 Apr 2026 18:35:13 -0500 Subject: [PATCH 09/38] migrate from CRA to vite --- .github/workflows/web-build.yml | 2 +- webclient/.env.development | 2 +- webclient/.env.production | 2 +- webclient/.env.test | 2 +- webclient/.eslintrc.js | 2 +- webclient/index.html | 22 + webclient/package-lock.json | 31974 +++------------- webclient/package.json | 41 +- webclient/public/index.html | 44 - webclient/public/manifest.json | 4 +- webclient/src/api/AdminService.spec.ts | 12 +- .../src/api/AuthenticationService.spec.ts | 10 +- webclient/src/api/ModeratorService.spec.ts | 16 +- webclient/src/api/RoomsService.spec.ts | 10 +- webclient/src/api/SessionService.spec.ts | 24 +- .../hooks/useFireOnce/useFireOnce.spec.tsx | 4 +- webclient/src/i18n-backend.ts | 2 +- webclient/src/images/countries/_Countries.ts | 504 +- webclient/src/react-app-env.d.ts | 1 - webclient/src/setupTests.ts | 2 +- .../src/store/game/game.dispatch.spec.ts | 4 +- webclient/src/store/game/game.reducer.spec.ts | 4 +- .../src/store/rooms/rooms.dispatch.spec.ts | 10 +- .../src/store/server/server.dispatch.spec.ts | 12 +- webclient/src/vite-env.d.ts | 2 + webclient/src/websocket/WebClient.spec.ts | 56 +- webclient/src/websocket/WebClient.ts | 2 +- .../websocket/__mocks__/callbackHelpers.ts | 6 +- webclient/src/websocket/__mocks__/helpers.ts | 22 +- .../__mocks__/sessionCommandMocks.ts | 116 +- .../commands/admin/adminCommands.spec.ts | 26 +- .../commands/game/gameCommands.spec.ts | 6 +- .../moderator/moderatorCommands.spec.ts | 50 +- .../commands/room/roomCommands.spec.ts | 24 +- .../session/sessionCommands-complex.spec.ts | 56 +- .../session/sessionCommands-simple.spec.ts | 144 +- .../websocket/events/game/gameEvents.spec.ts | 62 +- .../websocket/events/room/roomEvents.spec.ts | 24 +- .../events/session/sessionEvents.spec.ts | 120 +- .../persistence/AdminPersistence.spec.ts | 12 +- .../persistence/GamePersistence.spec.ts | 62 +- .../persistence/ModeratorPersistence.spec.ts | 30 +- .../persistence/RoomPersistence.spec.ts | 42 +- .../persistence/SessionPersistence.spec.ts | 128 +- .../websocket/services/BackendService.spec.ts | 52 +- .../services/KeepAliveService.spec.ts | 19 +- .../services/ProtoController.spec.ts | 13 +- .../src/websocket/services/ProtoController.ts | 2 +- .../services/ProtobufService.spec.ts | 103 +- .../services/WebSocketService.spec.ts | 40 +- .../src/websocket/utils/guid.util.spec.ts | 2 +- .../websocket/utils/passwordHasher.spec.ts | 2 +- .../websocket/utils/sanitizeHtml.util.spec.ts | 4 +- .../src/websocket/utils/sanitizeHtml.util.ts | 27 +- webclient/tsconfig.json | 2 +- webclient/vite.config.ts | 21 + 56 files changed, 5699 insertions(+), 28288 deletions(-) create mode 100644 webclient/index.html delete mode 100644 webclient/public/index.html delete mode 100644 webclient/src/react-app-env.d.ts create mode 100644 webclient/src/vite-env.d.ts create mode 100644 webclient/vite.config.ts diff --git a/.github/workflows/web-build.yml b/.github/workflows/web-build.yml index 8d756da02..a20dec1fc 100644 --- a/.github/workflows/web-build.yml +++ b/.github/workflows/web-build.yml @@ -30,7 +30,7 @@ jobs: fail-fast: false matrix: node_version: - - 16 + - 20 - lts/* steps: diff --git a/webclient/.env.development b/webclient/.env.development index 2accbba81..8b1378917 100644 --- a/webclient/.env.development +++ b/webclient/.env.development @@ -1 +1 @@ -ESLINT_NO_DEV_ERRORS=true + diff --git a/webclient/.env.production b/webclient/.env.production index 02269f00d..8b1378917 100644 --- a/webclient/.env.production +++ b/webclient/.env.production @@ -1 +1 @@ -DISABLE_ESLINT_PLUGIN=true + diff --git a/webclient/.env.test b/webclient/.env.test index 8711f95ab..8b1378917 100644 --- a/webclient/.env.test +++ b/webclient/.env.test @@ -1 +1 @@ -CI=true + diff --git a/webclient/.eslintrc.js b/webclient/.eslintrc.js index 98ce44430..8fb389073 100644 --- a/webclient/.eslintrc.js +++ b/webclient/.eslintrc.js @@ -1,7 +1,7 @@ module.exports = { "root": true, "parser": "@typescript-eslint/parser", - "parserOptions": {"project": ["./tsconfig.json"]}, + "parserOptions": {"ecmaVersion": 2020, "sourceType": "module", "ecmaFeatures": {"jsx": true}}, "plugins": [ "@typescript-eslint" ], diff --git a/webclient/index.html b/webclient/index.html new file mode 100644 index 000000000..8a6457319 --- /dev/null +++ b/webclient/index.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + Webatrice + + + +
+ + + diff --git a/webclient/package-lock.json b/webclient/package-lock.json index 6b12b4ad6..af3f74e50 100644 --- a/webclient/package-lock.json +++ b/webclient/package-lock.json @@ -14,6 +14,7 @@ "@mui/material": "^5.5.1", "crypto-js": "^4.2.0", "dexie": "^3.2.2", + "dompurify": "^3.3.3", "final-form": "^4.20.6", "final-form-set-field-touched": "^1.0.1", "i18next": "^22.0.4", @@ -30,21 +31,18 @@ "react-i18next": "^12.0.0", "react-redux": "^8.0.4", "react-router-dom": "^6.2.2", - "react-scripts": "5.0.1", "react-virtualized-auto-sizer": "^1.0.6", "react-window": "^1.8.6", "redux": "^4.1.2", "redux-form": "^8.3.8", "redux-thunk": "^2.4.1", - "rxjs": "^7.5.4", - "sanitize-html": "^2.7.3" + "rxjs": "^7.5.4" }, "devDependencies": { - "@babel/core": "^7.17.5", "@mui/types": "^7.1.3", - "@testing-library/jest-dom": "^5.16.2", + "@testing-library/jest-dom": "^6.4.0", "@testing-library/react": "^13.4.0", - "@types/jest": "29.2.0", + "@types/dompurify": "^3.0.5", "@types/jquery": "^3.5.14", "@types/lodash": "^4.14.179", "@types/node": "18.11.7", @@ -58,51 +56,67 @@ "@types/redux-form": "^8.3.3", "@typescript-eslint/eslint-plugin": "^5.14.0", "@typescript-eslint/parser": "^5.14.0", + "@vitejs/plugin-react": "^4.2.0", + "@vitest/coverage-v8": "^1.3.0", + "eslint": "^8.0.0", "fs-extra": "^10.0.1", "husky": "^8.0.1", - "typescript": "^4.6.2" + "jsdom": "^24.0.0", + "typescript": "^4.6.2", + "vite": "^5.1.0", + "vite-tsconfig-paths": "^4.3.1", + "vitest": "^1.3.0" } }, "node_modules/@adobe/css-tools": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", - "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", - "dev": true + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" }, "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, - "node_modules/@apideck/better-ajv-errors": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", - "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", "dependencies": { - "json-schema": "^0.4.0", - "jsonpointer": "^5.0.0", - "leven": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "ajv": ">=8" + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" } }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -111,33 +125,37 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.0.tgz", - "integrity": "sha512-Gt9jszFJYq7qzXVK4slhc6NzJXnOVmRECWcVjF/T23rNXD9NtWQ0W3qxdg+p9wWIB+VQw3GYV/U2Ha9bRTfs4w==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.6.tgz", - "integrity": "sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.6", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helpers": "^7.19.4", - "@babel/parser": "^7.19.6", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.6", - "@babel/types": "^7.19.4", - "convert-source-map": "^1.7.0", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -147,259 +165,95 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/eslint-parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz", - "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", - "dependencies": { - "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || >=14.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.11.0", - "eslint": "^7.5.0 || ^8.0.0" - } - }, - "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "engines": { - "node": ">=10" - } + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" }, "node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", - "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", - "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.20.0", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz", - "integrity": "sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", - "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", - "dependencies": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "dependencies": { - "@babel/types": "^7.18.6" + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" + "yallist": "^3.0.2" } }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", - "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", - "dependencies": { - "@babel/types": "^7.18.9" - }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz", - "integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.19.4", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.6", - "@babel/types": "^7.19.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", - "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -408,50 +262,11 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", - "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/traverse": "^7.19.1", - "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz", - "integrity": "sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==", - "dependencies": { - "@babel/types": "^7.19.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", - "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", - "dependencies": { - "@babel/types": "^7.20.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dependencies": { - "@babel/types": "^7.22.5" - }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -465,53 +280,45 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", - "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==", - "dependencies": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - }, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", - "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", - "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -520,421 +327,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", - "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-proposal-optional-chaining": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz", - "integrity": "sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q==", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", - "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.20.0.tgz", - "integrity": "sha512-vnuRRS20ygSxclEYikHzVrP9nZDFXaSzvJxGLQNAiBX041TmhS4hOUHWNIpq/q4muENuEP9XPJFXTNFejhemkg==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-replace-supers": "^7.19.1", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/plugin-syntax-decorators": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", - "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz", - "integrity": "sha512-wHmj6LDxVDnL+3WhXteUBaoM1aVILZODAUjg11kHqG4cOlfgMQGxw6aCgvrXrmaJR3Bn14oZhImyCPZzRpC93Q==", - "dependencies": { - "@babel/compat-data": "^7.19.4", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.18.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", - "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", - "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.19.0.tgz", - "integrity": "sha512-xaBZUEDntt4faL1yN8oIFlhfXeQAWJW7CLKYsHTUqriCUbj8xOra8bfxxKGi/UwExPFBuPdH4XfHc9rGQhrVkQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-flow": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz", - "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", - "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", @@ -949,78 +341,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1029,763 +357,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", - "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", - "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.0.tgz", - "integrity": "sha512-sXOohbpHZSk7GjxK9b3dKB7CfqUD5DwOH+DggKzOQ7TXYP+RCSbRykfjQmn/zq+rBjycVRtLf9pYhAaEJA786w==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz", - "integrity": "sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", - "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.0.tgz", - "integrity": "sha512-1dIhvZfkDVx/zn2S1aFwlruspTt4189j7fEkH0Y0VyuDM6bQt7bD6kLcz3l4IlLG+e5OReaBz9ROAbttRtUHqA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.19.0.tgz", - "integrity": "sha512-sgeMlNaQVbCSpgLSKP4ZZKfsJVnFnNQlUSk6gPYzR/q7tzCgQF2t8RBKAP6cKJeZdveei7Q7Jm527xepI8lNLg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-flow": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", - "dependencies": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz", - "integrity": "sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==", - "dependencies": { - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", - "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==", - "dependencies": { - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-simple-access": "^7.19.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz", - "integrity": "sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==", - "dependencies": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-identifier": "^7.19.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", - "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", - "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.18.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.18.12.tgz", - "integrity": "sha512-Q99U9/ttiu+LMnRU8psd23HhvwXmKWDQIpocm0JKaICcZHnw+mdQbHm6xnSy7dOl8I5PELakYtNBubNQlBXbZw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", - "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz", - "integrity": "sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", - "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", - "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", - "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "regenerator-transform": "^0.15.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", - "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", - "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.0.tgz", - "integrity": "sha512-xOAsAFaun3t9hCwZ13Qe7gq423UgMZ6zAgmLxeGGapFqlT/X3L5qT2btjiVLlFn7gWtMaVyceS5VxGAuKbgizw==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-typescript": "^7.20.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.4.tgz", - "integrity": "sha512-5QVOTXUdqTCjQuh2GGtdd7YEhoRXBMVGROAtsBeLGIbIz3obCBIfRMT1I3ZKkMgNzwkyCkftDXSSkHxnfVf4qg==", - "dependencies": { - "@babel/compat-data": "^7.19.4", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.19.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.19.4", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.19.4", - "@babel/plugin-transform-classes": "^7.19.0", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.19.4", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.18.6", - "@babel/plugin-transform-modules-commonjs": "^7.18.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.0", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.18.8", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.19.4", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-react": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", - "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-react-display-name": "^7.18.6", - "@babel/plugin-transform-react-jsx": "^7.18.6", - "@babel/plugin-transform-react-jsx-development": "^7.18.6", - "@babel/plugin-transform-react-pure-annotations": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", - "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-typescript": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1802,57 +381,46 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.27.1.tgz", - "integrity": "sha512-909rVuj3phpjW6y0MCXAZ5iNeORePa6ldJvp2baWGcTjwqbBDDz6xoS5JHJ7lS88NlwLYj07ImL/8IUMtDZzTA==", - "dependencies": { - "core-js-pure": "^3.30.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", - "globals": "^11.1.0" + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", - "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1861,277 +429,123 @@ "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" }, - "node_modules/@csstools/normalize.css": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.0.0.tgz", - "integrity": "sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg==" + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } }, - "node_modules/@csstools/postcss-cascade-layers": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", - "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "dependencies": { - "@csstools/selector-specificity": "^2.0.2", - "postcss-selector-parser": "^6.0.10" + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" }, "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.2" + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@csstools/postcss-color-function": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", - "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.2" + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@csstools/postcss-font-format-keywords": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", - "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-hwb-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", - "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-ic-unit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", - "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", - "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", - "dependencies": { - "@csstools/selector-specificity": "^2.0.0", - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-nested-calc": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", - "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-normalize-display-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", - "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-oklab-function": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", - "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-progressive-custom-properties": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", - "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.3" - } - }, - "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", - "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", - "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", - "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/postcss-unset-value": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", - "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/@csstools/selector-specificity": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", - "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2", - "postcss-selector-parser": "^6.0.10" + "node": ">=18" } }, "node_modules/@emotion/babel-plugin": { @@ -2279,15 +693,437 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.0", + "espree": "^9.6.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -2301,45 +1137,14 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dependencies": { - "type-fest": "^0.20.2" - }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@formatjs/ecma402-abstract": { @@ -2387,12 +1192,15 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", - "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -2403,6 +1211,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -2412,1366 +1222,62 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "engines": { - "node": ">=8" - } + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", - "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/console/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/console/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/console/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", - "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", - "dependencies": { - "@jest/console": "^27.5.1", - "@jest/reporters": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^27.5.1", - "jest-config": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-resolve-dependencies": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "jest-watcher": "^27.5.1", - "micromatch": "^4.0.4", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/core/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/core/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/core/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", - "dependencies": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/environment/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/environment/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/environment/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/environment/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/environment/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.2.2", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.2.2.tgz", - "integrity": "sha512-vwnVmrVhTmGgQzyvcpze08br91OL61t9O0lJMDyb6Y/D8EKQ9V7rGUb/p7PDt0GPzK0zFYqXWFo4EO2legXmkg==", "dev": true, - "dependencies": { - "jest-get-type": "^29.2.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", - "dependencies": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/fake-timers/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/fake-timers/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/fake-timers/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/fake-timers/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/fake-timers/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/globals": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", - "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/types": "^27.5.1", - "expect": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/globals/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/globals/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/globals/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/globals/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/globals/node_modules/diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "dependencies": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/globals/node_modules/jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", - "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.1.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/reporters/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, + "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.24.1" + "@sinclair/typebox": "^0.27.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/source-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", - "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", - "dependencies": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9", - "source-map": "^0.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/source-map/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jest/test-result": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", - "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", - "dependencies": { - "@jest/console": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/test-result/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/test-result/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/test-result/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/test-result/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/test-result/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", - "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", - "dependencies": { - "@jest/test-result": "^27.5.1", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-runtime": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", - "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.5.1", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-util": "^27.5.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/transform/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.2.1.tgz", - "integrity": "sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==", + "node_modules/@jest/schemas/node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - }, - "engines": { - "node": ">=6.0.0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, "node_modules/@jridgewell/resolve-uri": { @@ -3782,55 +1288,22 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", - "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" - }, "node_modules/@mui/base": { "version": "5.0.0-alpha.103", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.103.tgz", @@ -4072,18 +1545,11 @@ "react": "^17.0.0 || ^18.0.0" } }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "dependencies": { - "eslint-scope": "5.1.1" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -4096,6 +1562,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, "engines": { "node": ">= 8" } @@ -4104,6 +1571,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -4112,63 +1580,6 @@ "node": ">= 8" } }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.8.tgz", - "integrity": "sha512-wxXRwf+IQ6zvHSJZ+5T2RQNEsq+kx4jKRXfFvdt3nBIUzJUAvXEFsUeoaohDe/Kr84MTjGwcuIUPNcstNJORsA==", - "dependencies": { - "ansi-html-community": "^0.0.8", - "common-path-prefix": "^3.0.0", - "core-js-pure": "^3.23.3", - "error-stack-parser": "^2.0.6", - "find-up": "^5.0.0", - "html-entities": "^2.1.0", - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">= 10.13" - }, - "peerDependencies": { - "@types/webpack": "4.x || 5.x", - "react-refresh": ">=0.10.0 <1.0.0", - "sockjs-client": "^1.4.0", - "type-fest": ">=0.17.0 <4.0.0", - "webpack": ">=4.43.0 <6.0.0", - "webpack-dev-server": "3.x || 4.x", - "webpack-hot-middleware": "2.x", - "webpack-plugin-serve": "0.x || 1.x" - }, - "peerDependenciesMeta": { - "@types/webpack": { - "optional": true - }, - "sockjs-client": { - "optional": true - }, - "type-fest": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - }, - "webpack-hot-middleware": { - "optional": true - }, - "webpack-plugin-serve": { - "optional": true - } - } - }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "engines": { - "node": ">= 8" - } - }, "node_modules/@popperjs/core": { "version": "2.11.6", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", @@ -4240,323 +1651,362 @@ "node": ">=14" } }, - "node_modules/@rollup/plugin-babel": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", - "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", - "dependencies": { - "@babel/helper-module-imports": "^7.10.4", - "@rollup/pluginutils": "^3.1.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "@types/babel__core": "^7.1.9", - "rollup": "^1.20.0||^2.0.0" - }, - "peerDependenciesMeta": { - "@types/babel__core": { - "optional": true - } - } + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", - "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "@types/resolve": "1.17.1", - "builtin-modules": "^3.1.0", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@rollup/plugin-replace": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", - "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "magic-string": "^0.25.7" - }, - "peerDependencies": { - "rollup": "^1.20.0 || ^2.0.0" - } + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@rollup/pluginutils/node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", - "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dependencies": { - "type-detect": "4.0.8" - } + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@surma/rollup-plugin-off-main-thread": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", - "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", - "dependencies": { - "ejs": "^3.1.6", - "json5": "^2.2.0", - "magic-string": "^0.25.0", - "string.prototype.matchall": "^4.0.6" - } + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@svgr/babel-plugin-add-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", - "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", - "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@svgr/babel-plugin-svg-dynamic-title": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", - "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@svgr/babel-plugin-svg-em-dimensions": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", - "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@svgr/babel-plugin-transform-react-native-svg": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", - "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@svgr/babel-plugin-transform-svg-component": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", - "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@svgr/babel-preset": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", - "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", - "dependencies": { - "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", - "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", - "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", - "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", - "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", - "@svgr/babel-plugin-transform-svg-component": "^5.5.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@svgr/core": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", - "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", - "dependencies": { - "@svgr/plugin-jsx": "^5.5.0", - "camelcase": "^6.2.0", - "cosmiconfig": "^7.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@svgr/hast-util-to-babel-ast": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", - "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", - "dependencies": { - "@babel/types": "^7.12.6" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] }, - "node_modules/@svgr/plugin-jsx": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", - "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", - "dependencies": { - "@babel/core": "^7.12.3", - "@svgr/babel-preset": "^5.5.0", - "@svgr/hast-util-to-babel-ast": "^5.5.0", - "svg-parser": "^2.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/@svgr/plugin-svgo": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", - "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", - "dependencies": { - "cosmiconfig": "^7.0.0", - "deepmerge": "^4.2.2", - "svgo": "^1.2.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@svgr/webpack": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", - "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/plugin-transform-react-constant-elements": "^7.12.1", - "@babel/preset-env": "^7.12.1", - "@babel/preset-react": "^7.12.5", - "@svgr/core": "^5.5.0", - "@svgr/plugin-jsx": "^5.5.0", - "@svgr/plugin-svgo": "^5.5.0", - "loader-utils": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@testing-library/dom": { "version": "8.19.0", @@ -4577,164 +2027,32 @@ "node": ">=12" } }, - "node_modules/@testing-library/dom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@testing-library/dom/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@testing-library/dom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/dom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@testing-library/dom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/dom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@testing-library/jest-dom": { - "version": "5.16.5", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", - "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", "dev": true, + "license": "MIT", "dependencies": { - "@adobe/css-tools": "^4.0.1", - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", + "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", - "chalk": "^3.0.0", "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.5.6", - "lodash": "^4.17.15", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", "redent": "^3.0.0" }, "engines": { - "node": ">=8", + "node": ">=14", "npm": ">=6", "yarn": ">=1" } }, - "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@testing-library/jest-dom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/@testing-library/react": { "version": "13.4.0", @@ -4754,22 +2072,6 @@ "react-dom": "^18.0.0" } }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@types/aria-query": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", @@ -4777,12 +2079,14 @@ "dev": true }, "node_modules/@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" @@ -4792,6 +2096,7 @@ "version": "7.6.4", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, "dependencies": { "@babel/types": "^7.0.0" } @@ -4800,6 +2105,7 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -4809,95 +2115,26 @@ "version": "7.18.2", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.2.tgz", "integrity": "sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg==", + "dev": true, "dependencies": { "@babel/types": "^7.3.0" } }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, + "license": "MIT", "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", - "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", - "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "node_modules/@types/eslint": { - "version": "8.4.9", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.9.tgz", - "integrity": "sha512-jFCSo4wJzlHQLCpceUhUnXdrPuCNOjGFMQ8Eg6JXxlz3QaCKOb7eGi2cephQdM4XTYsNej69P9JDJ1zqNIbncQ==", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" + "@types/trusted-types": "*" } }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" - }, - "node_modules/@types/express": { - "version": "4.17.14", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz", - "integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.31", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", - "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dependencies": { - "@types/node": "*" - } + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true }, "node_modules/@types/history": { "version": "4.7.11", @@ -4914,76 +2151,6 @@ "hoist-non-react-statics": "^3.3.0" } }, - "node_modules/@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" - }, - "node_modules/@types/http-proxy": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", - "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.0.tgz", - "integrity": "sha512-KO7bPV21d65PKwv3LLsD8Jn3E05pjNjRZvkm+YTacWhVmykAb07wW6IkZUmQAltwQafNcDUEUrMO2h3jeBSisg==", - "dev": true, - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/jest/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@types/jest/node_modules/pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@types/jquery": { "version": "3.5.14", "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz", @@ -4996,12 +2163,8 @@ "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true }, "node_modules/@types/lodash": { "version": "4.14.186", @@ -5009,11 +2172,6 @@ "integrity": "sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==", "dev": true }, - "node_modules/@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" - }, "node_modules/@types/node": { "version": "18.11.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.7.tgz", @@ -5024,31 +2182,11 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, - "node_modules/@types/prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==" - }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, - "node_modules/@types/q": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", - "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, "node_modules/@types/react": { "version": "18.0.24", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.24.tgz", @@ -5145,19 +2283,6 @@ "redux": "^3.6.0 || ^4.0.0" } }, - "node_modules/@types/resolve": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", - "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" - }, "node_modules/@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", @@ -5166,24 +2291,8 @@ "node_modules/@types/semver": { "version": "7.3.13", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==" - }, - "node_modules/@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", - "dependencies": { - "@types/mime": "*", - "@types/node": "*" - } + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true }, "node_modules/@types/sizzle": { "version": "2.3.3", @@ -5191,63 +2300,23 @@ "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", "dev": true }, - "node_modules/@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" - }, - "node_modules/@types/testing-library__jest-dom": { - "version": "5.14.5", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", - "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", - "dev": true, - "dependencies": { - "@types/jest": "*" - } - }, "node_modules/@types/trusted-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", - "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "devOptional": true, + "license": "MIT" }, "node_modules/@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" }, - "node_modules/@types/ws": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", - "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yargs": { - "version": "17.0.13", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz", - "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.41.0.tgz", "integrity": "sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA==", + "dev": true, "dependencies": { "@typescript-eslint/scope-manager": "5.41.0", "@typescript-eslint/type-utils": "5.41.0", @@ -5279,6 +2348,7 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -5289,28 +2359,11 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.41.0.tgz", - "integrity": "sha512-/qxT2Kd2q/A22JVIllvws4rvc00/3AT4rAo/0YgEN28y+HPhbJbk6X4+MAHEoZzpNyAOugIT7D/OLnKBW8FfhA==", - "dependencies": { - "@typescript-eslint/utils": "5.41.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, "node_modules/@typescript-eslint/parser": { "version": "5.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.41.0.tgz", "integrity": "sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA==", + "dev": true, "dependencies": { "@typescript-eslint/scope-manager": "5.41.0", "@typescript-eslint/types": "5.41.0", @@ -5337,6 +2390,7 @@ "version": "5.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.41.0.tgz", "integrity": "sha512-xOxPJCnuktUkY2xoEZBKXO5DBCugFzjrVndKdUnyQr3+9aDWZReKq9MhaoVnbL+maVwWJu/N0SEtrtEUNb62QQ==", + "dev": true, "dependencies": { "@typescript-eslint/types": "5.41.0", "@typescript-eslint/visitor-keys": "5.41.0" @@ -5353,6 +2407,7 @@ "version": "5.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.41.0.tgz", "integrity": "sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA==", + "dev": true, "dependencies": { "@typescript-eslint/typescript-estree": "5.41.0", "@typescript-eslint/utils": "5.41.0", @@ -5379,6 +2434,7 @@ "version": "5.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.41.0.tgz", "integrity": "sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA==", + "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -5391,6 +2447,7 @@ "version": "5.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.41.0.tgz", "integrity": "sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg==", + "dev": true, "dependencies": { "@typescript-eslint/types": "5.41.0", "@typescript-eslint/visitor-keys": "5.41.0", @@ -5417,6 +2474,7 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -5431,6 +2489,7 @@ "version": "5.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.41.0.tgz", "integrity": "sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ==", + "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", @@ -5456,6 +2515,7 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -5470,6 +2530,7 @@ "version": "5.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.41.0.tgz", "integrity": "sha512-vilqeHj267v8uzzakbm13HkPMl7cbYpKVjgFWZPIOHIJHZtinvypUhJ5xBXfWYg4eFKqztbMMpOgFpT9Gfx4fw==", + "dev": true, "dependencies": { "@typescript-eslint/types": "5.41.0", "eslint-visitor-keys": "^3.3.0" @@ -5482,168 +2543,237 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" - }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" }, "engines": { - "node": ">= 0.6" + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitejs/plugin-react/node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.1.tgz", + "integrity": "sha512-6YeRZwuO4oTGKxD3bijok756oktHSIm3eczVVzNe3scqzuhLwltIF3S9ZL/vwOVIpURmU6SnZhziXXAfw8/Qlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.4", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.4", + "istanbul-reports": "^3.1.6", + "magic-string": "^0.30.5", + "magicast": "^0.3.3", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "test-exclude": "^6.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "1.6.1" + } + }, + "node_modules/@vitest/expect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", + "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", + "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "1.6.1", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", + "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vitest/snapshot/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/spy": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", + "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vitest/utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -5651,109 +2781,32 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "dependencies": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "node_modules/acorn-node/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/address": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.1.tgz", - "integrity": "sha512-B+6bi5D34+fDYENiH5qOlA0cV2rAGKuWZ9LeyUUehbXy8e0VS9e498yO0Jeeh+iM+6KbfudHTFjXw2MmJD4QRA==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/adjust-sourcemap-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", - "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", - "dependencies": { - "loader-utils": "^2.0.0", - "regex-parser": "^2.2.11" - }, - "engines": { - "node": ">=8.9" - } - }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5765,118 +2818,37 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "engines": [ - "node >= 0.8.0" - ], - "bin": { - "ansi-html": "bin/ansi-html" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { "node": ">=8" } }, "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "node": ">=8" }, - "engines": { - "node": ">= 8" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" - }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dependencies": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" }, "node_modules/aria-query": { "version": "5.1.2", @@ -5887,148 +2859,31 @@ "deep-equal": "^2.0.5" } }, - "node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" - }, - "node_modules/array-includes": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", - "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, "engines": { "node": ">=8" } }, - "node_modules/array.prototype.flat": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", - "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" - }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "*" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", - "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.reduce": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", - "integrity": "sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" - }, - "node_modules/ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" - }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.13", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", - "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - } - ], - "dependencies": { - "browserslist": "^4.21.4", - "caniuse-lite": "^1.0.30001426", - "fraction.js": "^4.2.0", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" }, "node_modules/available-typed-arrays": { "version": "1.0.5", @@ -6042,191 +2897,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axe-core": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.5.0.tgz", - "integrity": "sha512-4+rr8eQ7+XXS5nZrKcMO/AikHL0hVqy+lHWAnE3xdHl+aguag8SOQ6eEqLexwLNWgXIMfunGuD3ON1/6Kyet0A==", - "engines": { - "node": ">=4" - } - }, - "node_modules/axobject-query": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", - "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==" - }, - "node_modules/babel-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", - "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", - "dependencies": { - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.5.1", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-jest/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/babel-jest/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/babel-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-loader": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", - "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", - "dependencies": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 8.9" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "webpack": ">=2" - } - }, - "node_modules/babel-loader/node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", - "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -6241,243 +2911,28 @@ "npm": ">=6" } }, - "node_modules/babel-plugin-named-asset-import": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", - "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==", - "peerDependencies": { - "@babel/core": "^7.1.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", - "dependencies": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-transform-react-remove-prop-types": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", - "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==" - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", - "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", - "dependencies": { - "babel-plugin-jest-hoist": "^27.5.1", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-react-app": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz", - "integrity": "sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==", - "dependencies": { - "@babel/core": "^7.16.0", - "@babel/plugin-proposal-class-properties": "^7.16.0", - "@babel/plugin-proposal-decorators": "^7.16.4", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", - "@babel/plugin-proposal-numeric-separator": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.0", - "@babel/plugin-proposal-private-methods": "^7.16.0", - "@babel/plugin-transform-flow-strip-types": "^7.16.0", - "@babel/plugin-transform-react-display-name": "^7.16.0", - "@babel/plugin-transform-runtime": "^7.16.4", - "@babel/preset-env": "^7.16.4", - "@babel/preset-react": "^7.16.0", - "@babel/preset-typescript": "^7.16.0", - "@babel/runtime": "^7.16.3", - "babel-plugin-macros": "^3.1.0", - "babel-plugin-transform-react-remove-prop-types": "^0.4.24" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" }, "node_modules/baseline-browser-mapping": { "version": "2.9.19", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, "bin": { "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" - }, - "node_modules/bfj": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.0.2.tgz", - "integrity": "sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw==", - "dependencies": { - "bluebird": "^3.5.5", - "check-types": "^11.1.1", - "hoopy": "^0.1.4", - "tryer": "^1.0.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/bonjour-service": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.14.tgz", - "integrity": "sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ==", - "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" - }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6487,6 +2942,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -6494,15 +2950,11 @@ "node": ">=8" } }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" - }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, "funding": [ { "type": "opencollective", @@ -6531,42 +2983,21 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "engines": { - "node": ">= 0.8" + "node": ">=8" } }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -6581,6 +3012,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -6589,49 +3034,11 @@ "node": ">=6" } }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001769", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -6647,119 +3054,63 @@ } ] }, - "node_modules/case-sensitive-paths-webpack-plugin": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", - "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", + "node_modules/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chai/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/check-types": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz", - "integrity": "sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==" - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chrome-trace-event": { + "node_modules/check-error": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz", - "integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==" - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==" - }, - "node_modules/clean-css": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz", - "integrity": "sha512-lCr8OHhiWCTw4v8POJovCoh4T7I9U11yVsPjMWWnnMmp9ZowCxyad1Pathle/9HjaDp+fdQKjO9fQydE6RHTZg==", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "license": "MIT", "dependencies": { - "source-map": "~0.6.0" + "get-func-name": "^2.0.2" }, "engines": { - "node": ">= 10.0" - } - }, - "node_modules/clean-css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "node": "*" } }, "node_modules/clsx": { @@ -6770,60 +3121,32 @@ "node": ">=6" } }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 4.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==" - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" + "node": ">=7.0.0" } }, "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" - }, - "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -6831,170 +3154,25 @@ "node": ">= 0.8" } }, - "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "engines": { - "node": ">= 12" - } - }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==" - }, - "node_modules/common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" }, - "node_modules/confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==" - }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/core-js": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz", - "integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.0.tgz", - "integrity": "sha512-piOX9Go+Z4f9ZiBFLnZ5VrOpBl0h7IGCkiFUN11QTe6LjAvOT3ifL/5TdoizMh99hcGy5SoLyWbapIY/PIb/3A==", - "dependencies": { - "browserslist": "^4.21.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-pure": { - "version": "3.42.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.42.0.tgz", - "integrity": "sha512-007bM04u91fF4kMgwom2I5cQxAFIy8jVulgr9eozILl/SZE53QOqnW/+vviC+wQWLv+AunBG+8Q0TLoeSsSxRQ==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, "node_modules/cosmiconfig": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", @@ -7014,6 +3192,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -7028,434 +3207,50 @@ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/css-blank-pseudo": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", - "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", - "dependencies": { - "postcss-selector-parser": "^6.0.9" - }, - "bin": { - "css-blank-pseudo": "dist/cli.cjs" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-declaration-sorter": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", - "integrity": "sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==", - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } - }, - "node_modules/css-has-pseudo": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", - "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", - "dependencies": { - "postcss-selector-parser": "^6.0.9" - }, - "bin": { - "css-has-pseudo": "dist/cli.cjs" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-loader": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", - "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.7", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", - "postcss-modules-scope": "^3.0.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.3.5" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/css-loader/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/css-minimizer-webpack-plugin": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", - "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", - "dependencies": { - "cssnano": "^5.0.6", - "jest-worker": "^27.0.2", - "postcss": "^8.3.5", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@parcel/css": { - "optional": true - }, - "clean-css": { - "optional": true - }, - "csso": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/css-prefers-color-scheme": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", - "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", - "bin": { - "css-prefers-color-scheme": "dist/cli.cjs" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" - }, - "node_modules/css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "dependencies": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/css-tree/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", "dev": true }, - "node_modules/cssdb": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.0.2.tgz", - "integrity": "sha512-Vm4b6P/PifADu0a76H0DKRNVWq3Rq9xa/Nx6oEMUBJlwTUuZoZ3dkZxo8Gob3UEL53Cq+Ma1GBgISed6XEBs3w==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano": { - "version": "5.1.14", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.14.tgz", - "integrity": "sha512-Oou7ihiTocbKqi0J1bB+TRJIQX5RMR3JghA8hcWSw9mjBLQ5Y3RWqEDoYG3sRNlAbCIXpqMoZGbq5KDR3vdzgw==", - "dependencies": { - "cssnano-preset-default": "^5.2.13", - "lilconfig": "^2.0.3", - "yaml": "^1.10.2" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/cssnano" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/cssnano-preset-default": { - "version": "5.2.13", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.13.tgz", - "integrity": "sha512-PX7sQ4Pb+UtOWuz8A1d+Rbi+WimBIxJTRyBdgGp1J75VU0r/HFQeLnMYgHiCAp6AR4rqrc7Y4R+1Rjk3KJz6DQ==", - "dependencies": { - "css-declaration-sorter": "^6.3.1", - "cssnano-utils": "^3.1.0", - "postcss-calc": "^8.2.3", - "postcss-colormin": "^5.3.0", - "postcss-convert-values": "^5.1.3", - "postcss-discard-comments": "^5.1.2", - "postcss-discard-duplicates": "^5.1.0", - "postcss-discard-empty": "^5.1.1", - "postcss-discard-overridden": "^5.1.0", - "postcss-merge-longhand": "^5.1.7", - "postcss-merge-rules": "^5.1.3", - "postcss-minify-font-values": "^5.1.0", - "postcss-minify-gradients": "^5.1.1", - "postcss-minify-params": "^5.1.4", - "postcss-minify-selectors": "^5.2.1", - "postcss-normalize-charset": "^5.1.0", - "postcss-normalize-display-values": "^5.1.0", - "postcss-normalize-positions": "^5.1.1", - "postcss-normalize-repeat-style": "^5.1.1", - "postcss-normalize-string": "^5.1.0", - "postcss-normalize-timing-functions": "^5.1.0", - "postcss-normalize-unicode": "^5.1.1", - "postcss-normalize-url": "^5.1.0", - "postcss-normalize-whitespace": "^5.1.1", - "postcss-ordered-values": "^5.1.3", - "postcss-reduce-initial": "^5.1.1", - "postcss-reduce-transforms": "^5.1.0", - "postcss-svgo": "^5.1.0", - "postcss-unique-selectors": "^5.1.1" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/cssnano-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", - "dependencies": { - "css-tree": "^1.1.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" - }, - "node_modules/csso/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" - }, "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", "dependencies": { - "cssom": "~0.3.6" + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" }, "node_modules/csstype": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" - }, "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/debug": { @@ -7475,14 +3270,24 @@ } }, "node_modules/decimal.js": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.2.tgz", - "integrity": "sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==" + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==" + "node_modules/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } }, "node_modules/deep-equal": { "version": "2.0.5", @@ -7513,31 +3318,15 @@ "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">= 10" - } + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -7550,18 +3339,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "engines": { - "node": ">=8" - } - }, "node_modules/define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, "dependencies": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" @@ -7573,97 +3355,16 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/defined": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", - "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" - }, - "node_modules/detect-port-alt": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", - "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", - "dependencies": { - "address": "^1.0.1", - "debug": "^2.6.0" - }, - "bin": { - "detect": "bin/detect-port", - "detect-port": "bin/detect-port" - }, - "engines": { - "node": ">= 4.2.1" - } - }, - "node_modules/detect-port-alt/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/detect-port-alt/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/detective": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", - "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", - "dependencies": { - "acorn-node": "^1.8.2", - "defined": "^1.0.0", - "minimist": "^1.2.6" - }, - "bin": { - "detective": "bin/detective.js" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/dexie": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/dexie/-/dexie-3.2.2.tgz", @@ -7672,16 +3373,12 @@ "node": ">=6.0" } }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" - }, "node_modules/diff-sequences": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.2.0.tgz", - "integrity": "sha512-413SY5JpYeSBZxmenGEmCVQ8mCgtFJF0w9PROdaS6z987XC2Pd2GOKqOITLtMftmyFZqgtCOb/QA7/Z3ZXfzIw==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, + "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -7690,6 +3387,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, "dependencies": { "path-type": "^4.0.0" }, @@ -7697,31 +3395,12 @@ "node": ">=8" } }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" - }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==" - }, - "node_modules/dns-packet": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", - "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -7735,14 +3414,6 @@ "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==", "dev": true }, - "node_modules/dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "dependencies": { - "utila": "~0.4" - } - }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -7752,178 +3423,35 @@ "csstype": "^3.0.2" } }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + "node_modules/dompurify": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", + "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" } }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", "dependencies": { - "webidl-conversions": "^5.0.0" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", - "engines": { - "node": ">=10" - } - }, - "node_modules/dotenv-expand": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", - "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, "node_modules/electron-to-chromium": { "version": "1.5.286", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==" - }, - "node_modules/emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", - "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "dev": true }, "node_modules/error-ex": { "version": "1.3.2", @@ -7933,18 +3461,11 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "dependencies": { - "stackframe": "^1.3.4" - } - }, "node_modules/es-abstract": { "version": "1.20.4", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", @@ -7978,18 +3499,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" - }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -7998,6 +3513,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "engines": { "node": ">= 0.4" } @@ -8021,23 +3537,40 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==" - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", "dependencies": { - "has": "^1.0.3" + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -8055,19 +3588,54 @@ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "engines": { "node": ">=6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -8079,134 +3647,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/escodegen/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/escodegen/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/eslint": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz", - "integrity": "sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", "dependencies": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.15.0", - "grapheme-splitter": "^1.0.4", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -8219,294 +3704,11 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-config-react-app": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", - "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", - "dependencies": { - "@babel/core": "^7.16.0", - "@babel/eslint-parser": "^7.16.3", - "@rushstack/eslint-patch": "^1.1.0", - "@typescript-eslint/eslint-plugin": "^5.5.0", - "@typescript-eslint/parser": "^5.5.0", - "babel-preset-react-app": "^10.0.1", - "confusing-browser-globals": "^1.0.11", - "eslint-plugin-flowtype": "^8.0.3", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jest": "^25.3.0", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.27.1", - "eslint-plugin-react-hooks": "^4.3.0", - "eslint-plugin-testing-library": "^5.0.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "eslint": "^8.0.0" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "dependencies": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-flowtype": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", - "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", - "dependencies": { - "lodash": "^4.17.21", - "string-natural-compare": "^3.0.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@babel/plugin-syntax-flow": "^7.14.5", - "@babel/plugin-transform-react-jsx": "^7.14.9", - "eslint": "^8.1.0" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", - "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", - "dependencies": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.3", - "has": "^1.0.3", - "is-core-module": "^2.8.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.5", - "resolve": "^1.22.0", - "tsconfig-paths": "^3.14.1" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/eslint-plugin-jest": { - "version": "25.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", - "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", - "dependencies": { - "@typescript-eslint/experimental-utils": "^5.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^4.0.0 || ^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - }, - "jest": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz", - "integrity": "sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==", - "dependencies": { - "@babel/runtime": "^7.18.9", - "aria-query": "^4.2.2", - "array-includes": "^3.1.5", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.4.3", - "axobject-query": "^2.2.0", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "has": "^1.0.3", - "jsx-ast-utils": "^3.3.2", - "language-tags": "^1.0.5", - "minimatch": "^3.1.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "dependencies": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/eslint-plugin-react": { - "version": "7.31.10", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz", - "integrity": "sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA==", - "dependencies": { - "array-includes": "^3.1.5", - "array.prototype.flatmap": "^1.3.0", - "doctrine": "^2.1.0", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.5", - "object.fromentries": "^2.0.5", - "object.hasown": "^1.1.1", - "object.values": "^1.1.5", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.3", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-plugin-testing-library": { - "version": "5.9.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.9.1.tgz", - "integrity": "sha512-6BQp3tmb79jLLasPHJmy8DnxREe+2Pgf7L+7o09TSWPfdqqtQfRZmZNetr5mOs3yqZk/MRNxpN3RUpJe0wB4LQ==", - "dependencies": { - "@typescript-eslint/utils": "^5.13.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0", - "npm": ">=6" - }, - "peerDependencies": { - "eslint": "^7.5.0 || ^8.0.0" - } - }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -8519,6 +3721,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, "dependencies": { "eslint-visitor-keys": "^2.0.0" }, @@ -8536,270 +3739,34 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, "engines": { "node": ">=10" } }, "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint-webpack-plugin": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz", - "integrity": "sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==", - "dependencies": { - "@types/eslint": "^7.29.0 || ^8.4.1", - "jest-worker": "^28.0.2", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0", - "webpack": "^5.0.0" + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-webpack-plugin/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint-webpack-plugin/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/eslint-webpack-plugin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint-webpack-plugin/node_modules/jest-worker": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", - "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/eslint-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/eslint-webpack-plugin/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/eslint-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", - "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -8807,22 +3774,53 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" }, "engines": { - "node": ">=4" + "node": ">=10.13.0" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -8834,6 +3832,8 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -8842,6 +3842,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -8853,6 +3854,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "engines": { "node": ">=4.0" } @@ -8861,162 +3863,43 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, "engines": { "node": ">=4.0" } }, "node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.2.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.2.2.tgz", - "integrity": "sha512-hE09QerxZ5wXiOhqkXy5d2G9ar+EqOyifnCXCpMNu+vZ6DG9TJ6CO2c2kPDSLqERTTWrO7OZj8EkYHQqSd78Yw==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^29.2.2", - "jest-get-type": "^29.2.0", - "jest-matcher-utils": "^29.2.2", - "jest-message-util": "^29.2.1", - "jest-util": "^29.2.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" }, "node_modules/fast-glob": { "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -9031,59 +3914,32 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" - }, - "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ] + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" }, "node_modules/fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, "dependencies": { "reusify": "^1.0.4" } }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dependencies": { - "bser": "2.1.1" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", "dependencies": { "flat-cache": "^3.0.4" }, @@ -9091,64 +3947,11 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/file-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", - "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/filesize": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", - "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -9176,52 +3979,6 @@ "final-form": ">=1.2.0" } }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -9231,6 +3988,8 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -9243,11 +4002,14 @@ } }, "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", "dependencies": { - "flatted": "^3.1.0", + "flatted": "^3.2.9", + "keyv": "^4.5.3", "rimraf": "^3.0.2" }, "engines": { @@ -9255,28 +4017,11 @@ } }, "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" - }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" }, "node_modules/for-each": { "version": "0.3.3", @@ -9287,221 +4032,28 @@ "is-callable": "^1.1.3" } }, - "node_modules/fork-ts-checker-webpack-plugin": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz", - "integrity": "sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA==", - "dependencies": { - "@babel/code-frame": "^7.8.3", - "@types/json-schema": "^7.0.5", - "chalk": "^4.1.0", - "chokidar": "^3.4.2", - "cosmiconfig": "^6.0.0", - "deepmerge": "^4.2.2", - "fs-extra": "^9.0.0", - "glob": "^7.1.6", - "memfs": "^3.1.2", - "minimatch": "^3.0.4", - "schema-utils": "2.7.0", - "semver": "^7.3.2", - "tapable": "^1.0.0" - }, - "engines": { - "node": ">=10", - "yarn": ">=1.0.0" - }, - "peerDependencies": { - "eslint": ">= 6", - "typescript": ">= 2.7", - "vue-template-compiler": "*", - "webpack": ">= 4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - }, - "vue-template-compiler": { - "optional": true - } - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", - "dependencies": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "engines": { - "node": ">=6" - } - }, "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { "node": ">= 6" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fraction.js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", - "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://www.patreon.com/infusion" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -9511,21 +4063,20 @@ "node": ">=12" } }, - "node_modules/fs-monkey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", - "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==" - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -9546,6 +4097,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -9563,6 +4115,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -9571,28 +4124,38 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, "engines": { "node": ">=6.9.0" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": "*" } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -9601,34 +4164,25 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "engines": { - "node": ">=10" + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 0.4" } }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -9644,6 +4198,9 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -9663,6 +4220,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -9670,58 +4228,27 @@ "node": ">= 6" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" - }, - "node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -9737,12 +4264,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9751,36 +4287,15 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" - }, - "node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" - }, - "node_modules/harmony-reflect": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", - "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==" + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" }, "node_modules/has": { "version": "1.0.3", @@ -9797,22 +4312,26 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -9820,21 +4339,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -9843,11 +4353,13 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -9860,6 +4372,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -9867,14 +4380,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "bin": { - "he": "bin/he" - } - }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -9888,97 +4393,25 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/hoopy": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", - "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", "dependencies": { - "whatwg-encoding": "^1.0.5" + "whatwg-encoding": "^3.1.1" }, "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/html-entities": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", - "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==" - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" - }, - "node_modules/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": ">=12" - } + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" }, "node_modules/html-parse-stringify": { "version": "3.0.1", @@ -9988,138 +4421,32 @@ "void-elements": "3.1.0" } }, - "node_modules/html-webpack-plugin": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", - "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", - "dependencies": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/html-webpack-plugin" - }, - "peerDependencies": { - "webpack": "^5.20.0" - } - }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/http-proxy-middleware": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", - "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } + "node": ">= 14" } }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "6", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "engines": { - "node": ">=10.17.0" + "node": ">= 14" } }, "node_modules/husky": { @@ -10179,6 +4506,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -10186,50 +4514,15 @@ "node": ">=0.10.0" } }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/idb": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.0.tgz", - "integrity": "sha512-Wsk07aAxDsntgYJY4h0knZJuTxM73eQ4reRAO+Z1liOh8eMCJ/MoDS8fCui1vGT9mnjtl1sOu3I2i/W1swPYZg==" - }, - "node_modules/identity-obj-proxy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", - "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", - "dependencies": { - "harmony-reflect": "^1.4.6" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, "engines": { "node": ">= 4" } }, - "node_modules/immer": { - "version": "9.0.16", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.16.tgz", - "integrity": "sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -10245,28 +4538,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -10284,6 +4561,9 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -10292,17 +4572,15 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" }, "node_modules/internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, "dependencies": { "get-intrinsic": "^1.1.0", "has": "^1.0.3", @@ -10331,14 +4609,6 @@ "loose-envify": "^1.0.0" } }, - "node_modules/ipaddr.js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", - "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", - "engines": { - "node": ">= 10" - } - }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -10364,6 +4634,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, "dependencies": { "has-bigints": "^1.0.1" }, @@ -10371,21 +4642,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -10401,6 +4662,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -10423,6 +4685,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -10433,48 +4696,20 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "engines": { - "node": ">=6" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -10491,15 +4726,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==" - }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -10511,6 +4742,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "engines": { "node": ">=0.12.0" } @@ -10519,6 +4751,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -10529,45 +4762,21 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true }, "node_modules/is-promise": { "version": "2.2.2", @@ -10578,6 +4787,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -10589,22 +4799,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-root": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", - "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", - "engines": { - "node": ">=6" - } - }, "node_modules/is-set": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", @@ -10618,6 +4812,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -10625,21 +4820,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -10654,6 +4839,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -10683,11 +4869,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" - }, "node_modules/is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", @@ -10701,6 +4882,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -10721,17 +4903,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -10741,88 +4912,55 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=8" } }, "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, "engines": { "node": ">=10" } }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -10831,3427 +4969,58 @@ "node": ">=8" } }, - "node_modules/jake": { - "version": "10.8.5", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", - "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jake/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jake/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jake/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jake/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jake/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jake/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", - "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", - "dependencies": { - "@jest/core": "^27.5.1", - "import-local": "^3.0.2", - "jest-cli": "^27.5.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", - "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", - "dependencies": { - "@jest/types": "^27.5.1", - "execa": "^5.0.0", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-changed-files/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-changed-files/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-changed-files/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-changed-files/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-changed-files/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-changed-files/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-changed-files/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", - "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.5.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-circus/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-circus/node_modules/diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "dependencies": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", - "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", - "dependencies": { - "@jest/core": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "prompts": "^2.0.1", - "yargs": "^16.2.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-cli/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-cli/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", - "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", - "dependencies": { - "@babel/core": "^7.8.0", - "@jest/test-sequencer": "^27.5.1", - "@jest/types": "^27.5.1", - "babel-jest": "^27.5.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.9", - "jest-circus": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-jasmine2": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-config/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-config/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.2.1.tgz", - "integrity": "sha512-gfh/SMNlQmP3MOUgdzxPOd4XETDJifADpT937fN1iUGz+9DgOu2eUPHH25JDkLVcLwwqxv3GzVyK4VBUr9fjfA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.2.0", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.2.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-diff/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-docblock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", - "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", - "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", - "dependencies": { - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-each/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-each/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-each/node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", - "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1", - "jsdom": "^16.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-environment-jsdom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-environment-jsdom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-jsdom/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-node": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", - "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-environment-node/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-environment-node/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-environment-node/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-environment-node/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-node/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-get-type": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", - "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-haste-map/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-haste-map/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-haste-map/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-haste-map/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-haste-map/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-haste-map/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-jasmine2": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", - "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.5.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-jasmine2/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-jasmine2/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-jasmine2/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-jasmine2/node_modules/diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "dependencies": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-jasmine2/node_modules/jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-leak-detector": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", - "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", - "dependencies": { - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-leak-detector/node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.2.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.2.2.tgz", - "integrity": "sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.2.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.2.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.2.1.tgz", - "integrity": "sha512-Dx5nEjw9V8C1/Yj10S/8ivA8F439VS8vTq1L7hEgwHFn9ovSKNpYW/kwNh7UglaEgXO42XxzKJB+2x0nSglFVw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.2.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.2.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-mock/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-mock/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-mock/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-mock/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-mock/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-mock/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", - "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", - "dependencies": { - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", - "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", - "dependencies": { - "@jest/types": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-snapshot": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-resolve-dependencies/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-resolve/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-resolve/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", - "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", - "dependencies": { - "@jest/console": "^27.5.1", - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-leak-detector": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runner/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runner/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runner/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-runner/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runner/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", - "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/globals": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runtime/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-runtime/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runtime/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runtime/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-serializer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", - "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", - "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", - "dependencies": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^27.5.1", - "graceful-fs": "^4.2.9", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^27.5.1", - "semver": "^7.3.2" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-snapshot/node_modules/diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "dependencies": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.2.1.tgz", - "integrity": "sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g==", - "dev": true, - "dependencies": { - "@jest/types": "^29.2.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", - "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", - "dependencies": { - "@jest/types": "^27.5.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "leven": "^3.1.0", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-validate/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-validate/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate/node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watch-typeahead": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-1.1.0.tgz", - "integrity": "sha512-Va5nLSJTN7YFtC2jd+7wsoe1pNe5K4ShLux/E5iHEwlB9AxaxmggY7to9KUqKojhaJw3aXqt5WAb4jGPOolpEw==", - "dependencies": { - "ansi-escapes": "^4.3.1", - "chalk": "^4.0.0", - "jest-regex-util": "^28.0.0", - "jest-watcher": "^28.0.0", - "slash": "^4.0.0", - "string-length": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "jest": "^27.0.0 || ^28.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/@jest/console": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/@jest/console/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watch-typeahead/node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", - "dependencies": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", - "dependencies": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-watch-typeahead/node_modules/emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-message-util/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-watcher": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", - "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", - "dependencies": { - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^28.1.3", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watch-typeahead/node_modules/pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watch-typeahead/node_modules/string-length": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", - "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", - "dependencies": { - "char-regex": "^2.0.0", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watch-typeahead/node_modules/string-length/node_modules/char-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz", - "integrity": "sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==", - "engines": { - "node": ">=12.20" - } - }, - "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", - "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", - "dependencies": { - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^27.5.1", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-watcher/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-watcher/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watcher/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/jest-watcher/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-watcher/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-sdsl": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", - "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "version": "24.1.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.3.tgz", + "integrity": "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==", + "dev": true, + "license": "MIT", "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" + "tough-cookie": "^4.1.4", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "peerDependencies": { - "canvas": "^2.5.0" + "canvas": "^2.11.2" }, "peerDependenciesMeta": { "canvas": { @@ -14260,40 +5029,48 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "bin": { "json5": "lib/cli.js" }, @@ -14305,6 +5082,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, "dependencies": { "universalify": "^2.0.0" }, @@ -14312,75 +5090,22 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsonpointer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", - "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jsx-ast-utils": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", - "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", "dependencies": { - "array-includes": "^3.1.5", - "object.assign": "^4.1.3" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "engines": { - "node": ">=6" - } - }, - "node_modules/klona": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", - "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/language-subtag-registry": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", - "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==" - }, - "node_modules/language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", - "dependencies": { - "language-subtag-registry": "~0.3.2" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "engines": { - "node": ">=6" + "json-buffer": "3.0.1" } }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -14389,48 +5114,34 @@ "node": ">= 0.8.0" } }, - "node_modules/lilconfig": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", - "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", - "engines": { - "node": ">=10" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, - "node_modules/loader-runner": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", - "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, "engines": { - "node": ">=6.11.5" + "node": ">=14" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" + "url": "https://github.com/sponsors/antfu" } }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -14446,30 +5157,12 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" }, "node_modules/long": { "version": "5.2.0", @@ -14487,18 +5180,21 @@ "loose-envify": "cli.js" } }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.0.3" + "get-func-name": "^2.0.1" } }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -14516,57 +5212,64 @@ } }, "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", "dependencies": { - "sourcemap-codec": "^1.4.8" + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" } }, "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", "dependencies": { - "semver": "^6.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memfs": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.9.tgz", - "integrity": "sha512-3rm8kbrzpUGRyPKSGuk387NZOwQ90O4rI9tsWQkzNW7BLSnKGp23RsEsKK8N8QVCrtJoAMqy3spxHC4os4G6PQ==", - "dependencies": { - "fs-monkey": "^1.0.3" + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">= 4.0.0" + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, "node_modules/memoize-one": { @@ -14574,39 +5277,26 @@ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, "engines": { "node": ">= 8" } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -14615,21 +5305,11 @@ "node": ">=8.6" } }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -14638,6 +5318,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -14645,14 +5326,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -14662,82 +5335,12 @@ "node": ">=4" } }, - "node_modules/mini-css-extract-plugin": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz", - "integrity": "sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg==", - "dependencies": { - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -14745,46 +5348,36 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, "node_modules/nanoid": { "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, "funding": [ { "type": "github", @@ -14801,101 +5394,22 @@ "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true }, "node_modules/nwsapi": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", - "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==" + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", + "dev": true, + "license": "MIT" }, "node_modules/object-assign": { "version": "4.1.1", @@ -14905,18 +5419,11 @@ "node": ">=0.10.0" } }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "engines": { - "node": ">= 6" - } - }, "node_modules/object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -14944,6 +5451,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, "engines": { "node": ">= 0.4" } @@ -14952,6 +5460,7 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -14965,153 +5474,29 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.entries": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", - "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", - "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", - "integrity": "sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==", - "dependencies": { - "array.prototype.reduce": "^1.0.4", - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.hasown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", - "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", - "dependencies": { - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", "dependencies": { "wrappy": "1" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", - "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -15121,6 +5506,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -15135,6 +5522,8 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -15145,35 +5534,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -15202,37 +5562,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse-srcset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", - "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" - }, "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -15241,6 +5602,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -15249,6 +5612,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "engines": { "node": ">=8" } @@ -15258,11 +5622,6 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -15271,10 +5630,22 @@ "node": ">=8" } }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } }, "node_modules/picocolors": { "version": "1.1.1", @@ -15285,6 +5656,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "engines": { "node": ">=8.6" }, @@ -15292,152 +5664,30 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" } }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-up/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-up/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "engines": { - "node": ">=4" - } + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" }, "node_modules/postcss": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, "funding": [ { "type": "opencollective", @@ -15461,1193 +5711,21 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-attribute-case-insensitive": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", - "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-browser-comments": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz", - "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==", - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "browserslist": ">=4", - "postcss": ">=8" - } - }, - "node_modules/postcss-calc": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", - "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", - "dependencies": { - "postcss-selector-parser": "^6.0.9", - "postcss-value-parser": "^4.2.0" - }, - "peerDependencies": { - "postcss": "^8.2.2" - } - }, - "node_modules/postcss-clamp": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", - "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=7.6.0" - }, - "peerDependencies": { - "postcss": "^8.4.6" - } - }, - "node_modules/postcss-color-functional-notation": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", - "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-color-hex-alpha": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", - "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-color-rebeccapurple": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", - "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-colormin": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.0.tgz", - "integrity": "sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==", - "dependencies": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0", - "colord": "^2.9.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-convert-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", - "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", - "dependencies": { - "browserslist": "^4.21.4", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-custom-media": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", - "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.3" - } - }, - "node_modules/postcss-custom-properties": { - "version": "12.1.10", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.10.tgz", - "integrity": "sha512-U3BHdgrYhCrwTVcByFHs9EOBoqcKq4Lf3kXwbTi4hhq0qWhl/pDWq2THbv/ICX/Fl9KqeHBb8OVrTf2OaYF07A==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-custom-selectors": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", - "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", - "dependencies": { - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.3" - } - }, - "node_modules/postcss-dir-pseudo-class": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", - "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-discard-comments": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", - "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-duplicates": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-empty": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-overridden": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-double-position-gradients": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", - "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-env-function": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", - "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-flexbugs-fixes": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz", - "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==", - "peerDependencies": { - "postcss": "^8.1.4" - } - }, - "node_modules/postcss-focus-visible": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", - "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", - "dependencies": { - "postcss-selector-parser": "^6.0.9" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-within": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", - "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", - "dependencies": { - "postcss-selector-parser": "^6.0.9" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-font-variant": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-gap-properties": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", - "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-image-set-function": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", - "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-import": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", - "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-initial": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", - "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz", - "integrity": "sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.3.3" - } - }, - "node_modules/postcss-lab-function": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", - "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", - "dependencies": { - "lilconfig": "^2.0.5", - "yaml": "^1.10.2" - }, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-loader": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", - "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", - "dependencies": { - "cosmiconfig": "^7.0.0", - "klona": "^2.0.5", - "semver": "^7.3.5" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" - } - }, - "node_modules/postcss-loader/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/postcss-logical": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", - "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", - "engines": { - "node": "^12 || ^14 || >=16" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-media-minmax": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", - "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-merge-longhand": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", - "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^5.1.1" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-merge-rules": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.3.tgz", - "integrity": "sha512-LbLd7uFC00vpOuMvyZop8+vvhnfRGpp2S+IMQKeuOZZapPRY4SMq5ErjQeHbHsjCUgJkRNrlU+LmxsKIqPKQlA==", - "dependencies": { - "browserslist": "^4.21.4", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^3.1.0", - "postcss-selector-parser": "^6.0.5" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-font-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", - "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-gradients": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", - "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", - "dependencies": { - "colord": "^2.9.1", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-params": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", - "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", - "dependencies": { - "browserslist": "^4.21.4", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-selectors": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", - "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", - "dependencies": { - "postcss-selector-parser": "^6.0.5" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", - "dependencies": { - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-nested": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", - "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-nesting": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", - "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", - "dependencies": { - "@csstools/selector-specificity": "^2.0.0", - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-normalize": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-10.0.1.tgz", - "integrity": "sha512-+5w18/rDev5mqERcG3W5GZNMJa1eoYYNGo8gB7tEwaos0ajk3ZXAI4mHGcNT47NE+ZnZD1pEpUOFLvltIwmeJA==", - "dependencies": { - "@csstools/normalize.css": "*", - "postcss-browser-comments": "^4", - "sanitize.css": "*" - }, - "engines": { - "node": ">= 12" - }, - "peerDependencies": { - "browserslist": ">= 4", - "postcss": ">= 8" - } - }, - "node_modules/postcss-normalize-charset": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-display-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", - "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-positions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", - "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-repeat-style": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", - "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-string": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", - "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-timing-functions": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", - "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-unicode": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", - "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", - "dependencies": { - "browserslist": "^4.21.4", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", - "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", - "dependencies": { - "normalize-url": "^6.0.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-whitespace": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", - "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-opacity-percentage": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.2.tgz", - "integrity": "sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w==", - "funding": [ - { - "type": "kofi", - "url": "https://ko-fi.com/mrcgrtz" - }, - { - "type": "liberapay", - "url": "https://liberapay.com/mrcgrtz" - } - ], - "engines": { - "node": "^12 || ^14 || >=16" - } - }, - "node_modules/postcss-ordered-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", - "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", - "dependencies": { - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-overflow-shorthand": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", - "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-page-break": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", - "peerDependencies": { - "postcss": "^8" - } - }, - "node_modules/postcss-place": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", - "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-preset-env": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.2.tgz", - "integrity": "sha512-rSMUEaOCnovKnwc5LvBDHUDzpGP+nrUeWZGWt9M72fBvckCi45JmnJigUr4QG4zZeOHmOCNCZnd2LKDvP++ZuQ==", - "dependencies": { - "@csstools/postcss-cascade-layers": "^1.1.0", - "@csstools/postcss-color-function": "^1.1.1", - "@csstools/postcss-font-format-keywords": "^1.0.1", - "@csstools/postcss-hwb-function": "^1.0.2", - "@csstools/postcss-ic-unit": "^1.0.1", - "@csstools/postcss-is-pseudo-class": "^2.0.7", - "@csstools/postcss-nested-calc": "^1.0.0", - "@csstools/postcss-normalize-display-values": "^1.0.1", - "@csstools/postcss-oklab-function": "^1.1.1", - "@csstools/postcss-progressive-custom-properties": "^1.3.0", - "@csstools/postcss-stepped-value-functions": "^1.0.1", - "@csstools/postcss-text-decoration-shorthand": "^1.0.0", - "@csstools/postcss-trigonometric-functions": "^1.0.2", - "@csstools/postcss-unset-value": "^1.0.2", - "autoprefixer": "^10.4.11", - "browserslist": "^4.21.3", - "css-blank-pseudo": "^3.0.3", - "css-has-pseudo": "^3.0.4", - "css-prefers-color-scheme": "^6.0.3", - "cssdb": "^7.0.1", - "postcss-attribute-case-insensitive": "^5.0.2", - "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^4.2.4", - "postcss-color-hex-alpha": "^8.0.4", - "postcss-color-rebeccapurple": "^7.1.1", - "postcss-custom-media": "^8.0.2", - "postcss-custom-properties": "^12.1.9", - "postcss-custom-selectors": "^6.0.3", - "postcss-dir-pseudo-class": "^6.0.5", - "postcss-double-position-gradients": "^3.1.2", - "postcss-env-function": "^4.0.6", - "postcss-focus-visible": "^6.0.4", - "postcss-focus-within": "^5.0.4", - "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^3.0.5", - "postcss-image-set-function": "^4.0.7", - "postcss-initial": "^4.0.1", - "postcss-lab-function": "^4.2.1", - "postcss-logical": "^5.0.4", - "postcss-media-minmax": "^5.0.0", - "postcss-nesting": "^10.2.0", - "postcss-opacity-percentage": "^1.1.2", - "postcss-overflow-shorthand": "^3.0.4", - "postcss-page-break": "^3.0.4", - "postcss-place": "^7.0.5", - "postcss-pseudo-class-any-link": "^7.1.6", - "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^6.0.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-pseudo-class-any-link": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", - "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-reduce-initial": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.1.tgz", - "integrity": "sha512-//jeDqWcHPuXGZLoolFrUXBDyuEGbr9S2rMo19bkTIjBQ4PqkaO+oI8wua5BOUxpfi97i3PCoInsiFIEBfkm9w==", - "dependencies": { - "browserslist": "^4.21.4", - "caniuse-api": "^3.0.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-reduce-transforms": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", - "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-replace-overflow-wrap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", - "peerDependencies": { - "postcss": "^8.0.3" - } - }, - "node_modules/postcss-selector-not": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", - "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^12 || ^14 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-svgo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", - "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "svgo": "^2.7.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-svgo/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/postcss-svgo/node_modules/css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/postcss-svgo/node_modules/mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" - }, - "node_modules/postcss-svgo/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-svgo/node_modules/svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", - "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/postcss-unique-selectors": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", - "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", - "dependencies": { - "postcss-selector-parser": "^6.0.5" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } - }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -16661,6 +5739,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, "engines": { "node": ">=10" }, @@ -16671,32 +5750,8 @@ "node_modules/pretty-format/node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/promise": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "dependencies": { - "asap": "~2.0.6" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true }, "node_modules/prop-types": { "version": "15.8.1", @@ -16736,71 +5791,41 @@ "node": ">=12.0.0" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" + "punycode": "^2.3.1" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" } }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -16816,74 +5841,6 @@ } ] }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "dependencies": { - "performance-now": "^2.1.0" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -16895,128 +5852,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-app-polyfill": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", - "integrity": "sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w==", - "dependencies": { - "core-js": "^3.19.2", - "object-assign": "^4.1.1", - "promise": "^8.1.0", - "raf": "^3.4.1", - "regenerator-runtime": "^0.13.9", - "whatwg-fetch": "^3.6.2" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/react-dev-utils": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", - "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", - "dependencies": { - "@babel/code-frame": "^7.16.0", - "address": "^1.1.2", - "browserslist": "^4.18.1", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.3", - "detect-port-alt": "^1.1.6", - "escape-string-regexp": "^4.0.0", - "filesize": "^8.0.6", - "find-up": "^5.0.0", - "fork-ts-checker-webpack-plugin": "^6.5.0", - "global-modules": "^2.0.0", - "globby": "^11.0.4", - "gzip-size": "^6.0.0", - "immer": "^9.0.7", - "is-root": "^2.1.0", - "loader-utils": "^3.2.0", - "open": "^8.4.0", - "pkg-up": "^3.1.0", - "prompts": "^2.4.2", - "react-error-overlay": "^6.0.11", - "recursive-readdir": "^2.2.2", - "shell-quote": "^1.7.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/react-dev-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/react-dev-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/react-dev-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/react-dev-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/react-dev-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/react-dev-utils/node_modules/loader-utils": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", - "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/react-dev-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -17029,11 +5864,6 @@ "react": "^18.2.0" } }, - "node_modules/react-error-overlay": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", - "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" - }, "node_modules/react-final-form": { "version": "6.5.9", "resolved": "https://registry.npmjs.org/react-final-form/-/react-final-form-6.5.9.tgz", @@ -17128,14 +5958,6 @@ } } }, - "node_modules/react-refresh": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", - "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react-router": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.2.tgz", @@ -17166,92 +5988,6 @@ "react-dom": ">=16.8" } }, - "node_modules/react-scripts": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", - "integrity": "sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==", - "dependencies": { - "@babel/core": "^7.16.0", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", - "@svgr/webpack": "^5.5.0", - "babel-jest": "^27.4.2", - "babel-loader": "^8.2.3", - "babel-plugin-named-asset-import": "^0.3.8", - "babel-preset-react-app": "^10.0.1", - "bfj": "^7.0.2", - "browserslist": "^4.18.1", - "camelcase": "^6.2.1", - "case-sensitive-paths-webpack-plugin": "^2.4.0", - "css-loader": "^6.5.1", - "css-minimizer-webpack-plugin": "^3.2.0", - "dotenv": "^10.0.0", - "dotenv-expand": "^5.1.0", - "eslint": "^8.3.0", - "eslint-config-react-app": "^7.0.1", - "eslint-webpack-plugin": "^3.1.1", - "file-loader": "^6.2.0", - "fs-extra": "^10.0.0", - "html-webpack-plugin": "^5.5.0", - "identity-obj-proxy": "^3.0.0", - "jest": "^27.4.3", - "jest-resolve": "^27.4.2", - "jest-watch-typeahead": "^1.0.0", - "mini-css-extract-plugin": "^2.4.5", - "postcss": "^8.4.4", - "postcss-flexbugs-fixes": "^5.0.2", - "postcss-loader": "^6.2.1", - "postcss-normalize": "^10.0.1", - "postcss-preset-env": "^7.0.1", - "prompts": "^2.4.2", - "react-app-polyfill": "^3.0.0", - "react-dev-utils": "^12.0.1", - "react-refresh": "^0.11.0", - "resolve": "^1.20.0", - "resolve-url-loader": "^4.0.0", - "sass-loader": "^12.3.0", - "semver": "^7.3.5", - "source-map-loader": "^3.0.0", - "style-loader": "^3.3.1", - "tailwindcss": "^3.0.2", - "terser-webpack-plugin": "^5.2.5", - "webpack": "^5.64.4", - "webpack-dev-server": "^4.6.0", - "webpack-manifest-plugin": "^4.0.2", - "workbox-webpack-plugin": "^6.4.1" - }, - "bin": { - "react-scripts": "bin/react-scripts.js" - }, - "engines": { - "node": ">=14.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - }, - "peerDependencies": { - "react": ">= 16", - "typescript": "^3.2.1 || ^4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/react-scripts/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -17295,49 +6031,6 @@ "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/recursive-readdir": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", - "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", - "dependencies": { - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -17405,44 +6098,11 @@ "redux": "^4" } }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.10", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", - "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==" - }, - "node_modules/regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regex-parser": { - "version": "2.2.11", - "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", - "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==" - }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -17459,6 +6119,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, "engines": { "node": ">=8" }, @@ -17466,86 +6127,11 @@ "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/regexpu-core": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.1.tgz", - "integrity": "sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ==", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==" - }, - "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/renderkid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true }, "node_modules/resolve": { "version": "1.22.1", @@ -17563,25 +6149,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "engines": { - "node": ">=8" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -17590,82 +6157,11 @@ "node": ">=4" } }, - "node_modules/resolve-url-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz", - "integrity": "sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA==", - "dependencies": { - "adjust-sourcemap-loader": "^4.0.0", - "convert-source-map": "^1.7.0", - "loader-utils": "^2.0.0", - "postcss": "^7.0.35", - "source-map": "0.6.1" - }, - "engines": { - "node": ">=8.9" - }, - "peerDependencies": { - "rework": "1.0.1", - "rework-visit": "1.0.0" - }, - "peerDependenciesMeta": { - "rework": { - "optional": true - }, - "rework-visit": { - "optional": true - } - } - }, - "node_modules/resolve-url-loader/node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" - }, - "node_modules/resolve-url-loader/node_modules/postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dependencies": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/resolve-url-loader/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "engines": { - "node": ">= 4" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -17675,6 +6171,9 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -17685,78 +6184,18 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rollup": { - "version": "2.79.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", - "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/rollup-plugin-terser": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", - "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "jest-worker": "^26.2.1", - "serialize-javascript": "^4.0.0", - "terser": "^5.0.0" - }, - "peerDependencies": { - "rollup": "^2.0.0" - } - }, - "node_modules/rollup-plugin-terser/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/rollup-plugin-terser/node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/rollup-plugin-terser/node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/rollup-plugin-terser/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true, + "license": "MIT" }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { "type": "github", @@ -17783,29 +6222,11 @@ "tslib": "^2.1.0" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -17818,146 +6239,20 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sanitize-html": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.0.tgz", - "integrity": "sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==", - "dependencies": { - "deepmerge": "^4.2.2", - "escape-string-regexp": "^4.0.0", - "htmlparser2": "^8.0.0", - "is-plain-object": "^5.0.0", - "parse-srcset": "^1.0.2", - "postcss": "^8.3.11" - } - }, - "node_modules/sanitize-html/node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/sanitize-html/node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/sanitize-html/node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/sanitize-html/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/sanitize-html/node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, - "node_modules/sanitize.css": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", - "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==" - }, - "node_modules/sass-loader": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", - "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", - "dependencies": { - "klona": "^2.0.4", - "neo-async": "^2.6.2" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "fibers": ">= 3.1.0", - "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", - "sass": "^1.3.0", - "sass-embedded": "*", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "fibers": { - "optional": true - }, - "node-sass": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - } - } - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", "dependencies": { "xmlchars": "^2.2.0" }, "engines": { - "node": ">=10" + "node": ">=v12.22.7" } }, "node_modules/scheduler": { @@ -17968,193 +6263,20 @@ "loose-envify": "^1.1.0" } }, - "node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" - }, - "node_modules/selfsigned": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", - "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", - "dependencies": { - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "bin": { "semver": "bin/semver.js" } }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -18167,15 +6289,11 @@ "node": ">= 0.4" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -18187,22 +6305,16 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "engines": { "node": ">=8" } }, - "node_modules/shell-quote": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.4.tgz", - "integrity": "sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -18216,39 +6328,22 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, "engines": { "node": ">=8" } }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" - }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -18261,188 +6356,30 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/source-map-loader": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.2.tgz", - "integrity": "sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==", - "dependencies": { - "abab": "^2.0.5", - "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" - }, - "node_modules/stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility" - }, - "node_modules/stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-natural-compare": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", - "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==" - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", - "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.1", - "side-channel": "^1.0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" }, "node_modules/string.prototype.trimend": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -18456,6 +6393,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -18465,23 +6403,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -18489,30 +6416,6 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", - "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", - "engines": { - "node": ">=10" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "engines": { - "node": ">=6" - } - }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -18529,6 +6432,8 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -18536,35 +6441,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/style-loader": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", - "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", - "engines": { - "node": ">= 12.13.0" + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/stylehacks": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", - "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", - "dependencies": { - "browserslist": "^4.21.4", - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" }, "node_modules/stylis": { "version": "4.1.3", @@ -18572,40 +6467,11 @@ "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" }, "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -18624,323 +6490,18 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svg-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" - }, - "node_modules/svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.", - "dependencies": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/svgo/node_modules/css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "node_modules/svgo/node_modules/css-what": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", - "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/svgo/node_modules/dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "dependencies": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - } - }, - "node_modules/svgo/node_modules/domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "node_modules/svgo/node_modules/domutils/node_modules/domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - }, - "node_modules/svgo/node_modules/nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dependencies": { - "boolbase": "~1.0.0" - } - }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" - }, - "node_modules/tailwindcss": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.1.tgz", - "integrity": "sha512-Uw+GVSxp5CM48krnjHObqoOwlCt5Qo6nw1jlCRwfGy68dSYb/LwS9ZFidYGRiM+w6rMawkZiu1mEMAsHYAfoLg==", - "dependencies": { - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "color-name": "^1.1.4", - "detective": "^5.2.1", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.2.12", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "lilconfig": "^2.0.6", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.17", - "postcss-import": "^14.1.0", - "postcss-js": "^4.0.0", - "postcss-load-config": "^3.1.4", - "postcss-nested": "6.0.0", - "postcss-selector-parser": "^6.0.10", - "postcss-value-parser": "^4.2.0", - "quick-lru": "^5.1.1", - "resolve": "^1.22.1" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=12.13.0" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } - }, - "node_modules/tailwindcss/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/tailwindcss/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/tempy": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", - "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", - "dependencies": { - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser": { - "version": "5.39.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.2.tgz", - "integrity": "sha512-yEPUmWve+VA78bI71BW70Dh0TuV4HHd+I5SHOAfS1+QBOmvmCiiffgjR8ryyEd3KIfvPGFqoADt8LdQ6XpXIvg==", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.14.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", - "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -18953,27 +6514,42 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" }, - "node_modules/throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==" + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -18981,18 +6557,12 @@ "node": ">=8.0" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -19007,54 +6577,44 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 4.0.0" } }, "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", "dependencies": { - "punycode": "^2.1.1" + "punycode": "^2.3.1" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/tryer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", - "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" - }, - "node_modules/tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dependencies": { - "minimist": "^1.2.0" - }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "dev": true, + "license": "MIT", "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "tsconfck": "bin/tsconfck.js" + }, "engines": { - "node": ">=4" + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/tslib": { @@ -19066,6 +6626,7 @@ "version": "3.21.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, "dependencies": { "tslib": "^1.8.1" }, @@ -19079,12 +6640,15 @@ "node_modules/tsutils/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -19096,14 +6660,17 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, "engines": { "node": ">=4" } }, "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -19111,26 +6678,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, "node_modules/typescript": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", @@ -19144,10 +6691,18 @@ "node": ">=4.2.0" } }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-bigints": "^1.0.2", @@ -19158,87 +6713,20 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, "engines": { "node": ">= 10.0.0" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==" - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "engines": { - "node": ">=4", - "yarn": "*" - } - }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, "funding": [ { "type": "opencollective", @@ -19268,6 +6756,8 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -19276,6 +6766,8 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -19289,73 +6781,375 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } } }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-to-istanbul": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", - "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", + "node_modules/vite-node": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", + "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", + "dev": true, + "license": "MIT", "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" }, "engines": { - "node": ">=10.12.0" + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/v8-to-istanbul/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "engines": { - "node": ">= 8" + "node_modules/vite-tsconfig-paths": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz", + "integrity": "sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "node_modules/vite/node_modules/rollup": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, "engines": { - "node": ">= 0.8" + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", + "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "1.6.1", + "@vitest/runner": "1.6.1", + "@vitest/snapshot": "1.6.1", + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.1", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.1", + "@vitest/ui": "1.6.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/vitest/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/vitest/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/vitest/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/vitest/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/void-elements": { @@ -19366,462 +7160,72 @@ "node": ">=0.10.0" } }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", "dependencies": { - "xml-name-validator": "^3.0.0" + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=10" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/watchpack": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", - "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dependencies": { - "minimalistic-assert": "^1.0.0" + "node": ">=18" } }, "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=10.4" - } - }, - "node_modules/webpack": { - "version": "5.105.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", - "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.28.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.19.0", - "es-module-lexer": "^2.0.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.3.1", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.16", - "watchpack": "^2.5.1", - "webpack-sources": "^3.3.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-middleware": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", - "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", - "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/webpack-dev-middleware/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/webpack-dev-middleware/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/webpack-dev-server": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz", - "integrity": "sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==", - "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.1", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.4.2" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack-dev-server/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/webpack-manifest-plugin": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz", - "integrity": "sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow==", - "dependencies": { - "tapable": "^2.0.0", - "webpack-sources": "^2.2.0" - }, - "engines": { - "node": ">=12.22.0" - }, - "peerDependencies": { - "webpack": "^4.44.2 || ^5.47.0" - } - }, - "node_modules/webpack-manifest-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-manifest-plugin/node_modules/webpack-sources": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", - "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", - "dependencies": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/webpack/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "engines": { - "node": ">=0.8.0" + "node": ">=12" } }, "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "iconv-lite": "0.6.3" }, "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" - }, "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -19836,6 +7240,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -19882,364 +7287,52 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/word-wrap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", - "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/workbox-background-sync": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz", - "integrity": "sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==", + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", "dependencies": { - "idb": "^7.0.1", - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-broadcast-update": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz", - "integrity": "sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==", - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-build": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.5.4.tgz", - "integrity": "sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==", - "dependencies": { - "@apideck/better-ajv-errors": "^0.3.1", - "@babel/core": "^7.11.1", - "@babel/preset-env": "^7.11.0", - "@babel/runtime": "^7.11.2", - "@rollup/plugin-babel": "^5.2.0", - "@rollup/plugin-node-resolve": "^11.2.1", - "@rollup/plugin-replace": "^2.4.1", - "@surma/rollup-plugin-off-main-thread": "^2.2.3", - "ajv": "^8.6.0", - "common-tags": "^1.8.0", - "fast-json-stable-stringify": "^2.1.0", - "fs-extra": "^9.0.1", - "glob": "^7.1.6", - "lodash": "^4.17.20", - "pretty-bytes": "^5.3.0", - "rollup": "^2.43.1", - "rollup-plugin-terser": "^7.0.0", - "source-map": "^0.8.0-beta.0", - "stringify-object": "^3.3.0", - "strip-comments": "^2.0.1", - "tempy": "^0.6.0", - "upath": "^1.2.0", - "workbox-background-sync": "6.5.4", - "workbox-broadcast-update": "6.5.4", - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-google-analytics": "6.5.4", - "workbox-navigation-preload": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-range-requests": "6.5.4", - "workbox-recipes": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4", - "workbox-streams": "6.5.4", - "workbox-sw": "6.5.4", - "workbox-window": "6.5.4" + "siginfo": "^2.0.0", + "stackback": "0.0.2" }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/workbox-build/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/workbox-build/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/workbox-build/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/workbox-build/node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "dependencies": { - "whatwg-url": "^7.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/workbox-build/node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/workbox-build/node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" - }, - "node_modules/workbox-build/node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "node_modules/workbox-cacheable-response": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz", - "integrity": "sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==", - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-core": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.5.4.tgz", - "integrity": "sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==" - }, - "node_modules/workbox-expiration": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.5.4.tgz", - "integrity": "sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==", - "dependencies": { - "idb": "^7.0.1", - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-google-analytics": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz", - "integrity": "sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==", - "dependencies": { - "workbox-background-sync": "6.5.4", - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "node_modules/workbox-navigation-preload": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz", - "integrity": "sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==", - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-precaching": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.5.4.tgz", - "integrity": "sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==", - "dependencies": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "node_modules/workbox-range-requests": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz", - "integrity": "sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==", - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-recipes": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.5.4.tgz", - "integrity": "sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==", - "dependencies": { - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "node_modules/workbox-routing": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.5.4.tgz", - "integrity": "sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==", - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-strategies": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.5.4.tgz", - "integrity": "sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==", - "dependencies": { - "workbox-core": "6.5.4" - } - }, - "node_modules/workbox-streams": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.5.4.tgz", - "integrity": "sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==", - "dependencies": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4" - } - }, - "node_modules/workbox-sw": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.5.4.tgz", - "integrity": "sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==" - }, - "node_modules/workbox-webpack-plugin": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.4.tgz", - "integrity": "sha512-LmWm/zoaahe0EGmMTrSLUi+BjyR3cdGEfU3fS6PN1zKFYbqAKuQ+Oy/27e4VSXsyIwAw8+QDfk1XHNGtZu9nQg==", - "dependencies": { - "fast-json-stable-stringify": "^2.1.0", - "pretty-bytes": "^5.4.1", - "upath": "^1.2.0", - "webpack-sources": "^1.4.3", - "workbox-build": "6.5.4" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "webpack": "^4.4.0 || ^5.9.0" - } - }, - "node_modules/workbox-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/workbox-webpack-plugin/node_modules/webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dependencies": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - }, - "node_modules/workbox-window": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.5.4.tgz", - "integrity": "sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==", - "dependencies": { - "@types/trusted-types": "^2.0.2", - "workbox-core": "6.5.4" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" + "bin": { + "why-is-node-running": "cli.js" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=7.0.0" + "node": ">=0.10.0" } }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" }, "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -20251,35 +7344,27 @@ } }, "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "node_modules/yaml": { "version": "1.10.2", @@ -20289,35 +7374,12 @@ "node": ">= 6" } }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "engines": { - "node": ">=10" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -20328,291 +7390,160 @@ }, "dependencies": { "@adobe/css-tools": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", - "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", "dev": true }, "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "@apideck/better-ajv-errors": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", - "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, "requires": { - "json-schema": "^0.4.0", - "jsonpointer": "^5.0.0", - "leven": "^3.1.0" + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + }, + "dependencies": { + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + } } }, "@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "requires": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "@babel/compat-data": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.0.tgz", - "integrity": "sha512-Gt9jszFJYq7qzXVK4slhc6NzJXnOVmRECWcVjF/T23rNXD9NtWQ0W3qxdg+p9wWIB+VQw3GYV/U2Ha9bRTfs4w==" + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true }, "@babel/core": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.6.tgz", - "integrity": "sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.6", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helpers": "^7.19.4", - "@babel/parser": "^7.19.6", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.6", - "@babel/types": "^7.19.4", - "convert-source-map": "^1.7.0", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - } - }, - "@babel/eslint-parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz", - "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", - "requires": { - "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==" + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true } } }, "@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "requires": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", - "requires": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" } }, "@babel/helper-compilation-targets": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", - "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, "requires": { - "@babel/compat-data": "^7.20.0", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "semver": "^6.3.0" + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } } }, - "@babel/helper-create-class-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz", - "integrity": "sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", - "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.1.0" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", - "requires": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", - "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", - "requires": { - "@babel/types": "^7.18.9" - } + "@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==" }, "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "requires": { - "@babel/types": "^7.18.6" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" } }, "@babel/helper-module-transforms": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz", - "integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.19.4", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.6", - "@babel/types": "^7.19.4" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "requires": { - "@babel/types": "^7.18.6" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" } }, "@babel/helper-plugin-utils": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", - "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-replace-supers": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", - "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/traverse": "^7.19.1", - "@babel/types": "^7.19.0" - } - }, - "@babel/helper-simple-access": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz", - "integrity": "sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==", - "requires": { - "@babel/types": "^7.19.4" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", - "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", - "requires": { - "@babel/types": "^7.20.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "requires": { - "@babel/types": "^7.22.5" - } + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==" }, "@babel/helper-string-parser": { "version": "7.27.1", @@ -20620,303 +7551,32 @@ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" }, "@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==" + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==" }, "@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" - }, - "@babel/helper-wrap-function": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", - "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==", - "requires": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - } + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true }, "@babel/helpers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", - "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, "requires": { - "@babel/template": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" } }, "@babel/parser": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", - "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "requires": { - "@babel/types": "^7.27.1" - } - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", - "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-proposal-optional-chaining": "^7.18.9" - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz", - "integrity": "sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q==", - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-class-static-block": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", - "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-proposal-decorators": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.20.0.tgz", - "integrity": "sha512-vnuRRS20ygSxclEYikHzVrP9nZDFXaSzvJxGLQNAiBX041TmhS4hOUHWNIpq/q4muENuEP9XPJFXTNFejhemkg==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-replace-supers": "^7.19.1", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/plugin-syntax-decorators": "^7.19.0" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", - "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz", - "integrity": "sha512-wHmj6LDxVDnL+3WhXteUBaoM1aVILZODAUjg11kHqG4cOlfgMQGxw6aCgvrXrmaJR3Bn14oZhImyCPZzRpC93Q==", - "requires": { - "@babel/compat-data": "^7.19.4", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.18.8" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", - "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", - "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-decorators": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.19.0.tgz", - "integrity": "sha512-xaBZUEDntt4faL1yN8oIFlhfXeQAWJW7CLKYsHTUqriCUbj8xOra8bfxxKGi/UwExPFBuPdH4XfHc9rGQhrVkQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-flow": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz", - "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-import-assertions": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", - "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/types": "^7.29.0" } }, "@babel/plugin-syntax-jsx": { @@ -20927,552 +7587,22 @@ "@babel/helper-plugin-utils": "^7.18.6" } }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.27.1" } }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", - "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", - "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.0.tgz", - "integrity": "sha512-sXOohbpHZSk7GjxK9b3dKB7CfqUD5DwOH+DggKzOQ7TXYP+RCSbRykfjQmn/zq+rBjycVRtLf9pYhAaEJA786w==", - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz", - "integrity": "sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", - "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.0.tgz", - "integrity": "sha512-1dIhvZfkDVx/zn2S1aFwlruspTt4189j7fEkH0Y0VyuDM6bQt7bD6kLcz3l4IlLG+e5OReaBz9ROAbttRtUHqA==", - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-flow-strip-types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.19.0.tgz", - "integrity": "sha512-sgeMlNaQVbCSpgLSKP4ZZKfsJVnFnNQlUSk6gPYzR/q7tzCgQF2t8RBKAP6cKJeZdveei7Q7Jm527xepI8lNLg==", - "requires": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-flow": "^7.18.6" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", - "requires": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz", - "integrity": "sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==", - "requires": { - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", - "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==", - "requires": { - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-simple-access": "^7.19.4" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz", - "integrity": "sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==", - "requires": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-identifier": "^7.19.1" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", - "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", - "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-react-constant-elements": { - "version": "7.18.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.18.12.tgz", - "integrity": "sha512-Q99U9/ttiu+LMnRU8psd23HhvwXmKWDQIpocm0JKaICcZHnw+mdQbHm6xnSy7dOl8I5PELakYtNBubNQlBXbZw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-react-display-name": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", - "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz", - "integrity": "sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.19.0" - } - }, - "@babel/plugin-transform-react-jsx-development": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", - "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", - "requires": { - "@babel/plugin-transform-react-jsx": "^7.18.6" - } - }, - "@babel/plugin-transform-react-pure-annotations": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", - "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", - "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "regenerator-transform": "^0.15.0" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-runtime": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", - "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", - "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", - "requires": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.0.tgz", - "integrity": "sha512-xOAsAFaun3t9hCwZ13Qe7gq423UgMZ6zAgmLxeGGapFqlT/X3L5qT2btjiVLlFn7gWtMaVyceS5VxGAuKbgizw==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-typescript": "^7.20.0" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/preset-env": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.4.tgz", - "integrity": "sha512-5QVOTXUdqTCjQuh2GGtdd7YEhoRXBMVGROAtsBeLGIbIz3obCBIfRMT1I3ZKkMgNzwkyCkftDXSSkHxnfVf4qg==", - "requires": { - "@babel/compat-data": "^7.19.4", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.19.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.19.4", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.19.4", - "@babel/plugin-transform-classes": "^7.19.0", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.19.4", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.18.6", - "@babel/plugin-transform-modules-commonjs": "^7.18.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.0", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.18.8", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.19.4", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" - } - }, - "@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/preset-react": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", - "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-react-display-name": "^7.18.6", - "@babel/plugin-transform-react-jsx": "^7.18.6", - "@babel/plugin-transform-react-jsx-development": "^7.18.6", - "@babel/plugin-transform-react-pure-annotations": "^7.18.6" - } - }, - "@babel/preset-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", - "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-typescript": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/runtime": { @@ -21480,178 +7610,78 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==" }, - "@babel/runtime-corejs3": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.27.1.tgz", - "integrity": "sha512-909rVuj3phpjW6y0MCXAZ5iNeORePa6ldJvp2baWGcTjwqbBDDz6xoS5JHJ7lS88NlwLYj07ImL/8IUMtDZzTA==", - "requires": { - "core-js-pure": "^3.30.2" - } - }, "@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "requires": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" } }, "@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", - "globals": "^11.1.0" + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" } }, "@babel/types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", - "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "requires": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" } }, "@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true }, - "@csstools/normalize.css": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.0.0.tgz", - "integrity": "sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg==" + "@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true }, - "@csstools/postcss-cascade-layers": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", - "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", + "@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true + }, + "@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, "requires": { - "@csstools/selector-specificity": "^2.0.2", - "postcss-selector-parser": "^6.0.10" + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" } }, - "@csstools/postcss-color-function": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", - "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", - "requires": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - } + "@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true }, - "@csstools/postcss-font-format-keywords": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", - "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-hwb-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", - "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-ic-unit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", - "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", - "requires": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-is-pseudo-class": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", - "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", - "requires": { - "@csstools/selector-specificity": "^2.0.0", - "postcss-selector-parser": "^6.0.10" - } - }, - "@csstools/postcss-nested-calc": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", - "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-normalize-display-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", - "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-oklab-function": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", - "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", - "requires": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-progressive-custom-properties": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", - "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-stepped-value-functions": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", - "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-text-decoration-shorthand": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", - "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-trigonometric-functions": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", - "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "@csstools/postcss-unset-value": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", - "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==" - }, - "@csstools/selector-specificity": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", - "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==" + "@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true }, "@emotion/babel-plugin": { "version": "11.10.5", @@ -21767,50 +7797,205 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" }, + "@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "dev": true, + "optional": true + }, + "@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.4.3" + } + }, + "@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true + }, "@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.0", + "espree": "^9.6.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "requires": { - "type-fest": "^0.20.2" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "requires": { - "argparse": "^2.0.1" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" - } } }, + "@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true + }, "@formatjs/ecma402-abstract": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.13.0.tgz", @@ -21856,1054 +8041,68 @@ } }, "@humanwhocodes/config-array": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", - "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true }, "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" - } - } + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true }, "@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==" - }, - "@jest/console": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", - "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/core": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", - "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", - "requires": { - "@jest/console": "^27.5.1", - "@jest/reporters": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^27.5.1", - "jest-config": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-resolve-dependencies": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "jest-watcher": "^27.5.1", - "micromatch": "^4.0.4", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", - "requires": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/expect-utils": { - "version": "29.2.2", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.2.2.tgz", - "integrity": "sha512-vwnVmrVhTmGgQzyvcpze08br91OL61t9O0lJMDyb6Y/D8EKQ9V7rGUb/p7PDt0GPzK0zFYqXWFo4EO2legXmkg==", - "dev": true, - "requires": { - "jest-get-type": "^29.2.0" - } - }, - "@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", - "requires": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/globals": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", - "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", - "requires": { - "@jest/environment": "^27.5.1", - "@jest/types": "^27.5.1", - "expect": "^27.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==" - }, - "expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "requires": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" - }, - "jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/reporters": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", - "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.1.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true }, "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/source-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", - "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9", - "source-map": "^0.6.0" + "@sinclair/typebox": "^0.27.8" }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "@jest/test-result": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", - "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", - "requires": { - "@jest/console": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/test-sequencer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", - "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", - "requires": { - "@jest/test-result": "^27.5.1", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-runtime": "^27.5.1" - } - }, - "@jest/transform": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", - "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.5.1", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-util": "^27.5.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/types": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.2.1.tgz", - "integrity": "sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, "@jridgewell/resolve-uri": { @@ -22911,51 +8110,20 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" }, - "@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" - }, - "@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "requires": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "requires": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - } - } - } - }, "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" }, "@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "requires": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "@leichtgewicht/ip-codec": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", - "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" - }, "@mui/base": { "version": "5.0.0-alpha.103", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.103.tgz", @@ -23056,18 +8224,11 @@ "react-is": "^18.2.0" } }, - "@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "requires": { - "eslint-scope": "5.1.1" - } - }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "requires": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -23076,40 +8237,19 @@ "@nodelib/fs.stat": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true }, "@nodelib/fs.walk": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, - "@pmmmwh/react-refresh-webpack-plugin": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.8.tgz", - "integrity": "sha512-wxXRwf+IQ6zvHSJZ+5T2RQNEsq+kx4jKRXfFvdt3nBIUzJUAvXEFsUeoaohDe/Kr84MTjGwcuIUPNcstNJORsA==", - "requires": { - "ansi-html-community": "^0.0.8", - "common-path-prefix": "^3.0.0", - "core-js-pure": "^3.23.3", - "error-stack-parser": "^2.0.6", - "find-up": "^5.0.0", - "html-entities": "^2.1.0", - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" - } - } - }, "@popperjs/core": { "version": "2.11.6", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", @@ -23174,199 +8314,186 @@ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.2.tgz", "integrity": "sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ==" }, - "@rollup/plugin-babel": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", - "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", - "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@rollup/pluginutils": "^3.1.0" - } + "@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true }, - "@rollup/plugin-node-resolve": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", - "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", - "requires": { - "@rollup/pluginutils": "^3.1.0", - "@types/resolve": "1.17.1", - "builtin-modules": "^3.1.0", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.19.0" - } + "@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "dev": true, + "optional": true }, - "@rollup/plugin-replace": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", - "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", - "requires": { - "@rollup/pluginutils": "^3.1.0", - "magic-string": "^0.25.7" - } + "@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "dev": true, + "optional": true }, - "@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "requires": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "dependencies": { - "@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" - } - } + "@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "dev": true, + "optional": true }, - "@rushstack/eslint-patch": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", - "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" + "@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "dev": true, + "optional": true }, - "@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "dev": true, + "optional": true }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "requires": { - "type-detect": "4.0.8" - } + "@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "dev": true, + "optional": true }, - "@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "requires": { - "@sinonjs/commons": "^1.7.0" - } + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "dev": true, + "optional": true }, - "@surma/rollup-plugin-off-main-thread": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", - "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", - "requires": { - "ejs": "^3.1.6", - "json5": "^2.2.0", - "magic-string": "^0.25.0", - "string.prototype.matchall": "^4.0.6" - } + "@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "dev": true, + "optional": true }, - "@svgr/babel-plugin-add-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==" + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "dev": true, + "optional": true }, - "@svgr/babel-plugin-remove-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==" + "@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "dev": true, + "optional": true }, - "@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", - "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==" + "@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "dev": true, + "optional": true }, - "@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", - "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==" + "@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "dev": true, + "optional": true }, - "@svgr/babel-plugin-svg-dynamic-title": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", - "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==" + "@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "dev": true, + "optional": true }, - "@svgr/babel-plugin-svg-em-dimensions": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", - "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==" + "@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "dev": true, + "optional": true }, - "@svgr/babel-plugin-transform-react-native-svg": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", - "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==" + "@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "dev": true, + "optional": true }, - "@svgr/babel-plugin-transform-svg-component": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", - "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==" + "@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "dev": true, + "optional": true }, - "@svgr/babel-preset": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", - "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", - "requires": { - "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", - "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", - "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", - "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", - "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", - "@svgr/babel-plugin-transform-svg-component": "^5.5.0" - } + "@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "dev": true, + "optional": true }, - "@svgr/core": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", - "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", - "requires": { - "@svgr/plugin-jsx": "^5.5.0", - "camelcase": "^6.2.0", - "cosmiconfig": "^7.0.0" - } + "@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "dev": true, + "optional": true }, - "@svgr/hast-util-to-babel-ast": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", - "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", - "requires": { - "@babel/types": "^7.12.6" - } + "@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "dev": true, + "optional": true }, - "@svgr/plugin-jsx": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", - "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", - "requires": { - "@babel/core": "^7.12.3", - "@svgr/babel-preset": "^5.5.0", - "@svgr/hast-util-to-babel-ast": "^5.5.0", - "svg-parser": "^2.0.2" - } + "@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "dev": true, + "optional": true }, - "@svgr/plugin-svgo": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", - "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", - "requires": { - "cosmiconfig": "^7.0.0", - "deepmerge": "^4.2.2", - "svgo": "^1.2.2" - } + "@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "dev": true, + "optional": true }, - "@svgr/webpack": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", - "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", - "requires": { - "@babel/core": "^7.12.3", - "@babel/plugin-transform-react-constant-elements": "^7.12.1", - "@babel/preset-env": "^7.12.1", - "@babel/preset-react": "^7.12.5", - "@svgr/core": "^5.5.0", - "@svgr/plugin-jsx": "^5.5.0", - "@svgr/plugin-svgo": "^5.5.0", - "loader-utils": "^2.0.0" - } + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "dev": true, + "optional": true }, "@testing-library/dom": { "version": "8.19.0", @@ -23382,124 +8509,27 @@ "dom-accessibility-api": "^0.5.9", "lz-string": "^1.4.4", "pretty-format": "^27.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "@testing-library/jest-dom": { - "version": "5.16.5", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", - "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", "dev": true, "requires": { - "@adobe/css-tools": "^4.0.1", - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", + "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", - "chalk": "^3.0.0", "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.5.6", - "lodash": "^4.17.15", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", "redent": "^3.0.0" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, @@ -23514,16 +8544,6 @@ "@types/react-dom": "^18.0.0" } }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" - }, - "@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==" - }, "@types/aria-query": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", @@ -23531,12 +8551,13 @@ "dev": true }, "@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" @@ -23546,6 +8567,7 @@ "version": "7.6.4", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, "requires": { "@babel/types": "^7.0.0" } @@ -23554,6 +8576,7 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, "requires": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -23563,95 +8586,25 @@ "version": "7.18.2", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.2.tgz", "integrity": "sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg==", + "dev": true, "requires": { "@babel/types": "^7.3.0" } }, - "@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/bonjour": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", - "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", - "requires": { - "@types/node": "*" - } - }, - "@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "requires": { - "@types/node": "*" - } - }, - "@types/connect-history-api-fallback": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", - "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", - "requires": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "@types/eslint": { - "version": "8.4.9", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.9.tgz", - "integrity": "sha512-jFCSo4wJzlHQLCpceUhUnXdrPuCNOjGFMQ8Eg6JXxlz3QaCKOb7eGi2cephQdM4XTYsNej69P9JDJ1zqNIbncQ==", - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "requires": { - "@types/eslint": "*", - "@types/estree": "*" + "@types/trusted-types": "*" } }, "@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" - }, - "@types/express": { - "version": "4.17.14", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz", - "integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==", - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.31", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", - "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "requires": { - "@types/node": "*" - } + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true }, "@types/history": { "version": "4.7.11", @@ -23668,69 +8621,6 @@ "hoist-non-react-statics": "^3.3.0" } }, - "@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" - }, - "@types/http-proxy": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", - "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.0.tgz", - "integrity": "sha512-KO7bPV21d65PKwv3LLsD8Jn3E05pjNjRZvkm+YTacWhVmykAb07wW6IkZUmQAltwQafNcDUEUrMO2h3jeBSisg==", - "dev": true, - "requires": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - } - } - }, "@types/jquery": { "version": "3.5.14", "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz", @@ -23743,12 +8633,8 @@ "@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true }, "@types/lodash": { "version": "4.14.186", @@ -23756,11 +8642,6 @@ "integrity": "sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==", "dev": true }, - "@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" - }, "@types/node": { "version": "18.11.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.7.tgz", @@ -23771,31 +8652,11 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, - "@types/prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==" - }, "@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, - "@types/q": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", - "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" - }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, "@types/react": { "version": "18.0.24", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.24.tgz", @@ -23892,19 +8753,6 @@ "redux": "^3.6.0 || ^4.0.0" } }, - "@types/resolve": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", - "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", - "requires": { - "@types/node": "*" - } - }, - "@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" - }, "@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", @@ -23913,24 +8761,8 @@ "@types/semver": { "version": "7.3.13", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==" - }, - "@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", - "requires": { - "@types/express": "*" - } - }, - "@types/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", - "requires": { - "@types/mime": "*", - "@types/node": "*" - } + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true }, "@types/sizzle": { "version": "2.3.3", @@ -23938,63 +8770,22 @@ "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", "dev": true }, - "@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", - "requires": { - "@types/node": "*" - } - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" - }, - "@types/testing-library__jest-dom": { - "version": "5.14.5", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", - "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", - "dev": true, - "requires": { - "@types/jest": "*" - } - }, "@types/trusted-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", - "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "devOptional": true }, "@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" }, - "@types/ws": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", - "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", - "requires": { - "@types/node": "*" - } - }, - "@types/yargs": { - "version": "17.0.13", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz", - "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" - }, "@typescript-eslint/eslint-plugin": { "version": "5.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.41.0.tgz", "integrity": "sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA==", + "dev": true, "requires": { "@typescript-eslint/scope-manager": "5.41.0", "@typescript-eslint/type-utils": "5.41.0", @@ -24010,24 +8801,18 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, "requires": { "lru-cache": "^6.0.0" } } } }, - "@typescript-eslint/experimental-utils": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.41.0.tgz", - "integrity": "sha512-/qxT2Kd2q/A22JVIllvws4rvc00/3AT4rAo/0YgEN28y+HPhbJbk6X4+MAHEoZzpNyAOugIT7D/OLnKBW8FfhA==", - "requires": { - "@typescript-eslint/utils": "5.41.0" - } - }, "@typescript-eslint/parser": { "version": "5.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.41.0.tgz", "integrity": "sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA==", + "dev": true, "requires": { "@typescript-eslint/scope-manager": "5.41.0", "@typescript-eslint/types": "5.41.0", @@ -24039,6 +8824,7 @@ "version": "5.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.41.0.tgz", "integrity": "sha512-xOxPJCnuktUkY2xoEZBKXO5DBCugFzjrVndKdUnyQr3+9aDWZReKq9MhaoVnbL+maVwWJu/N0SEtrtEUNb62QQ==", + "dev": true, "requires": { "@typescript-eslint/types": "5.41.0", "@typescript-eslint/visitor-keys": "5.41.0" @@ -24048,6 +8834,7 @@ "version": "5.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.41.0.tgz", "integrity": "sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA==", + "dev": true, "requires": { "@typescript-eslint/typescript-estree": "5.41.0", "@typescript-eslint/utils": "5.41.0", @@ -24058,12 +8845,14 @@ "@typescript-eslint/types": { "version": "5.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.41.0.tgz", - "integrity": "sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA==" + "integrity": "sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA==", + "dev": true }, "@typescript-eslint/typescript-estree": { "version": "5.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.41.0.tgz", "integrity": "sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg==", + "dev": true, "requires": { "@typescript-eslint/types": "5.41.0", "@typescript-eslint/visitor-keys": "5.41.0", @@ -24078,6 +8867,7 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, "requires": { "lru-cache": "^6.0.0" } @@ -24088,6 +8878,7 @@ "version": "5.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.41.0.tgz", "integrity": "sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ==", + "dev": true, "requires": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", @@ -24103,6 +8894,7 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, "requires": { "lru-cache": "^6.0.0" } @@ -24113,245 +8905,193 @@ "version": "5.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.41.0.tgz", "integrity": "sha512-vilqeHj267v8uzzakbm13HkPMl7cbYpKVjgFWZPIOHIJHZtinvypUhJ5xBXfWYg4eFKqztbMMpOgFpT9Gfx4fw==", + "dev": true, "requires": { "@typescript-eslint/types": "5.41.0", "eslint-visitor-keys": "^3.3.0" } }, - "@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true + }, + "@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, "requires": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==" - }, - "@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==" - }, - "@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==" - }, - "@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==" - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "requires": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==" - }, - "@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "requires": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "requires": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "requires": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "requires": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "requires": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" - }, - "abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==" - }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" }, "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + "react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true } } }, - "acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==" + "@vitest/coverage-v8": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.1.tgz", + "integrity": "sha512-6YeRZwuO4oTGKxD3bijok756oktHSIm3eczVVzNe3scqzuhLwltIF3S9ZL/vwOVIpURmU6SnZhziXXAfw8/Qlw==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.1", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.4", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.4", + "istanbul-reports": "^3.1.6", + "magic-string": "^0.30.5", + "magicast": "^0.3.3", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "test-exclude": "^6.0.0" + } + }, + "@vitest/expect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", + "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "dev": true, + "requires": { + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "chai": "^4.3.10" + } + }, + "@vitest/runner": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", + "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "dev": true, + "requires": { + "@vitest/utils": "1.6.1", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "dependencies": { + "p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "requires": { + "yocto-queue": "^1.0.0" + } + }, + "yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true + } + } + }, + "@vitest/snapshot": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", + "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "dev": true, + "requires": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + } + } + }, + "@vitest/spy": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", + "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "dev": true, + "requires": { + "tinyspy": "^2.2.0" + } + }, + "@vitest/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "dev": true, + "requires": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + } + } + } + }, + "acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==" - }, - "acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "requires": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" - } - } - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" - }, - "address": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.1.tgz", - "integrity": "sha512-B+6bi5D34+fDYENiH5qOlA0cV2rAGKuWZ9LeyUUehbXy8e0VS9e498yO0Jeeh+iM+6KbfudHTFjXw2MmJD4QRA==" - }, - "adjust-sourcemap-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", - "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", - "requires": { - "loader-utils": "^2.0.0", - "regex-parser": "^2.2.11" - } + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true }, "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "requires": { - "debug": "4" - } + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -24359,84 +9099,26 @@ "uri-js": "^4.2.2" } }, - "ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "requires": { - "ajv": "^8.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - } - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==" - }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true }, "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" } }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" - }, "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "aria-query": { "version": "5.1.2", @@ -24447,99 +9129,23 @@ "deep-equal": "^2.0.5" } }, - "array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" - }, - "array-includes": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", - "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.7" - } - }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true }, - "array.prototype.flat": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", - "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" - } - }, - "array.prototype.flatmap": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", - "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" - } - }, - "array.prototype.reduce": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", - "integrity": "sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - } - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" - }, - "ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" - }, - "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" - }, - "autoprefixer": { - "version": "10.4.13", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", - "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", - "requires": { - "browserslist": "^4.21.4", - "caniuse-lite": "^1.0.30001426", - "fraction.js": "^4.2.0", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - } + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true }, "available-typed-arrays": { "version": "1.0.5", @@ -24547,142 +9153,6 @@ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", "dev": true }, - "axe-core": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.5.0.tgz", - "integrity": "sha512-4+rr8eQ7+XXS5nZrKcMO/AikHL0hVqy+lHWAnE3xdHl+aguag8SOQ6eEqLexwLNWgXIMfunGuD3ON1/6Kyet0A==" - }, - "axobject-query": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", - "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==" - }, - "babel-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", - "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", - "requires": { - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.5.1", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "babel-loader": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", - "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", - "requires": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "dependencies": { - "schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "requires": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - } - } - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", - "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - } - }, "babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -24693,202 +9163,23 @@ "resolve": "^1.19.0" } }, - "babel-plugin-named-asset-import": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", - "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==" - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", - "requires": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.3" - } - }, - "babel-plugin-transform-react-remove-prop-types": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", - "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==" - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", - "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", - "requires": { - "babel-plugin-jest-hoist": "^27.5.1", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "babel-preset-react-app": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz", - "integrity": "sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==", - "requires": { - "@babel/core": "^7.16.0", - "@babel/plugin-proposal-class-properties": "^7.16.0", - "@babel/plugin-proposal-decorators": "^7.16.4", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", - "@babel/plugin-proposal-numeric-separator": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.0", - "@babel/plugin-proposal-private-methods": "^7.16.0", - "@babel/plugin-transform-flow-strip-types": "^7.16.0", - "@babel/plugin-transform-react-display-name": "^7.16.0", - "@babel/plugin-transform-runtime": "^7.16.4", - "@babel/preset-env": "^7.16.4", - "@babel/preset-react": "^7.16.0", - "@babel/preset-typescript": "^7.16.0", - "@babel/runtime": "^7.16.3", - "babel-plugin-macros": "^3.1.0", - "babel-plugin-transform-react-remove-prop-types": "^0.4.24" - } - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "baseline-browser-mapping": { "version": "2.9.19", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==" - }, - "batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" - }, - "bfj": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.0.2.tgz", - "integrity": "sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw==", - "requires": { - "bluebird": "^3.5.5", - "check-types": "^11.1.1", - "hoopy": "^0.1.4", - "tryer": "^1.0.1" - } - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "bonjour-service": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.14.tgz", - "integrity": "sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ==", - "requires": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -24898,19 +9189,16 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "requires": { "fill-range": "^7.1.1" } }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" - }, "browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, "requires": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -24919,33 +9207,17 @@ "update-browserslist-db": "^1.2.0" } }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==" - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==" + "cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true }, "call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, "requires": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -24954,131 +9226,67 @@ "set-function-length": "^1.2.1" } }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, - "camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "requires": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" - }, - "camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" - }, - "caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, "caniuse-lite": { "version": "1.0.30001769", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", - "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==" + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "dev": true }, - "case-sensitive-paths-webpack-plugin": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", - "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==" + "chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "dependencies": { + "type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true + } + } }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - } + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==" - }, - "check-types": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz", - "integrity": "sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==" - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "chrome-trace-event": { + "check-error": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==" - }, - "ci-info": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz", - "integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==" - }, - "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==" - }, - "clean-css": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz", - "integrity": "sha512-lCr8OHhiWCTw4v8POJovCoh4T7I9U11yVsPjMWWnnMmp9ZowCxyad1Pathle/9HjaDp+fdQKjO9fQydE6RHTZg==", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, "requires": { - "source-map": "~0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "get-func-name": "^2.0.2" } }, "clsx": { @@ -25086,185 +9294,47 @@ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==" - }, - "coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "requires": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - } - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==" - }, "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { - "color-name": "1.1.3" + "color-name": "~1.1.4" } }, "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" - }, - "colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "requires": { "delayed-stream": "~1.0.0" } }, - "commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" - }, - "common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==" - }, - "common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==" - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" - }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, - "confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==" - }, - "connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==" - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + "confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true }, "convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, - "cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "core-js": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz", - "integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==" - }, - "core-js-compat": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.0.tgz", - "integrity": "sha512-piOX9Go+Z4f9ZiBFLnZ5VrOpBl0h7IGCkiFUN11QTe6LjAvOT3ifL/5TdoizMh99hcGy5SoLyWbapIY/PIb/3A==", - "requires": { - "browserslist": "^4.21.4" - } - }, - "core-js-pure": { - "version": "3.42.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.42.0.tgz", - "integrity": "sha512-007bM04u91fF4kMgwom2I5cQxAFIy8jVulgr9eozILl/SZE53QOqnW/+vviC+wQWLv+AunBG+8Q0TLoeSsSxRQ==" - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, "cosmiconfig": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", @@ -25281,6 +9351,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -25292,268 +9363,27 @@ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" - }, - "css-blank-pseudo": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", - "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", - "requires": { - "postcss-selector-parser": "^6.0.9" - } - }, - "css-declaration-sorter": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", - "integrity": "sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==" - }, - "css-has-pseudo": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", - "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", - "requires": { - "postcss-selector-parser": "^6.0.9" - } - }, - "css-loader": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", - "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", - "requires": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.7", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", - "postcss-modules-scope": "^3.0.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.3.5" - }, - "dependencies": { - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "css-minimizer-webpack-plugin": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", - "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", - "requires": { - "cssnano": "^5.0.6", - "jest-worker": "^27.0.2", - "postcss": "^8.3.5", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "requires": { - "fast-deep-equal": "^3.1.3" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "css-prefers-color-scheme": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", - "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==" - }, - "css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - } - }, - "css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" - }, - "css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "requires": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" - }, "css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", "dev": true }, - "cssdb": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.0.2.tgz", - "integrity": "sha512-Vm4b6P/PifADu0a76H0DKRNVWq3Rq9xa/Nx6oEMUBJlwTUuZoZ3dkZxo8Gob3UEL53Cq+Ma1GBgISed6XEBs3w==" - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" - }, - "cssnano": { - "version": "5.1.14", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.14.tgz", - "integrity": "sha512-Oou7ihiTocbKqi0J1bB+TRJIQX5RMR3JghA8hcWSw9mjBLQ5Y3RWqEDoYG3sRNlAbCIXpqMoZGbq5KDR3vdzgw==", - "requires": { - "cssnano-preset-default": "^5.2.13", - "lilconfig": "^2.0.3", - "yaml": "^1.10.2" - } - }, - "cssnano-preset-default": { - "version": "5.2.13", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.13.tgz", - "integrity": "sha512-PX7sQ4Pb+UtOWuz8A1d+Rbi+WimBIxJTRyBdgGp1J75VU0r/HFQeLnMYgHiCAp6AR4rqrc7Y4R+1Rjk3KJz6DQ==", - "requires": { - "css-declaration-sorter": "^6.3.1", - "cssnano-utils": "^3.1.0", - "postcss-calc": "^8.2.3", - "postcss-colormin": "^5.3.0", - "postcss-convert-values": "^5.1.3", - "postcss-discard-comments": "^5.1.2", - "postcss-discard-duplicates": "^5.1.0", - "postcss-discard-empty": "^5.1.1", - "postcss-discard-overridden": "^5.1.0", - "postcss-merge-longhand": "^5.1.7", - "postcss-merge-rules": "^5.1.3", - "postcss-minify-font-values": "^5.1.0", - "postcss-minify-gradients": "^5.1.1", - "postcss-minify-params": "^5.1.4", - "postcss-minify-selectors": "^5.2.1", - "postcss-normalize-charset": "^5.1.0", - "postcss-normalize-display-values": "^5.1.0", - "postcss-normalize-positions": "^5.1.1", - "postcss-normalize-repeat-style": "^5.1.1", - "postcss-normalize-string": "^5.1.0", - "postcss-normalize-timing-functions": "^5.1.0", - "postcss-normalize-unicode": "^5.1.1", - "postcss-normalize-url": "^5.1.0", - "postcss-normalize-whitespace": "^5.1.1", - "postcss-ordered-values": "^5.1.3", - "postcss-reduce-initial": "^5.1.1", - "postcss-reduce-transforms": "^5.1.0", - "postcss-svgo": "^5.1.0", - "postcss-unique-selectors": "^5.1.1" - } - }, - "cssnano-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==" - }, - "csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", - "requires": { - "css-tree": "^1.1.2" - }, - "dependencies": { - "css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "requires": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - } - }, - "mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" - }, "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, "requires": { - "cssom": "~0.3.6" + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" }, "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + "rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true } } }, @@ -25562,19 +9392,14 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, - "damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" - }, "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" } }, "debug": { @@ -25586,14 +9411,19 @@ } }, "decimal.js": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.2.tgz", - "integrity": "sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==" + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==" + "deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } }, "deep-equal": { "version": "2.0.5", @@ -25621,155 +9451,61 @@ "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" - }, - "default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "requires": { - "execa": "^5.0.0" - } + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true }, "define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "requires": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, - "define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" - }, "define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, "requires": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, - "defined": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", - "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==" - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==" - }, - "detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" - }, - "detect-port-alt": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", - "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", - "requires": { - "address": "^1.0.1", - "debug": "^2.6.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "detective": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", - "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", - "requires": { - "acorn-node": "^1.8.2", - "defined": "^1.0.0", - "minimist": "^1.2.6" - } + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true }, "dexie": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/dexie/-/dexie-3.2.2.tgz", "integrity": "sha512-q5dC3HPmir2DERlX+toCBbHQXW5MsyrFqPFcovkH9N2S/UW/H3H5AWAB6iEOExeraAu+j+zRDG+zg/D7YhH0qg==" }, - "didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" - }, "diff-sequences": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.2.0.tgz", - "integrity": "sha512-413SY5JpYeSBZxmenGEmCVQ8mCgtFJF0w9PROdaS6z987XC2Pd2GOKqOITLtMftmyFZqgtCOb/QA7/Z3ZXfzIw==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, "requires": { "path-type": "^4.0.0" } }, - "dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" - }, - "dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==" - }, - "dns-packet": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", - "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", - "requires": { - "@leichtgewicht/ip-codec": "^2.0.1" - } - }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, "requires": { "esutils": "^2.0.2" } @@ -25780,14 +9516,6 @@ "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==", "dev": true }, - "dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "requires": { - "utila": "~0.4" - } - }, "dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -25797,129 +9525,30 @@ "csstype": "^3.0.2" } }, - "dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dompurify": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", + "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" + "@types/trusted-types": "^2.0.7" } }, - "domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" - }, - "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==" - } - } - }, - "domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "requires": { - "domelementtype": "^2.2.0" - } - }, - "domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } - }, - "dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" - }, - "dotenv-expand": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", - "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" - }, - "duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "requires": { - "jake": "^10.8.5" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" } }, "electron-to-chromium": { "version": "1.5.286", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==" - }, - "emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==" - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" - }, - "encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" - }, - "enhanced-resolve": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", - "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" - } - }, - "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "dev": true }, "error-ex": { "version": "1.3.2", @@ -25929,18 +9558,11 @@ "is-arrayish": "^0.2.1" } }, - "error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "requires": { - "stackframe": "^1.3.4" - } - }, "es-abstract": { "version": "1.20.4", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", @@ -25968,23 +9590,17 @@ "unbox-primitive": "^1.0.2" } }, - "es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" - }, "es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "requires": { - "get-intrinsic": "^1.2.4" - } + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true }, "es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true }, "es-get-iterator": { "version": "1.1.2", @@ -26002,23 +9618,32 @@ "isarray": "^2.0.5" } }, - "es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==" - }, - "es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, "requires": { - "has": "^1.0.3" + "es-errors": "^1.3.0" + } + }, + "es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" } }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -26030,166 +9655,99 @@ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" }, + "esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "requires": { - "prelude-ls": "~1.1.2" - } - } - } - }, "eslint": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz", - "integrity": "sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "dev": true, "requires": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.15.0", - "grapheme-splitter": "^1.0.4", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -26198,264 +9756,25 @@ "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true }, "glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "requires": { "is-glob": "^4.0.3" } - }, - "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "requires": { - "type-fest": "^0.20.2" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "requires": { - "argparse": "^2.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" } } }, - "eslint-config-react-app": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", - "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", - "requires": { - "@babel/core": "^7.16.0", - "@babel/eslint-parser": "^7.16.3", - "@rushstack/eslint-patch": "^1.1.0", - "@typescript-eslint/eslint-plugin": "^5.5.0", - "@typescript-eslint/parser": "^5.5.0", - "babel-preset-react-app": "^10.0.1", - "confusing-browser-globals": "^1.0.11", - "eslint-plugin-flowtype": "^8.0.3", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jest": "^25.3.0", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.27.1", - "eslint-plugin-react-hooks": "^4.3.0", - "eslint-plugin-testing-library": "^5.0.1" - } - }, - "eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "requires": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-module-utils": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", - "requires": { - "debug": "^3.2.7" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-plugin-flowtype": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", - "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", - "requires": { - "lodash": "^4.17.21", - "string-natural-compare": "^3.0.1" - } - }, - "eslint-plugin-import": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", - "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", - "requires": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.3", - "has": "^1.0.3", - "is-core-module": "^2.8.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.5", - "resolve": "^1.22.0", - "tsconfig-paths": "^3.14.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "requires": { - "esutils": "^2.0.2" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "eslint-plugin-jest": { - "version": "25.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", - "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", - "requires": { - "@typescript-eslint/experimental-utils": "^5.0.0" - } - }, - "eslint-plugin-jsx-a11y": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz", - "integrity": "sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==", - "requires": { - "@babel/runtime": "^7.18.9", - "aria-query": "^4.2.2", - "array-includes": "^3.1.5", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.4.3", - "axobject-query": "^2.2.0", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "has": "^1.0.3", - "jsx-ast-utils": "^3.3.2", - "language-tags": "^1.0.5", - "minimatch": "^3.1.2", - "semver": "^6.3.0" - }, - "dependencies": { - "aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "requires": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - } - } - } - }, - "eslint-plugin-react": { - "version": "7.31.10", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz", - "integrity": "sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA==", - "requires": { - "array-includes": "^3.1.5", - "array.prototype.flatmap": "^1.3.0", - "doctrine": "^2.1.0", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.5", - "object.fromentries": "^2.0.5", - "object.hasown": "^1.1.1", - "object.values": "^1.1.5", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.3", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.7" - }, - "dependencies": { - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "requires": { - "esutils": "^2.0.2" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" - }, - "resolve": { - "version": "2.0.0-next.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - } - } - }, - "eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==" - }, - "eslint-plugin-testing-library": { - "version": "5.9.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.9.1.tgz", - "integrity": "sha512-6BQp3tmb79jLLasPHJmy8DnxREe+2Pgf7L+7o09TSWPfdqqtQfRZmZNetr5mOs3yqZk/MRNxpN3RUpJe0wB4LQ==", - "requires": { - "@typescript-eslint/utils": "^5.13.0" - } - }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -26465,6 +9784,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, "requires": { "eslint-visitor-keys": "^2.0.0" }, @@ -26472,106 +9792,33 @@ "eslint-visitor-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==" + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true } } }, "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==" - }, - "eslint-webpack-plugin": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz", - "integrity": "sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==", - "requires": { - "@types/eslint": "^7.29.0 || ^8.4.1", - "jest-worker": "^28.0.2", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "requires": { - "fast-deep-equal": "^3.1.3" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-worker": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", - "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - } - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "requires": { - "has-flag": "^4.0.0" - } - } - } + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true }, "espree": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", - "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, "requires": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.1" } }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, "requires": { "estraverse": "^5.1.0" }, @@ -26579,7 +9826,8 @@ "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true } } }, @@ -26587,6 +9835,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "requires": { "estraverse": "^5.2.0" }, @@ -26594,141 +9843,43 @@ "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true } } }, "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true }, "estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0" + } }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==" - }, - "expect": { - "version": "29.2.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.2.2.tgz", - "integrity": "sha512-hE09QerxZ5wXiOhqkXy5d2G9ar+EqOyifnCXCpMNu+vZ6DG9TJ6CO2c2kPDSLqERTTWrO7OZj8EkYHQqSd78Yw==", - "dev": true, - "requires": { - "@jest/expect-utils": "^29.2.2", - "jest-get-type": "^29.2.0", - "jest-matcher-utils": "^29.2.2", - "jest-message-util": "^29.2.1", - "jest-util": "^29.2.1" - } - }, - "express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true }, "fast-glob": { "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -26740,94 +9891,38 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" - }, - "fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==" + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true }, "fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, "requires": { "reusify": "^1.0.4" } }, - "faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "requires": { - "websocket-driver": ">=0.5.1" - } - }, - "fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "requires": { - "bser": "2.1.1" - } - }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, "requires": { "flat-cache": "^3.0.4" } }, - "file-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", - "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", - "requires": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - } - }, - "filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "requires": { - "minimatch": "^5.0.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "filesize": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", - "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==" - }, "fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -26845,45 +9940,6 @@ "resolved": "https://registry.npmjs.org/final-form-set-field-touched/-/final-form-set-field-touched-1.0.1.tgz", "integrity": "sha512-yvE5AAs9U3OgJQ9YF8NhSF0I0mJEECvOpkaXNqovloxji5Q6gOZ0DCIAyLAKHluGSpsXKUGORyBm8Hq0beZIqQ==" }, - "finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, "find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -26893,29 +9949,28 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "requires": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, "requires": { - "flatted": "^3.1.0", + "flatted": "^3.2.9", + "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" - }, - "follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==" + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true }, "for-each": { "version": "0.3.3", @@ -26926,166 +9981,41 @@ "is-callable": "^1.1.3" } }, - "fork-ts-checker-webpack-plugin": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz", - "integrity": "sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA==", - "requires": { - "@babel/code-frame": "^7.8.3", - "@types/json-schema": "^7.0.5", - "chalk": "^4.1.0", - "chokidar": "^3.4.2", - "cosmiconfig": "^6.0.0", - "deepmerge": "^4.2.2", - "fs-extra": "^9.0.0", - "glob": "^7.1.6", - "memfs": "^3.1.2", - "minimatch": "^3.0.4", - "schema-utils": "2.7.0", - "semver": "^7.3.2", - "tapable": "^1.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - } - }, - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", - "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" - } - } - }, "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fraction.js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", - "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, "fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, - "fs-monkey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", - "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==" - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "optional": true }, "function-bind": { @@ -27097,6 +10027,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -27107,49 +10038,54 @@ "functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + "get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true }, "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" } }, - "get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==" - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } }, "get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -27159,6 +10095,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -27172,52 +10109,25 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "requires": { "is-glob": "^4.0.1" } }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" - }, - "global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "requires": { - "global-prefix": "^3.0.0" - } - }, - "global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "requires": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "dependencies": { - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - } - } - }, "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } }, "globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -27227,41 +10137,29 @@ "slash": "^3.0.0" } }, + "globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "requires": { - "get-intrinsic": "^1.1.3" - } + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" - }, - "gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "requires": { - "duplexer": "^0.1.2" - } - }, - "handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" - }, - "harmony-reflect": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", - "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==" + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true }, "has": { "version": "1.0.3", @@ -27274,52 +10172,48 @@ "has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true }, "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "requires": { "es-define-property": "^1.0.0" } }, - "has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" - }, "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true }, "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, "requires": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" } }, "hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "requires": { "function-bind": "^1.1.2" } }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" - }, "hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -27335,87 +10229,20 @@ } } }, - "hoopy": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", - "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==" - }, - "hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "requires": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, "requires": { - "whatwg-encoding": "^1.0.5" + "whatwg-encoding": "^3.1.1" } }, - "html-entities": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", - "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==" - }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" - }, - "html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "requires": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - } + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true }, "html-parse-stringify": { "version": "3.0.1", @@ -27425,97 +10252,26 @@ "void-elements": "3.1.0" } }, - "html-webpack-plugin": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", - "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", - "requires": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" - } - }, - "htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==" - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" - }, - "http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "http-proxy-middleware": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", - "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", - "requires": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" + "agent-base": "^7.1.0", + "debug": "^4.3.4" } }, "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, "requires": { - "agent-base": "6", + "agent-base": "^7.1.2", "debug": "4" } }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" - }, "husky": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz", @@ -27547,37 +10303,16 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, - "icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==" - }, - "idb": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.0.tgz", - "integrity": "sha512-Wsk07aAxDsntgYJY4h0knZJuTxM73eQ4reRAO+Z1liOh8eMCJ/MoDS8fCui1vGT9mnjtl1sOu3I2i/W1swPYZg==" - }, - "identity-obj-proxy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", - "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", - "requires": { - "harmony-reflect": "^1.4.6" - } - }, "ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==" - }, - "immer": { - "version": "9.0.16", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.16.tgz", - "integrity": "sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ==" + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true }, "import-fresh": { "version": "3.3.0", @@ -27588,19 +10323,11 @@ "resolve-from": "^4.0.0" } }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true }, "indent-string": { "version": "4.0.0", @@ -27612,6 +10339,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -27620,17 +10348,14 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, "requires": { "get-intrinsic": "^1.1.0", "has": "^1.0.3", @@ -27656,11 +10381,6 @@ "loose-envify": "^1.0.0" } }, - "ipaddr.js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", - "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==" - }, "is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -27680,22 +10400,16 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, "requires": { "has-bigints": "^1.0.1" } }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "requires": { - "binary-extensions": "^2.0.0" - } - }, "is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -27704,7 +10418,8 @@ "is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true }, "is-core-module": { "version": "2.11.0", @@ -27718,34 +10433,22 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } }, - "is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==" + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -27756,53 +10459,38 @@ "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", "dev": true }, - "is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==" - }, "is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true }, "is-number-object": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==" - }, "is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" - }, - "is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==" - }, - "is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true }, "is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true }, "is-promise": { "version": "2.2.2", @@ -27813,21 +10501,12 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" } }, - "is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==" - }, - "is-root": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", - "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==" - }, "is-set": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", @@ -27838,19 +10517,16 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, "requires": { "call-bind": "^1.0.2" } }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" - }, "is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -27859,6 +10535,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, "requires": { "has-symbols": "^1.0.2" } @@ -27876,11 +10553,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" - }, "is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", @@ -27891,6 +10563,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, "requires": { "call-bind": "^1.0.2" } @@ -27905,14 +10578,6 @@ "get-intrinsic": "^1.1.1" } }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "requires": { - "is-docker": "^2.0.0" - } - }, "isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -27922,2778 +10587,173 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==" - }, - "istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - } + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true }, "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, "requires": { "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } } }, "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, "requires": { + "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } + "istanbul-lib-coverage": "^3.0.0" } }, "istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, "requires": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, - "jake": { - "version": "10.8.5", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", - "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", - "requires": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", - "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", - "requires": { - "@jest/core": "^27.5.1", - "import-local": "^3.0.2", - "jest-cli": "^27.5.1" - } - }, - "jest-changed-files": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", - "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", - "requires": { - "@jest/types": "^27.5.1", - "execa": "^5.0.0", - "throat": "^6.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-circus": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", - "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", - "requires": { - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.5.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==" - }, - "expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "requires": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" - }, - "jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-cli": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", - "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", - "requires": { - "@jest/core": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "prompts": "^2.0.1", - "yargs": "^16.2.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-config": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", - "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", - "requires": { - "@babel/core": "^7.8.0", - "@jest/test-sequencer": "^27.5.1", - "@jest/types": "^27.5.1", - "babel-jest": "^27.5.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.9", - "jest-circus": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-jasmine2": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-diff": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.2.1.tgz", - "integrity": "sha512-gfh/SMNlQmP3MOUgdzxPOd4XETDJifADpT937fN1iUGz+9DgOu2eUPHH25JDkLVcLwwqxv3GzVyK4VBUr9fjfA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.2.0", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.2.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-docblock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", - "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", - "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", - "requires": { - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-jsdom": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", - "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", - "requires": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1", - "jsdom": "^16.6.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-node": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", - "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", - "requires": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-get-type": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", - "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", - "dev": true - }, - "jest-haste-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", - "requires": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-jasmine2": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", - "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", - "requires": { - "@jest/environment": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.5.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", - "throat": "^6.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==" - }, - "expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "requires": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" - }, - "jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-leak-detector": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", - "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", - "requires": { - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "dependencies": { - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" - } - } - }, - "jest-matcher-utils": { - "version": "29.2.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.2.2.tgz", - "integrity": "sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^29.2.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.2.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-message-util": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.2.1.tgz", - "integrity": "sha512-Dx5nEjw9V8C1/Yj10S/8ivA8F439VS8vTq1L7hEgwHFn9ovSKNpYW/kwNh7UglaEgXO42XxzKJB+2x0nSglFVw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.2.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.2.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz", - "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==" - }, - "jest-regex-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==" - }, - "jest-resolve": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", - "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", - "requires": { - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-resolve-dependencies": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", - "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", - "requires": { - "@jest/types": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-snapshot": "^27.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runner": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", - "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", - "requires": { - "@jest/console": "^27.5.1", - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-leak-detector": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runtime": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", - "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", - "requires": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/globals": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-serializer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", - "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.9" - } - }, - "jest-snapshot": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", - "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", - "requires": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^27.5.1", - "graceful-fs": "^4.2.9", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^27.5.1", - "semver": "^7.3.2" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==" - }, - "expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "requires": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" - }, - "jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - } - }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-util": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.2.1.tgz", - "integrity": "sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g==", - "dev": true, - "requires": { - "@jest/types": "^29.2.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-validate": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", - "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", - "requires": { - "@jest/types": "^27.5.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "leven": "^3.1.0", - "pretty-format": "^27.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-watch-typeahead": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-1.1.0.tgz", - "integrity": "sha512-Va5nLSJTN7YFtC2jd+7wsoe1pNe5K4ShLux/E5iHEwlB9AxaxmggY7to9KUqKojhaJw3aXqt5WAb4jGPOolpEw==", - "requires": { - "ansi-escapes": "^4.3.1", - "chalk": "^4.0.0", - "jest-regex-util": "^28.0.0", - "jest-watcher": "^28.0.0", - "slash": "^4.0.0", - "string-length": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "dependencies": { - "@jest/console": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0" - }, - "dependencies": { - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" - } - } - }, - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", - "requires": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", - "requires": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" - } - } - }, - "jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==" - }, - "jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "jest-watcher": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", - "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", - "requires": { - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^28.1.3", - "string-length": "^4.0.1" - }, - "dependencies": { - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" - } - } - }, - "slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==" - }, - "string-length": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", - "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", - "requires": { - "char-regex": "^2.0.0", - "strip-ansi": "^7.0.1" - }, - "dependencies": { - "char-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz", - "integrity": "sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==" - } - } - }, - "strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "requires": { - "ansi-regex": "^6.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-watcher": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", - "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", - "requires": { - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^27.5.1", - "string-length": "^4.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-sdsl": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", - "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==" - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" } }, "jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "version": "24.1.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.3.tgz", + "integrity": "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==", + "dev": true, "requires": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" + "tough-cookie": "^4.1.4", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" } }, "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==" + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true }, "json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, "requires": { "graceful-fs": "^4.1.6", "universalify": "^2.0.0" } }, - "jsonpointer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", - "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==" - }, - "jsx-ast-utils": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", - "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, "requires": { - "array-includes": "^3.1.5", - "object.assign": "^4.1.3" + "json-buffer": "3.0.1" } }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" - }, - "klona": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", - "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==" - }, - "language-subtag-registry": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", - "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==" - }, - "language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", - "requires": { - "language-subtag-registry": "~0.3.2" - } - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" - }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "requires": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, - "lilconfig": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", - "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==" - }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, - "loader-runner": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", - "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==" - }, - "loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" } }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "requires": { "p-locate": "^5.0.0" } @@ -30703,30 +10763,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" - }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true }, "long": { "version": "5.2.0", @@ -30741,18 +10782,20 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, "requires": { - "tslib": "^2.0.3" + "get-func-name": "^2.0.1" } }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "requires": { "yallist": "^4.0.0" } @@ -30764,179 +10807,123 @@ "dev": true }, "magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, "requires": { - "sourcemap-codec": "^1.4.8" + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" } }, "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, "requires": { - "semver": "^6.0.0" + "semver": "^7.5.3" + }, + "dependencies": { + "semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true + } } }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "requires": { - "tmpl": "1.0.5" - } - }, - "mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" - }, - "memfs": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.9.tgz", - "integrity": "sha512-3rm8kbrzpUGRyPKSGuk387NZOwQ90O4rI9tsWQkzNW7BLSnKGp23RsEsKK8N8QVCrtJoAMqy3spxHC4os4G6PQ==", - "requires": { - "fs-monkey": "^1.0.3" - } + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true }, "memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" }, - "merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" - }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true }, "micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "requires": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true }, "mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "requires": { "mime-db": "1.52.0" } }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" - }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true }, - "mini-css-extract-plugin": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz", - "integrity": "sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg==", - "requires": { - "schema-utils": "^4.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "requires": { - "fast-deep-equal": "^3.1.3" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - } - } - } - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, "requires": { - "minimist": "^1.2.6" + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + }, + "dependencies": { + "pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + } } }, "ms": { @@ -30944,109 +10931,40 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "requires": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - } - }, "nanoid": { "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==" + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "requires": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true }, "node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==" - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==" - }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "requires": { - "path-key": "^3.0.0" - } - }, - "nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "requires": { - "boolbase": "^1.0.0" - } + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true }, "nwsapi": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", - "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==" + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", + "dev": true }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, - "object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==" - }, "object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true }, "object-is": { "version": "1.1.5", @@ -31061,12 +10979,14 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true }, "object.assign": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -31074,117 +10994,34 @@ "object-keys": "^1.1.1" } }, - "object.entries": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", - "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "object.fromentries": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", - "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", - "integrity": "sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==", - "requires": { - "array.prototype.reduce": "^1.0.4", - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.1" - } - }, - "object.hasown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", - "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", - "requires": { - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "requires": { "wrappy": "1" } }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "open": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", - "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", - "requires": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - } - }, "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, "requires": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" } }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "requires": { "yocto-queue": "^0.1.0" } @@ -31193,33 +11030,11 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "requires": { "p-limit": "^3.0.2" } }, - "p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "requires": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "requires": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -31239,64 +11054,62 @@ "lines-and-columns": "^1.1.6" } }, - "parse-srcset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", - "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" - }, "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" + "entities": "^6.0.0" + }, + "dependencies": { + "entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true + } } }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, - "path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" - }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + "pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true }, "picocolors": { "version": "1.1.1", @@ -31306,106 +11119,25 @@ "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==" - }, - "pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==" - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, "requires": { - "find-up": "^4.0.0" + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" }, "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - } - } - } - }, - "pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "requires": { - "find-up": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==" + "pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true } } }, @@ -31413,673 +11145,24 @@ "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, "requires": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, - "postcss-attribute-case-insensitive": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", - "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", - "requires": { - "postcss-selector-parser": "^6.0.10" - } - }, - "postcss-browser-comments": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz", - "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==" - }, - "postcss-calc": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", - "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", - "requires": { - "postcss-selector-parser": "^6.0.9", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-clamp": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", - "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-color-functional-notation": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", - "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-color-hex-alpha": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", - "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-color-rebeccapurple": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", - "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-colormin": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.0.tgz", - "integrity": "sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==", - "requires": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0", - "colord": "^2.9.1", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-convert-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", - "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", - "requires": { - "browserslist": "^4.21.4", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-custom-media": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", - "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-custom-properties": { - "version": "12.1.10", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.10.tgz", - "integrity": "sha512-U3BHdgrYhCrwTVcByFHs9EOBoqcKq4Lf3kXwbTi4hhq0qWhl/pDWq2THbv/ICX/Fl9KqeHBb8OVrTf2OaYF07A==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-custom-selectors": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", - "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", - "requires": { - "postcss-selector-parser": "^6.0.4" - } - }, - "postcss-dir-pseudo-class": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", - "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", - "requires": { - "postcss-selector-parser": "^6.0.10" - } - }, - "postcss-discard-comments": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", - "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==" - }, - "postcss-discard-duplicates": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==" - }, - "postcss-discard-empty": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==" - }, - "postcss-discard-overridden": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==" - }, - "postcss-double-position-gradients": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", - "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", - "requires": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-env-function": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", - "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-flexbugs-fixes": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz", - "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==" - }, - "postcss-focus-visible": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", - "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", - "requires": { - "postcss-selector-parser": "^6.0.9" - } - }, - "postcss-focus-within": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", - "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", - "requires": { - "postcss-selector-parser": "^6.0.9" - } - }, - "postcss-font-variant": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==" - }, - "postcss-gap-properties": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", - "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==" - }, - "postcss-image-set-function": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", - "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-import": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", - "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", - "requires": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - } - }, - "postcss-initial": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", - "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==" - }, - "postcss-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz", - "integrity": "sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==", - "requires": { - "camelcase-css": "^2.0.1" - } - }, - "postcss-lab-function": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", - "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", - "requires": { - "@csstools/postcss-progressive-custom-properties": "^1.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", - "requires": { - "lilconfig": "^2.0.5", - "yaml": "^1.10.2" - } - }, - "postcss-loader": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", - "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", - "requires": { - "cosmiconfig": "^7.0.0", - "klona": "^2.0.5", - "semver": "^7.3.5" - }, - "dependencies": { - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "postcss-logical": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", - "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==" - }, - "postcss-media-minmax": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", - "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==" - }, - "postcss-merge-longhand": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", - "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", - "requires": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^5.1.1" - } - }, - "postcss-merge-rules": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.3.tgz", - "integrity": "sha512-LbLd7uFC00vpOuMvyZop8+vvhnfRGpp2S+IMQKeuOZZapPRY4SMq5ErjQeHbHsjCUgJkRNrlU+LmxsKIqPKQlA==", - "requires": { - "browserslist": "^4.21.4", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^3.1.0", - "postcss-selector-parser": "^6.0.5" - } - }, - "postcss-minify-font-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", - "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-gradients": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", - "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", - "requires": { - "colord": "^2.9.1", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-params": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", - "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", - "requires": { - "browserslist": "^4.21.4", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-selectors": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", - "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", - "requires": { - "postcss-selector-parser": "^6.0.5" - } - }, - "postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==" - }, - "postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", - "requires": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", - "requires": { - "postcss-selector-parser": "^6.0.4" - } - }, - "postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "requires": { - "icss-utils": "^5.0.0" - } - }, - "postcss-nested": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", - "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", - "requires": { - "postcss-selector-parser": "^6.0.10" - } - }, - "postcss-nesting": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", - "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", - "requires": { - "@csstools/selector-specificity": "^2.0.0", - "postcss-selector-parser": "^6.0.10" - } - }, - "postcss-normalize": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-10.0.1.tgz", - "integrity": "sha512-+5w18/rDev5mqERcG3W5GZNMJa1eoYYNGo8gB7tEwaos0ajk3ZXAI4mHGcNT47NE+ZnZD1pEpUOFLvltIwmeJA==", - "requires": { - "@csstools/normalize.css": "*", - "postcss-browser-comments": "^4", - "sanitize.css": "*" - } - }, - "postcss-normalize-charset": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==" - }, - "postcss-normalize-display-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", - "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-positions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", - "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-repeat-style": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", - "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-string": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", - "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-timing-functions": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", - "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-unicode": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", - "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", - "requires": { - "browserslist": "^4.21.4", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", - "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", - "requires": { - "normalize-url": "^6.0.1", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-whitespace": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", - "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-opacity-percentage": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.2.tgz", - "integrity": "sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w==" - }, - "postcss-ordered-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", - "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", - "requires": { - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-overflow-shorthand": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", - "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-page-break": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==" - }, - "postcss-place": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", - "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-preset-env": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.2.tgz", - "integrity": "sha512-rSMUEaOCnovKnwc5LvBDHUDzpGP+nrUeWZGWt9M72fBvckCi45JmnJigUr4QG4zZeOHmOCNCZnd2LKDvP++ZuQ==", - "requires": { - "@csstools/postcss-cascade-layers": "^1.1.0", - "@csstools/postcss-color-function": "^1.1.1", - "@csstools/postcss-font-format-keywords": "^1.0.1", - "@csstools/postcss-hwb-function": "^1.0.2", - "@csstools/postcss-ic-unit": "^1.0.1", - "@csstools/postcss-is-pseudo-class": "^2.0.7", - "@csstools/postcss-nested-calc": "^1.0.0", - "@csstools/postcss-normalize-display-values": "^1.0.1", - "@csstools/postcss-oklab-function": "^1.1.1", - "@csstools/postcss-progressive-custom-properties": "^1.3.0", - "@csstools/postcss-stepped-value-functions": "^1.0.1", - "@csstools/postcss-text-decoration-shorthand": "^1.0.0", - "@csstools/postcss-trigonometric-functions": "^1.0.2", - "@csstools/postcss-unset-value": "^1.0.2", - "autoprefixer": "^10.4.11", - "browserslist": "^4.21.3", - "css-blank-pseudo": "^3.0.3", - "css-has-pseudo": "^3.0.4", - "css-prefers-color-scheme": "^6.0.3", - "cssdb": "^7.0.1", - "postcss-attribute-case-insensitive": "^5.0.2", - "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^4.2.4", - "postcss-color-hex-alpha": "^8.0.4", - "postcss-color-rebeccapurple": "^7.1.1", - "postcss-custom-media": "^8.0.2", - "postcss-custom-properties": "^12.1.9", - "postcss-custom-selectors": "^6.0.3", - "postcss-dir-pseudo-class": "^6.0.5", - "postcss-double-position-gradients": "^3.1.2", - "postcss-env-function": "^4.0.6", - "postcss-focus-visible": "^6.0.4", - "postcss-focus-within": "^5.0.4", - "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^3.0.5", - "postcss-image-set-function": "^4.0.7", - "postcss-initial": "^4.0.1", - "postcss-lab-function": "^4.2.1", - "postcss-logical": "^5.0.4", - "postcss-media-minmax": "^5.0.0", - "postcss-nesting": "^10.2.0", - "postcss-opacity-percentage": "^1.1.2", - "postcss-overflow-shorthand": "^3.0.4", - "postcss-page-break": "^3.0.4", - "postcss-place": "^7.0.5", - "postcss-pseudo-class-any-link": "^7.1.6", - "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^6.0.1", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-pseudo-class-any-link": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", - "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", - "requires": { - "postcss-selector-parser": "^6.0.10" - } - }, - "postcss-reduce-initial": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.1.tgz", - "integrity": "sha512-//jeDqWcHPuXGZLoolFrUXBDyuEGbr9S2rMo19bkTIjBQ4PqkaO+oI8wua5BOUxpfi97i3PCoInsiFIEBfkm9w==", - "requires": { - "browserslist": "^4.21.4", - "caniuse-api": "^3.0.0" - } - }, - "postcss-reduce-transforms": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", - "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-replace-overflow-wrap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==" - }, - "postcss-selector-not": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", - "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", - "requires": { - "postcss-selector-parser": "^6.0.10" - } - }, - "postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "postcss-svgo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", - "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", - "requires": { - "postcss-value-parser": "^4.2.0", - "svgo": "^2.7.0" - }, - "dependencies": { - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" - }, - "css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "requires": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - } - }, - "mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", - "requires": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" - } - } - } - }, - "postcss-unique-selectors": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", - "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", - "requires": { - "postcss-selector-parser": "^6.0.5" - } - }, - "postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" - }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" - }, - "pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==" - }, - "pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "requires": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true }, "pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, "requires": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -32089,37 +11172,17 @@ "ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true } } }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "promise": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "requires": { - "asap": "~2.0.6" - } - }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, "prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -32156,106 +11219,32 @@ "long": "^5.0.0" } }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "dependencies": { - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - } - } - }, "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "requires": { + "punycode": "^2.3.1" + } }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==" - }, - "qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "requires": { - "side-channel": "^1.0.6" - } + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true }, "querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" - }, - "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" - }, - "raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "requires": { - "performance-now": "^2.1.0" - } - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "dependencies": { - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } - } + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true }, "react": { "version": "18.2.0", @@ -32265,100 +11254,6 @@ "loose-envify": "^1.1.0" } }, - "react-app-polyfill": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", - "integrity": "sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w==", - "requires": { - "core-js": "^3.19.2", - "object-assign": "^4.1.1", - "promise": "^8.1.0", - "raf": "^3.4.1", - "regenerator-runtime": "^0.13.9", - "whatwg-fetch": "^3.6.2" - } - }, - "react-dev-utils": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", - "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", - "requires": { - "@babel/code-frame": "^7.16.0", - "address": "^1.1.2", - "browserslist": "^4.18.1", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.3", - "detect-port-alt": "^1.1.6", - "escape-string-regexp": "^4.0.0", - "filesize": "^8.0.6", - "find-up": "^5.0.0", - "fork-ts-checker-webpack-plugin": "^6.5.0", - "global-modules": "^2.0.0", - "globby": "^11.0.4", - "gzip-size": "^6.0.0", - "immer": "^9.0.7", - "is-root": "^2.1.0", - "loader-utils": "^3.2.0", - "open": "^8.4.0", - "pkg-up": "^3.1.0", - "prompts": "^2.4.2", - "react-error-overlay": "^6.0.11", - "recursive-readdir": "^2.2.2", - "shell-quote": "^1.7.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "loader-utils": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", - "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -32368,11 +11263,6 @@ "scheduler": "^0.23.0" } }, - "react-error-overlay": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", - "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" - }, "react-final-form": { "version": "6.5.9", "resolved": "https://registry.npmjs.org/react-final-form/-/react-final-form-6.5.9.tgz", @@ -32416,11 +11306,6 @@ "use-sync-external-store": "^1.0.0" } }, - "react-refresh": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", - "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" - }, "react-router": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.2.tgz", @@ -32438,71 +11323,6 @@ "react-router": "6.4.2" } }, - "react-scripts": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", - "integrity": "sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==", - "requires": { - "@babel/core": "^7.16.0", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", - "@svgr/webpack": "^5.5.0", - "babel-jest": "^27.4.2", - "babel-loader": "^8.2.3", - "babel-plugin-named-asset-import": "^0.3.8", - "babel-preset-react-app": "^10.0.1", - "bfj": "^7.0.2", - "browserslist": "^4.18.1", - "camelcase": "^6.2.1", - "case-sensitive-paths-webpack-plugin": "^2.4.0", - "css-loader": "^6.5.1", - "css-minimizer-webpack-plugin": "^3.2.0", - "dotenv": "^10.0.0", - "dotenv-expand": "^5.1.0", - "eslint": "^8.3.0", - "eslint-config-react-app": "^7.0.1", - "eslint-webpack-plugin": "^3.1.1", - "file-loader": "^6.2.0", - "fs-extra": "^10.0.0", - "fsevents": "^2.3.2", - "html-webpack-plugin": "^5.5.0", - "identity-obj-proxy": "^3.0.0", - "jest": "^27.4.3", - "jest-resolve": "^27.4.2", - "jest-watch-typeahead": "^1.0.0", - "mini-css-extract-plugin": "^2.4.5", - "postcss": "^8.4.4", - "postcss-flexbugs-fixes": "^5.0.2", - "postcss-loader": "^6.2.1", - "postcss-normalize": "^10.0.1", - "postcss-preset-env": "^7.0.1", - "prompts": "^2.4.2", - "react-app-polyfill": "^3.0.0", - "react-dev-utils": "^12.0.1", - "react-refresh": "^0.11.0", - "resolve": "^1.20.0", - "resolve-url-loader": "^4.0.0", - "sass-loader": "^12.3.0", - "semver": "^7.3.5", - "source-map-loader": "^3.0.0", - "style-loader": "^3.3.1", - "tailwindcss": "^3.0.2", - "terser-webpack-plugin": "^5.2.5", - "webpack": "^5.64.4", - "webpack-dev-server": "^4.6.0", - "webpack-manifest-plugin": "^4.0.2", - "workbox-webpack-plugin": "^6.4.1" - }, - "dependencies": { - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, "react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -32528,40 +11348,6 @@ "memoize-one": ">=3.1.1 <6" } }, - "read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "requires": { - "pify": "^2.3.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "requires": { - "picomatch": "^2.2.1" - } - }, - "recursive-readdir": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", - "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", - "requires": { - "minimatch": "^3.0.5" - } - }, "redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -32607,41 +11393,11 @@ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==" }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" - }, - "regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "requires": { - "regenerate": "^1.4.2" - } - }, - "regenerator-runtime": { - "version": "0.13.10", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", - "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==" - }, - "regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regex-parser": { - "version": "2.2.11", - "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", - "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==" - }, "regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -32651,72 +11407,14 @@ "regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==" - }, - "regexpu-core": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.1.tgz", - "integrity": "sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ==", - "requires": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - } - }, - "regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==" - }, - "regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==" - } - } - }, - "relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==" - }, - "renderkid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "requires": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true }, "resolve": { "version": "1.22.1", @@ -32728,138 +11426,37 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" - } - } - }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, - "resolve-url-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz", - "integrity": "sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA==", - "requires": { - "adjust-sourcemap-loader": "^4.0.0", - "convert-source-map": "^1.7.0", - "loader-utils": "^2.0.0", - "postcss": "^7.0.35", - "source-map": "0.6.1" - }, - "dependencies": { - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" - }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==" - }, - "retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" - }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, "requires": { "glob": "^7.1.3" } }, - "rollup": { - "version": "2.79.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", - "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", - "requires": { - "fsevents": "~2.3.2" - } - }, - "rollup-plugin-terser": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", - "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", - "requires": { - "@babel/code-frame": "^7.10.4", - "jest-worker": "^26.2.1", - "serialize-javascript": "^4.0.0", - "terser": "^5.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - } - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "requires": { - "randombytes": "^2.1.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } + "rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "requires": { "queue-microtask": "^1.2.2" } @@ -32872,15 +11469,11 @@ "tslib": "^2.1.0" } }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, "safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -32890,90 +11483,14 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sanitize-html": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.0.tgz", - "integrity": "sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==", - "requires": { - "deepmerge": "^4.2.2", - "escape-string-regexp": "^4.0.0", - "htmlparser2": "^8.0.0", - "is-plain-object": "^5.0.0", - "parse-srcset": "^1.0.2", - "postcss": "^8.3.11" - }, - "dependencies": { - "dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "requires": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - } - }, - "domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "requires": { - "domelementtype": "^2.3.0" - } - }, - "domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "requires": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - } - }, - "entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" - }, - "htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "requires": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - } - } - }, - "sanitize.css": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", - "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==" - }, - "sass-loader": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", - "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", - "requires": { - "klona": "^2.0.4", - "neo-async": "^2.6.2" - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, "requires": { "xmlchars": "^2.2.0" } @@ -32986,164 +11503,17 @@ "loose-envify": "^1.1.0" } }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" - }, - "selfsigned": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", - "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", - "requires": { - "node-forge": "^1" - } - }, "semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" - }, - "send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, - "serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "requires": { - "randombytes": "^2.1.0" - } - }, - "serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "requires": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" - } - } - }, - "serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "requires": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - } + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true }, "set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, "requires": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -33153,15 +11523,11 @@ "has-property-descriptors": "^1.0.2" } }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -33169,17 +11535,14 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" - }, - "shell-quote": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.4.tgz", - "integrity": "sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==" + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true }, "side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, "requires": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -33187,35 +11550,17 @@ "object-inspect": "^1.13.1" } }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + "siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" - }, - "sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "requires": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true }, "source-map": { "version": "0.5.7", @@ -33225,157 +11570,26 @@ "source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true }, - "source-map-loader": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.2.tgz", - "integrity": "sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==", - "requires": { - "abab": "^2.0.5", - "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.1" - } + "stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" - }, - "spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "requires": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - } - }, - "spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "requires": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" - }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" - }, - "stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" - } - } - }, - "stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-natural-compare": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", - "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==" - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - } - } - }, - "string.prototype.matchall": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", - "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.1", - "side-channel": "^1.0.4" - } + "std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true }, "string.prototype.trimend": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -33386,45 +11600,22 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", "es-abstract": "^1.19.5" } }, - "stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "requires": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - } - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "requires": { "ansi-regex": "^5.0.1" } }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==" - }, - "strip-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", - "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==" - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" - }, "strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -33437,20 +11628,24 @@ "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true }, - "style-loader": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", - "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==" - }, - "stylehacks": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", - "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, "requires": { - "browserslist": "^4.21.4", - "postcss-selector-parser": "^6.0.4" + "js-tokens": "^9.0.1" + }, + "dependencies": { + "js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true + } } }, "stylis": { @@ -33459,35 +11654,12 @@ "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } + "has-flag": "^4.0.0" } }, "supports-preserve-symlinks-flag": { @@ -33495,240 +11667,17 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, - "svg-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" - }, - "svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "requires": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - }, - "dependencies": { - "css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "css-what": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", - "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==" - }, - "dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "requires": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - } - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - }, - "dependencies": { - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - } - } - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "requires": { - "boolbase": "~1.0.0" - } - } - } - }, "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" - }, - "tailwindcss": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.1.tgz", - "integrity": "sha512-Uw+GVSxp5CM48krnjHObqoOwlCt5Qo6nw1jlCRwfGy68dSYb/LwS9ZFidYGRiM+w6rMawkZiu1mEMAsHYAfoLg==", - "requires": { - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "color-name": "^1.1.4", - "detective": "^5.2.1", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.2.12", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "lilconfig": "^2.0.6", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.17", - "postcss-import": "^14.1.0", - "postcss-js": "^4.0.0", - "postcss-load-config": "^3.1.4", - "postcss-nested": "6.0.0", - "postcss-selector-parser": "^6.0.10", - "postcss-value-parser": "^4.2.0", - "quick-lru": "^5.1.1", - "resolve": "^1.22.1" - }, - "dependencies": { - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "requires": { - "is-glob": "^4.0.3" - } - } - } - }, - "tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==" - }, - "temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==" - }, - "tempy": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", - "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", - "requires": { - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" - }, - "dependencies": { - "type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==" - } - } - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "terser": { - "version": "5.39.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.2.tgz", - "integrity": "sha512-yEPUmWve+VA78bI71BW70Dh0TuV4HHd+I5SHOAfS1+QBOmvmCiiffgjR8ryyEd3KIfvPGFqoADt8LdQ6XpXIvg==", - "requires": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.14.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - } - } - }, - "terser-webpack-plugin": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", - "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", - "requires": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "dependencies": { - "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "requires": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - } - }, - "ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "requires": { - "fast-deep-equal": "^3.1.3" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - } - } - } + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true }, "test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, "requires": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -33738,40 +11687,41 @@ "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true }, - "throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==" + "tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true }, - "thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + "tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" + "tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "requires": { "is-number": "^7.0.0" } }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, "tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, "requires": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -33782,48 +11732,25 @@ "universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true } } }, "tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, "requires": { - "punycode": "^2.1.1" + "punycode": "^2.3.1" } }, - "tryer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", - "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" - }, - "tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "requires": { - "minimist": "^1.2.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==" - } - } + "tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "dev": true }, "tslib": { "version": "2.4.0", @@ -33834,6 +11761,7 @@ "version": "3.21.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, "requires": { "tslib": "^1.8.1" }, @@ -33841,7 +11769,8 @@ "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true } } }, @@ -33849,6 +11778,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "requires": { "prelude-ls": "^1.2.1" } @@ -33856,29 +11786,14 @@ "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true }, "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "requires": { - "is-typedarray": "^1.0.0" - } + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true }, "typescript": { "version": "4.8.4", @@ -33886,10 +11801,17 @@ "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", "dev": true }, + "ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true + }, "unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-bigints": "^1.0.2", @@ -33897,62 +11819,17 @@ "which-boxed-primitive": "^1.0.2" } }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==" - }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==" - }, - "unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==" - }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "requires": { - "crypto-random-string": "^2.0.0" - } - }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" - }, - "unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==" - }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true }, "update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, "requires": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -33962,6 +11839,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "requires": { "punycode": "^2.1.0" } @@ -33970,6 +11848,7 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, "requires": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -33980,393 +11859,245 @@ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==" }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" - } - }, - "utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - }, - "v8-to-istanbul": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", - "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" + "esbuild": "^0.21.3", + "fsevents": "~2.3.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "dependencies": { - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" + "rollup": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "@types/estree": "1.0.8", + "fsevents": "~2.3.2" + } } } }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + "vite-node": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", + "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", + "dev": true, + "requires": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + } + }, + "vite-tsconfig-paths": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz", + "integrity": "sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + } + }, + "vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", + "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", + "dev": true, + "requires": { + "@vitest/expect": "1.6.1", + "@vitest/runner": "1.6.1", + "@vitest/snapshot": "1.6.1", + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.1", + "why-is-node-running": "^2.2.2" + }, + "dependencies": { + "acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "requires": { + "acorn": "^8.11.0" + } + }, + "execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + } + }, + "get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true + }, + "human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true + }, + "is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true + }, + "mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true + }, + "npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "requires": { + "path-key": "^4.0.0" + } + }, + "onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "requires": { + "mimic-fn": "^4.0.0" + } + }, + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true + } + } }, "void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==" }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, "requires": { - "xml-name-validator": "^3.0.0" - } - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "requires": { - "makeerror": "1.0.12" - } - }, - "watchpack": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", - "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "requires": { - "minimalistic-assert": "^1.0.0" + "xml-name-validator": "^5.0.0" } }, "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" - }, - "webpack": { - "version": "5.105.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", - "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", - "requires": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.28.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.19.0", - "es-module-lexer": "^2.0.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.3.1", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.16", - "watchpack": "^2.5.1", - "webpack-sources": "^3.3.3" - }, - "dependencies": { - "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "requires": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - } - }, - "ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "requires": { - "fast-deep-equal": "^3.1.3" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - } - } - } - }, - "webpack-dev-middleware": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", - "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", - "requires": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "requires": { - "fast-deep-equal": "^3.1.3" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - } - } - } - }, - "webpack-dev-server": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz", - "integrity": "sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==", - "requires": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.1", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.4.2" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "requires": { - "fast-deep-equal": "^3.1.3" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - } - }, - "ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==" - } - } - }, - "webpack-manifest-plugin": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz", - "integrity": "sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow==", - "requires": { - "tapable": "^2.0.0", - "webpack-sources": "^2.2.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "webpack-sources": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", - "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", - "requires": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" - } - } - } - }, - "webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==" - }, - "websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "requires": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - } - }, - "websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true }, "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, "requires": { - "iconv-lite": "0.4.24" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } + "iconv-lite": "0.6.3" } }, - "whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" - }, "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true }, "whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, "requires": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" } }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "requires": { "isexe": "^2.0.0" } @@ -34375,6 +12106,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, "requires": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -34409,378 +12141,62 @@ "is-typed-array": "^1.1.9" } }, + "why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "requires": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + } + }, "word-wrap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", - "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==" - }, - "workbox-background-sync": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz", - "integrity": "sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==", - "requires": { - "idb": "^7.0.1", - "workbox-core": "6.5.4" - } - }, - "workbox-broadcast-update": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz", - "integrity": "sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==", - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-build": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.5.4.tgz", - "integrity": "sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==", - "requires": { - "@apideck/better-ajv-errors": "^0.3.1", - "@babel/core": "^7.11.1", - "@babel/preset-env": "^7.11.0", - "@babel/runtime": "^7.11.2", - "@rollup/plugin-babel": "^5.2.0", - "@rollup/plugin-node-resolve": "^11.2.1", - "@rollup/plugin-replace": "^2.4.1", - "@surma/rollup-plugin-off-main-thread": "^2.2.3", - "ajv": "^8.6.0", - "common-tags": "^1.8.0", - "fast-json-stable-stringify": "^2.1.0", - "fs-extra": "^9.0.1", - "glob": "^7.1.6", - "lodash": "^4.17.20", - "pretty-bytes": "^5.3.0", - "rollup": "^2.43.1", - "rollup-plugin-terser": "^7.0.0", - "source-map": "^0.8.0-beta.0", - "stringify-object": "^3.3.0", - "strip-comments": "^2.0.1", - "tempy": "^0.6.0", - "upath": "^1.2.0", - "workbox-background-sync": "6.5.4", - "workbox-broadcast-update": "6.5.4", - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-google-analytics": "6.5.4", - "workbox-navigation-preload": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-range-requests": "6.5.4", - "workbox-recipes": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4", - "workbox-streams": "6.5.4", - "workbox-sw": "6.5.4", - "workbox-window": "6.5.4" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "requires": { - "whatwg-url": "^7.0.0" - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "requires": { - "punycode": "^2.1.0" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" - }, - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } - } - }, - "workbox-cacheable-response": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz", - "integrity": "sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==", - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-core": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.5.4.tgz", - "integrity": "sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==" - }, - "workbox-expiration": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.5.4.tgz", - "integrity": "sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==", - "requires": { - "idb": "^7.0.1", - "workbox-core": "6.5.4" - } - }, - "workbox-google-analytics": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz", - "integrity": "sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==", - "requires": { - "workbox-background-sync": "6.5.4", - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "workbox-navigation-preload": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz", - "integrity": "sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==", - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-precaching": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.5.4.tgz", - "integrity": "sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==", - "requires": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "workbox-range-requests": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz", - "integrity": "sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==", - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-recipes": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.5.4.tgz", - "integrity": "sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==", - "requires": { - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" - } - }, - "workbox-routing": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.5.4.tgz", - "integrity": "sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==", - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-strategies": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.5.4.tgz", - "integrity": "sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==", - "requires": { - "workbox-core": "6.5.4" - } - }, - "workbox-streams": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.5.4.tgz", - "integrity": "sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==", - "requires": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4" - } - }, - "workbox-sw": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.5.4.tgz", - "integrity": "sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==" - }, - "workbox-webpack-plugin": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.4.tgz", - "integrity": "sha512-LmWm/zoaahe0EGmMTrSLUi+BjyR3cdGEfU3fS6PN1zKFYbqAKuQ+Oy/27e4VSXsyIwAw8+QDfk1XHNGtZu9nQg==", - "requires": { - "fast-json-stable-stringify": "^2.1.0", - "pretty-bytes": "^5.4.1", - "upath": "^1.2.0", - "webpack-sources": "^1.4.3", - "workbox-build": "6.5.4" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - } - } - }, - "workbox-window": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.5.4.tgz", - "integrity": "sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==", - "requires": { - "@types/trusted-types": "^2.0.2", - "workbox-core": "6.5.4" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - } - } + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==" + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true }, "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true }, "xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" - }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/webclient/package.json b/webclient/package.json index a578fc9d9..9c24e60a4 100644 --- a/webclient/package.json +++ b/webclient/package.json @@ -5,13 +5,13 @@ "scripts": { "prebuild": "node prebuild.js", "prestart": "node prebuild.js", - "build": "react-scripts build", - "start": "react-scripts start", - "test": "react-scripts test", - "test:watch": "react-scripts test", - "eject": "react-scripts eject", - "lint": "eslint \"./**/*.{ts,tsx}\"", - "lint:fix": "eslint \"./**/*.{ts,tsx}\" --fix", + "build": "vite build", + "start": "vite", + "preview": "vite preview", + "test": "vitest run", + "test:watch": "vitest", + "lint": "eslint src/**/*.{ts,tsx}", + "lint:fix": "eslint src/**/*.{ts,tsx} --fix", "golden": "npm run lint && npm run test", "prepare": "cd .. && husky install", "translate": "node prebuild.js -i18nOnly" @@ -23,6 +23,7 @@ "@mui/material": "^5.5.1", "crypto-js": "^4.2.0", "dexie": "^3.2.2", + "dompurify": "^3.3.3", "final-form": "^4.20.6", "final-form-set-field-touched": "^1.0.1", "i18next": "^22.0.4", @@ -39,21 +40,18 @@ "react-i18next": "^12.0.0", "react-redux": "^8.0.4", "react-router-dom": "^6.2.2", - "react-scripts": "5.0.1", "react-virtualized-auto-sizer": "^1.0.6", "react-window": "^1.8.6", "redux": "^4.1.2", "redux-form": "^8.3.8", "redux-thunk": "^2.4.1", - "rxjs": "^7.5.4", - "sanitize-html": "^2.7.3" + "rxjs": "^7.5.4" }, "devDependencies": { - "@babel/core": "^7.17.5", "@mui/types": "^7.1.3", - "@testing-library/jest-dom": "^5.16.2", + "@testing-library/jest-dom": "^6.4.0", "@testing-library/react": "^13.4.0", - "@types/jest": "29.2.0", + "@types/dompurify": "^3.0.5", "@types/jquery": "^3.5.14", "@types/lodash": "^4.14.179", "@types/node": "18.11.7", @@ -67,12 +65,16 @@ "@types/redux-form": "^8.3.3", "@typescript-eslint/eslint-plugin": "^5.14.0", "@typescript-eslint/parser": "^5.14.0", + "@vitejs/plugin-react": "^4.2.0", + "@vitest/coverage-v8": "^1.3.0", + "eslint": "^8.0.0", "fs-extra": "^10.0.1", "husky": "^8.0.1", - "typescript": "^4.6.2" - }, - "eslintConfig": { - "extends": "react-app" + "jsdom": "^24.0.0", + "typescript": "^4.6.2", + "vite": "^5.1.0", + "vite-tsconfig-paths": "^4.3.1", + "vitest": "^1.3.0" }, "browserslist": { "production": [ @@ -85,10 +87,5 @@ "last 1 firefox version", "last 1 safari version" ] - }, - "jest": { - "moduleNameMapper": { - "\\.(css|less)$": "identity-obj-proxy" - } } } diff --git a/webclient/public/index.html b/webclient/public/index.html deleted file mode 100644 index c050e82f4..000000000 --- a/webclient/public/index.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - Webatrice - - - -
- - - diff --git a/webclient/public/manifest.json b/webclient/public/manifest.json index 080d6c77a..0d8f049ac 100644 --- a/webclient/public/manifest.json +++ b/webclient/public/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "React App", - "name": "Create React App Sample", + "short_name": "Webatrice", + "name": "Webatrice", "icons": [ { "src": "favicon.ico", diff --git a/webclient/src/api/AdminService.spec.ts b/webclient/src/api/AdminService.spec.ts index be8e84c99..d14207629 100644 --- a/webclient/src/api/AdminService.spec.ts +++ b/webclient/src/api/AdminService.spec.ts @@ -1,16 +1,16 @@ -jest.mock('websocket', () => ({ +vi.mock('websocket', () => ({ AdminCommands: { - adjustMod: jest.fn(), - reloadConfig: jest.fn(), - shutdownServer: jest.fn(), - updateServerMessage: jest.fn(), + adjustMod: vi.fn(), + reloadConfig: vi.fn(), + shutdownServer: vi.fn(), + updateServerMessage: vi.fn(), }, })); import { AdminService } from './AdminService'; import { AdminCommands } from 'websocket'; -beforeEach(() => jest.clearAllMocks()); +beforeEach(() => vi.clearAllMocks()); describe('AdminService', () => { describe('adjustMod', () => { diff --git a/webclient/src/api/AuthenticationService.spec.ts b/webclient/src/api/AuthenticationService.spec.ts index 234ecbe47..460c8af0f 100644 --- a/webclient/src/api/AuthenticationService.spec.ts +++ b/webclient/src/api/AuthenticationService.spec.ts @@ -1,14 +1,14 @@ -jest.mock('websocket', () => ({ +vi.mock('websocket', () => ({ SessionCommands: { - connect: jest.fn(), - disconnect: jest.fn(), + connect: vi.fn(), + disconnect: vi.fn(), }, webClient: { connectionAttemptMade: false, }, })); -jest.mock('websocket/services/ProtoController', () => ({ +vi.mock('websocket/services/ProtoController', () => ({ ProtoController: { root: { ServerInfo_User: { @@ -26,7 +26,7 @@ import { StatusEnum, WebSocketConnectOptions, WebSocketConnectReason } from 'typ const testOptions: WebSocketConnectOptions = { host: 'localhost', port: '4748', userName: 'user', password: 'pw' }; -beforeEach(() => jest.clearAllMocks()); +beforeEach(() => vi.clearAllMocks()); describe('AuthenticationService', () => { describe('login', () => { diff --git a/webclient/src/api/ModeratorService.spec.ts b/webclient/src/api/ModeratorService.spec.ts index ab83bfdd2..00c92d447 100644 --- a/webclient/src/api/ModeratorService.spec.ts +++ b/webclient/src/api/ModeratorService.spec.ts @@ -1,11 +1,11 @@ -jest.mock('websocket', () => ({ +vi.mock('websocket', () => ({ ModeratorCommands: { - banFromServer: jest.fn(), - getBanHistory: jest.fn(), - getWarnHistory: jest.fn(), - getWarnList: jest.fn(), - viewLogHistory: jest.fn(), - warnUser: jest.fn(), + banFromServer: vi.fn(), + getBanHistory: vi.fn(), + getWarnHistory: vi.fn(), + getWarnList: vi.fn(), + viewLogHistory: vi.fn(), + warnUser: vi.fn(), }, })); @@ -13,7 +13,7 @@ import { ModeratorService } from './ModeratorService'; import { ModeratorCommands } from 'websocket'; import { LogFilters } from 'types'; -beforeEach(() => jest.clearAllMocks()); +beforeEach(() => vi.clearAllMocks()); describe('ModeratorService', () => { describe('banFromServer', () => { diff --git a/webclient/src/api/RoomsService.spec.ts b/webclient/src/api/RoomsService.spec.ts index c99a5ed06..541efb904 100644 --- a/webclient/src/api/RoomsService.spec.ts +++ b/webclient/src/api/RoomsService.spec.ts @@ -1,17 +1,17 @@ -jest.mock('websocket', () => ({ +vi.mock('websocket', () => ({ SessionCommands: { - joinRoom: jest.fn(), + joinRoom: vi.fn(), }, RoomCommands: { - leaveRoom: jest.fn(), - roomSay: jest.fn(), + leaveRoom: vi.fn(), + roomSay: vi.fn(), }, })); import { RoomsService } from './RoomsService'; import { RoomCommands, SessionCommands } from 'websocket'; -beforeEach(() => jest.clearAllMocks()); +beforeEach(() => vi.clearAllMocks()); describe('RoomsService', () => { describe('joinRoom', () => { diff --git a/webclient/src/api/SessionService.spec.ts b/webclient/src/api/SessionService.spec.ts index b60631448..8e67219bc 100644 --- a/webclient/src/api/SessionService.spec.ts +++ b/webclient/src/api/SessionService.spec.ts @@ -1,22 +1,22 @@ -jest.mock('websocket', () => ({ +vi.mock('websocket', () => ({ SessionCommands: { - addToBuddyList: jest.fn(), - removeFromBuddyList: jest.fn(), - addToIgnoreList: jest.fn(), - removeFromIgnoreList: jest.fn(), - accountPassword: jest.fn(), - accountEdit: jest.fn(), - accountImage: jest.fn(), - message: jest.fn(), - getUserInfo: jest.fn(), - getGamesOfUser: jest.fn(), + addToBuddyList: vi.fn(), + removeFromBuddyList: vi.fn(), + addToIgnoreList: vi.fn(), + removeFromIgnoreList: vi.fn(), + accountPassword: vi.fn(), + accountEdit: vi.fn(), + accountImage: vi.fn(), + message: vi.fn(), + getUserInfo: vi.fn(), + getGamesOfUser: vi.fn(), }, })); import { SessionService } from './SessionService'; import { SessionCommands } from 'websocket'; -beforeEach(() => jest.clearAllMocks()); +beforeEach(() => vi.clearAllMocks()); describe('SessionService', () => { describe('addToBuddyList', () => { diff --git a/webclient/src/hooks/useFireOnce/useFireOnce.spec.tsx b/webclient/src/hooks/useFireOnce/useFireOnce.spec.tsx index 2f6ed34e0..8445bb567 100644 --- a/webclient/src/hooks/useFireOnce/useFireOnce.spec.tsx +++ b/webclient/src/hooks/useFireOnce/useFireOnce.spec.tsx @@ -10,7 +10,7 @@ import { useFireOnce } from './useFireOnce'; describe('useFireOnce hook', () => { test('it only fires once when button is clicked twice', async () => { // Mock a promise with a delay - const onClickWithPromise = jest.fn((e) => { + const onClickWithPromise = vi.fn((e) => { e.preventDefault() return new Promise((resolve) => { setTimeout(() => { @@ -54,7 +54,7 @@ describe('useFireOnce hook', () => { test('it only fires once when form is submitted twice', async () => { // Mock a promise with a delay - const onClickWithPromise = jest.fn((e) => { + const onClickWithPromise = vi.fn((e) => { e.preventDefault() return new Promise((resolve) => { setTimeout(() => { diff --git a/webclient/src/i18n-backend.ts b/webclient/src/i18n-backend.ts index d270b3503..0a92a3a0c 100644 --- a/webclient/src/i18n-backend.ts +++ b/webclient/src/i18n-backend.ts @@ -4,7 +4,7 @@ import { Language } from 'types'; class I18nBackend { static type: ModuleType = 'backend'; - static BASE_URL = `${process.env.PUBLIC_URL}/locales`; + static BASE_URL = `${import.meta.env.BASE_URL}locales`; read(language, namespace, callback) { if (!Language[language]) { diff --git a/webclient/src/images/countries/_Countries.ts b/webclient/src/images/countries/_Countries.ts index a4867db46..ed57e1f31 100644 --- a/webclient/src/images/countries/_Countries.ts +++ b/webclient/src/images/countries/_Countries.ts @@ -1,256 +1,254 @@ -// Remove !file-loader! once the following is no longer an issue -// https://github.com/facebook/create-react-app/issues/11770 -import ad from '!file-loader!./ad.svg'; -import ae from '!file-loader!./ae.svg'; -import af from '!file-loader!./af.svg'; -import ag from '!file-loader!./ag.svg'; -import ai from '!file-loader!./ai.svg'; -import al from '!file-loader!./al.svg'; -import am from '!file-loader!./am.svg'; -import ao from '!file-loader!./ao.svg'; -import aq from '!file-loader!./aq.svg'; -import ar from '!file-loader!./ar.svg'; -import as from '!file-loader!./as.svg'; -import at from '!file-loader!./at.svg'; -import au from '!file-loader!./au.svg'; -import aw from '!file-loader!./aw.svg'; -import ax from '!file-loader!./ax.svg'; -import az from '!file-loader!./az.svg'; -import ba from '!file-loader!./ba.svg'; -import bb from '!file-loader!./bb.svg'; -import bd from '!file-loader!./bd.svg'; -import be from '!file-loader!./be.svg'; -import bf from '!file-loader!./bf.svg'; -import bg from '!file-loader!./bg.svg'; -import bh from '!file-loader!./bh.svg'; -import bi from '!file-loader!./bi.svg'; -import bj from '!file-loader!./bj.svg'; -import bl from '!file-loader!./bl.svg'; -import bm from '!file-loader!./bm.svg'; -import bn from '!file-loader!./bn.svg'; -import bo from '!file-loader!./bo.svg'; -import bq from '!file-loader!./bq.svg'; -import br from '!file-loader!./br.svg'; -import bs from '!file-loader!./bs.svg'; -import bt from '!file-loader!./bt.svg'; -import bv from '!file-loader!./bv.svg'; -import bw from '!file-loader!./bw.svg'; -import by from '!file-loader!./by.svg'; -import bz from '!file-loader!./bz.svg'; -import ca from '!file-loader!./ca.svg'; -import cc from '!file-loader!./cc.svg'; -import cd from '!file-loader!./cd.svg'; -import cf from '!file-loader!./cf.svg'; -import cg from '!file-loader!./cg.svg'; -import ch from '!file-loader!./ch.svg'; -import ci from '!file-loader!./ci.svg'; -import ck from '!file-loader!./ck.svg'; -import cl from '!file-loader!./cl.svg'; -import cm from '!file-loader!./cm.svg'; -import cn from '!file-loader!./cn.svg'; -import co from '!file-loader!./co.svg'; -import cr from '!file-loader!./cr.svg'; -import cu from '!file-loader!./cu.svg'; -import cv from '!file-loader!./cv.svg'; -import cw from '!file-loader!./cw.svg'; -import cx from '!file-loader!./cx.svg'; -import cy from '!file-loader!./cy.svg'; -import cz from '!file-loader!./cz.svg'; -import de from '!file-loader!./de.svg'; -import dj from '!file-loader!./dj.svg'; -import dk from '!file-loader!./dk.svg'; -import dm from '!file-loader!./dm.svg'; -import _do from '!file-loader!./do.svg'; -import dz from '!file-loader!./dz.svg'; -import ec from '!file-loader!./ec.svg'; -import ee from '!file-loader!./ee.svg'; -import eg from '!file-loader!./eg.svg'; -import eh from '!file-loader!./eh.svg'; -import er from '!file-loader!./er.svg'; -import es from '!file-loader!./es.svg'; -import et from '!file-loader!./et.svg'; -import eu from '!file-loader!./eu.svg'; -import fi from '!file-loader!./fi.svg'; -import fj from '!file-loader!./fj.svg'; -import fk from '!file-loader!./fk.svg'; -import fm from '!file-loader!./fm.svg'; -import fo from '!file-loader!./fo.svg'; -import fr from '!file-loader!./fr.svg'; -import ga from '!file-loader!./ga.svg'; -import gb from '!file-loader!./gb.svg'; -import gd from '!file-loader!./gd.svg'; -import ge from '!file-loader!./ge.svg'; -import gf from '!file-loader!./gf.svg'; -import gg from '!file-loader!./gg.svg'; -import gh from '!file-loader!./gh.svg'; -import gi from '!file-loader!./gi.svg'; -import gl from '!file-loader!./gl.svg'; -import gm from '!file-loader!./gm.svg'; -import gn from '!file-loader!./gn.svg'; -import gp from '!file-loader!./gp.svg'; -import gq from '!file-loader!./gq.svg'; -import gr from '!file-loader!./gr.svg'; -import gs from '!file-loader!./gs.svg'; -import gt from '!file-loader!./gt.svg'; -import gu from '!file-loader!./gu.svg'; -import gw from '!file-loader!./gw.svg'; -import gy from '!file-loader!./gy.svg'; -import hk from '!file-loader!./hk.svg'; -import hm from '!file-loader!./hm.svg'; -import hn from '!file-loader!./hn.svg'; -import hr from '!file-loader!./hr.svg'; -import ht from '!file-loader!./ht.svg'; -import hu from '!file-loader!./hu.svg'; -import id from '!file-loader!./id.svg'; -import ie from '!file-loader!./ie.svg'; -import il from '!file-loader!./il.svg'; -import im from '!file-loader!./im.svg'; -import _in from '!file-loader!./in.svg'; -import io from '!file-loader!./io.svg'; -import iq from '!file-loader!./iq.svg'; -import ir from '!file-loader!./ir.svg'; -import is from '!file-loader!./is.svg'; -import it from '!file-loader!./it.svg'; -import je from '!file-loader!./je.svg'; -import jm from '!file-loader!./jm.svg'; -import jo from '!file-loader!./jo.svg'; -import jp from '!file-loader!./jp.svg'; -import ke from '!file-loader!./ke.svg'; -import kg from '!file-loader!./kg.svg'; -import kh from '!file-loader!./kh.svg'; -import ki from '!file-loader!./ki.svg'; -import km from '!file-loader!./km.svg'; -import kn from '!file-loader!./kn.svg'; -import kp from '!file-loader!./kp.svg'; -import kr from '!file-loader!./kr.svg'; -import kw from '!file-loader!./kw.svg'; -import ky from '!file-loader!./ky.svg'; -import kz from '!file-loader!./kz.svg'; -import la from '!file-loader!./la.svg'; -import lb from '!file-loader!./lb.svg'; -import lc from '!file-loader!./lc.svg'; -import li from '!file-loader!./li.svg'; -import lk from '!file-loader!./lk.svg'; -import lr from '!file-loader!./lr.svg'; -import ls from '!file-loader!./ls.svg'; -import lt from '!file-loader!./lt.svg'; -import lu from '!file-loader!./lu.svg'; -import lv from '!file-loader!./lv.svg'; -import ly from '!file-loader!./ly.svg'; -import ma from '!file-loader!./ma.svg'; -import mc from '!file-loader!./mc.svg'; -import md from '!file-loader!./md.svg'; -import me from '!file-loader!./me.svg'; -import mf from '!file-loader!./mf.svg'; -import mg from '!file-loader!./mg.svg'; -import mh from '!file-loader!./mh.svg'; -import mk from '!file-loader!./mk.svg'; -import ml from '!file-loader!./ml.svg'; -import mm from '!file-loader!./mm.svg'; -import mn from '!file-loader!./mn.svg'; -import mo from '!file-loader!./mo.svg'; -import mp from '!file-loader!./mp.svg'; -import mq from '!file-loader!./mq.svg'; -import mr from '!file-loader!./mr.svg'; -import ms from '!file-loader!./ms.svg'; -import mt from '!file-loader!./mt.svg'; -import mu from '!file-loader!./mu.svg'; -import mv from '!file-loader!./mv.svg'; -import mw from '!file-loader!./mw.svg'; -import mx from '!file-loader!./mx.svg'; -import my from '!file-loader!./my.svg'; -import mz from '!file-loader!./mz.svg'; -import na from '!file-loader!./na.svg'; -import nc from '!file-loader!./nc.svg'; -import ne from '!file-loader!./ne.svg'; -import nf from '!file-loader!./nf.svg'; -import ng from '!file-loader!./ng.svg'; -import ni from '!file-loader!./ni.svg'; -import nl from '!file-loader!./nl.svg'; -import no from '!file-loader!./no.svg'; -import np from '!file-loader!./np.svg'; -import nr from '!file-loader!./nr.svg'; -import nu from '!file-loader!./nu.svg'; -import nz from '!file-loader!./nz.svg'; -import om from '!file-loader!./om.svg'; -import pa from '!file-loader!./pa.svg'; -import pe from '!file-loader!./pe.svg'; -import pf from '!file-loader!./pf.svg'; -import pg from '!file-loader!./pg.svg'; -import ph from '!file-loader!./ph.svg'; -import pk from '!file-loader!./pk.svg'; -import pl from '!file-loader!./pl.svg'; -import pm from '!file-loader!./pm.svg'; -import pn from '!file-loader!./pn.svg'; -import pr from '!file-loader!./pr.svg'; -import ps from '!file-loader!./ps.svg'; -import pt from '!file-loader!./pt.svg'; -import pw from '!file-loader!./pw.svg'; -import py from '!file-loader!./py.svg'; -import qa from '!file-loader!./qa.svg'; -import re from '!file-loader!./re.svg'; -import ro from '!file-loader!./ro.svg'; -import rs from '!file-loader!./rs.svg'; -import ru from '!file-loader!./ru.svg'; -import rw from '!file-loader!./rw.svg'; -import sa from '!file-loader!./sa.svg'; -import sb from '!file-loader!./sb.svg'; -import sc from '!file-loader!./sc.svg'; -import sd from '!file-loader!./sd.svg'; -import se from '!file-loader!./se.svg'; -import sg from '!file-loader!./sg.svg'; -import sh from '!file-loader!./sh.svg'; -import si from '!file-loader!./si.svg'; -import sj from '!file-loader!./sj.svg'; -import sk from '!file-loader!./sk.svg'; -import sl from '!file-loader!./sl.svg'; -import sm from '!file-loader!./sm.svg'; -import sn from '!file-loader!./sn.svg'; -import so from '!file-loader!./so.svg'; -import sr from '!file-loader!./sr.svg'; -import ss from '!file-loader!./ss.svg'; -import st from '!file-loader!./st.svg'; -import sv from '!file-loader!./sv.svg'; -import sx from '!file-loader!./sx.svg'; -import sy from '!file-loader!./sy.svg'; -import sz from '!file-loader!./sz.svg'; -import tc from '!file-loader!./tc.svg'; -import td from '!file-loader!./td.svg'; -import tf from '!file-loader!./tf.svg'; -import tg from '!file-loader!./tg.svg'; -import th from '!file-loader!./th.svg'; -import tj from '!file-loader!./tj.svg'; -import tk from '!file-loader!./tk.svg'; -import tl from '!file-loader!./tl.svg'; -import tm from '!file-loader!./tm.svg'; -import tn from '!file-loader!./tn.svg'; -import to from '!file-loader!./to.svg'; -import tr from '!file-loader!./tr.svg'; -import tt from '!file-loader!./tt.svg'; -import tv from '!file-loader!./tv.svg'; -import tw from '!file-loader!./tw.svg'; -import tz from '!file-loader!./tz.svg'; -import ua from '!file-loader!./ua.svg'; -import ug from '!file-loader!./ug.svg'; -import um from '!file-loader!./um.svg'; -import us from '!file-loader!./us.svg'; -import uy from '!file-loader!./uy.svg'; -import uz from '!file-loader!./uz.svg'; -import va from '!file-loader!./va.svg'; -import vc from '!file-loader!./vc.svg'; -import ve from '!file-loader!./ve.svg'; -import vg from '!file-loader!./vg.svg'; -import vi from '!file-loader!./vi.svg'; -import vn from '!file-loader!./vn.svg'; -import vu from '!file-loader!./vu.svg'; -import wf from '!file-loader!./wf.svg'; -import ws from '!file-loader!./ws.svg'; -import xk from '!file-loader!./xk.svg'; -import ye from '!file-loader!./ye.svg'; -import yt from '!file-loader!./yt.svg'; -import za from '!file-loader!./za.svg'; -import zm from '!file-loader!./zm.svg'; -import zw from '!file-loader!./zw.svg'; +import ad from './ad.svg'; +import ae from './ae.svg'; +import af from './af.svg'; +import ag from './ag.svg'; +import ai from './ai.svg'; +import al from './al.svg'; +import am from './am.svg'; +import ao from './ao.svg'; +import aq from './aq.svg'; +import ar from './ar.svg'; +import as from './as.svg'; +import at from './at.svg'; +import au from './au.svg'; +import aw from './aw.svg'; +import ax from './ax.svg'; +import az from './az.svg'; +import ba from './ba.svg'; +import bb from './bb.svg'; +import bd from './bd.svg'; +import be from './be.svg'; +import bf from './bf.svg'; +import bg from './bg.svg'; +import bh from './bh.svg'; +import bi from './bi.svg'; +import bj from './bj.svg'; +import bl from './bl.svg'; +import bm from './bm.svg'; +import bn from './bn.svg'; +import bo from './bo.svg'; +import bq from './bq.svg'; +import br from './br.svg'; +import bs from './bs.svg'; +import bt from './bt.svg'; +import bv from './bv.svg'; +import bw from './bw.svg'; +import by from './by.svg'; +import bz from './bz.svg'; +import ca from './ca.svg'; +import cc from './cc.svg'; +import cd from './cd.svg'; +import cf from './cf.svg'; +import cg from './cg.svg'; +import ch from './ch.svg'; +import ci from './ci.svg'; +import ck from './ck.svg'; +import cl from './cl.svg'; +import cm from './cm.svg'; +import cn from './cn.svg'; +import co from './co.svg'; +import cr from './cr.svg'; +import cu from './cu.svg'; +import cv from './cv.svg'; +import cw from './cw.svg'; +import cx from './cx.svg'; +import cy from './cy.svg'; +import cz from './cz.svg'; +import de from './de.svg'; +import dj from './dj.svg'; +import dk from './dk.svg'; +import dm from './dm.svg'; +import _do from './do.svg'; +import dz from './dz.svg'; +import ec from './ec.svg'; +import ee from './ee.svg'; +import eg from './eg.svg'; +import eh from './eh.svg'; +import er from './er.svg'; +import es from './es.svg'; +import et from './et.svg'; +import eu from './eu.svg'; +import fi from './fi.svg'; +import fj from './fj.svg'; +import fk from './fk.svg'; +import fm from './fm.svg'; +import fo from './fo.svg'; +import fr from './fr.svg'; +import ga from './ga.svg'; +import gb from './gb.svg'; +import gd from './gd.svg'; +import ge from './ge.svg'; +import gf from './gf.svg'; +import gg from './gg.svg'; +import gh from './gh.svg'; +import gi from './gi.svg'; +import gl from './gl.svg'; +import gm from './gm.svg'; +import gn from './gn.svg'; +import gp from './gp.svg'; +import gq from './gq.svg'; +import gr from './gr.svg'; +import gs from './gs.svg'; +import gt from './gt.svg'; +import gu from './gu.svg'; +import gw from './gw.svg'; +import gy from './gy.svg'; +import hk from './hk.svg'; +import hm from './hm.svg'; +import hn from './hn.svg'; +import hr from './hr.svg'; +import ht from './ht.svg'; +import hu from './hu.svg'; +import id from './id.svg'; +import ie from './ie.svg'; +import il from './il.svg'; +import im from './im.svg'; +import _in from './in.svg'; +import io from './io.svg'; +import iq from './iq.svg'; +import ir from './ir.svg'; +import is from './is.svg'; +import it from './it.svg'; +import je from './je.svg'; +import jm from './jm.svg'; +import jo from './jo.svg'; +import jp from './jp.svg'; +import ke from './ke.svg'; +import kg from './kg.svg'; +import kh from './kh.svg'; +import ki from './ki.svg'; +import km from './km.svg'; +import kn from './kn.svg'; +import kp from './kp.svg'; +import kr from './kr.svg'; +import kw from './kw.svg'; +import ky from './ky.svg'; +import kz from './kz.svg'; +import la from './la.svg'; +import lb from './lb.svg'; +import lc from './lc.svg'; +import li from './li.svg'; +import lk from './lk.svg'; +import lr from './lr.svg'; +import ls from './ls.svg'; +import lt from './lt.svg'; +import lu from './lu.svg'; +import lv from './lv.svg'; +import ly from './ly.svg'; +import ma from './ma.svg'; +import mc from './mc.svg'; +import md from './md.svg'; +import me from './me.svg'; +import mf from './mf.svg'; +import mg from './mg.svg'; +import mh from './mh.svg'; +import mk from './mk.svg'; +import ml from './ml.svg'; +import mm from './mm.svg'; +import mn from './mn.svg'; +import mo from './mo.svg'; +import mp from './mp.svg'; +import mq from './mq.svg'; +import mr from './mr.svg'; +import ms from './ms.svg'; +import mt from './mt.svg'; +import mu from './mu.svg'; +import mv from './mv.svg'; +import mw from './mw.svg'; +import mx from './mx.svg'; +import my from './my.svg'; +import mz from './mz.svg'; +import na from './na.svg'; +import nc from './nc.svg'; +import ne from './ne.svg'; +import nf from './nf.svg'; +import ng from './ng.svg'; +import ni from './ni.svg'; +import nl from './nl.svg'; +import no from './no.svg'; +import np from './np.svg'; +import nr from './nr.svg'; +import nu from './nu.svg'; +import nz from './nz.svg'; +import om from './om.svg'; +import pa from './pa.svg'; +import pe from './pe.svg'; +import pf from './pf.svg'; +import pg from './pg.svg'; +import ph from './ph.svg'; +import pk from './pk.svg'; +import pl from './pl.svg'; +import pm from './pm.svg'; +import pn from './pn.svg'; +import pr from './pr.svg'; +import ps from './ps.svg'; +import pt from './pt.svg'; +import pw from './pw.svg'; +import py from './py.svg'; +import qa from './qa.svg'; +import re from './re.svg'; +import ro from './ro.svg'; +import rs from './rs.svg'; +import ru from './ru.svg'; +import rw from './rw.svg'; +import sa from './sa.svg'; +import sb from './sb.svg'; +import sc from './sc.svg'; +import sd from './sd.svg'; +import se from './se.svg'; +import sg from './sg.svg'; +import sh from './sh.svg'; +import si from './si.svg'; +import sj from './sj.svg'; +import sk from './sk.svg'; +import sl from './sl.svg'; +import sm from './sm.svg'; +import sn from './sn.svg'; +import so from './so.svg'; +import sr from './sr.svg'; +import ss from './ss.svg'; +import st from './st.svg'; +import sv from './sv.svg'; +import sx from './sx.svg'; +import sy from './sy.svg'; +import sz from './sz.svg'; +import tc from './tc.svg'; +import td from './td.svg'; +import tf from './tf.svg'; +import tg from './tg.svg'; +import th from './th.svg'; +import tj from './tj.svg'; +import tk from './tk.svg'; +import tl from './tl.svg'; +import tm from './tm.svg'; +import tn from './tn.svg'; +import to from './to.svg'; +import tr from './tr.svg'; +import tt from './tt.svg'; +import tv from './tv.svg'; +import tw from './tw.svg'; +import tz from './tz.svg'; +import ua from './ua.svg'; +import ug from './ug.svg'; +import um from './um.svg'; +import us from './us.svg'; +import uy from './uy.svg'; +import uz from './uz.svg'; +import va from './va.svg'; +import vc from './vc.svg'; +import ve from './ve.svg'; +import vg from './vg.svg'; +import vi from './vi.svg'; +import vn from './vn.svg'; +import vu from './vu.svg'; +import wf from './wf.svg'; +import ws from './ws.svg'; +import xk from './xk.svg'; +import ye from './ye.svg'; +import yt from './yt.svg'; +import za from './za.svg'; +import zm from './zm.svg'; +import zw from './zw.svg'; export const Countries = { ad, diff --git a/webclient/src/react-app-env.d.ts b/webclient/src/react-app-env.d.ts deleted file mode 100644 index 6431bc5fc..000000000 --- a/webclient/src/react-app-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/webclient/src/setupTests.ts b/webclient/src/setupTests.ts index c4e40e2ec..bfb001b2a 100644 --- a/webclient/src/setupTests.ts +++ b/webclient/src/setupTests.ts @@ -1,7 +1,7 @@ import protobuf from 'protobufjs'; // ensure jest-dom is always available during testing to cut down on boilerplate -import '@testing-library/jest-dom'; +import '@testing-library/jest-dom/vitest'; class MockProtobufRoot { load() {} diff --git a/webclient/src/store/game/game.dispatch.spec.ts b/webclient/src/store/game/game.dispatch.spec.ts index e4f9c51b9..4c5ca0701 100644 --- a/webclient/src/store/game/game.dispatch.spec.ts +++ b/webclient/src/store/game/game.dispatch.spec.ts @@ -1,4 +1,4 @@ -jest.mock('store/store', () => ({ store: { dispatch: jest.fn() } })); +vi.mock('store/store', () => ({ store: { dispatch: vi.fn() } })); import { store } from 'store/store'; import { Actions } from './game.actions'; @@ -11,7 +11,7 @@ import { makePlayerProperties, } from './__mocks__/fixtures'; -beforeEach(() => jest.clearAllMocks()); +beforeEach(() => vi.clearAllMocks()); describe('Dispatch', () => { it('clearStore dispatches Actions.clearStore()', () => { diff --git a/webclient/src/store/game/game.reducer.spec.ts b/webclient/src/store/game/game.reducer.spec.ts index 38ed89ad1..82ea025d6 100644 --- a/webclient/src/store/game/game.reducer.spec.ts +++ b/webclient/src/store/game/game.reducer.spec.ts @@ -890,14 +890,14 @@ describe('2J: Turn, phase, and chat', () => { it('GAME_SAY → appends message with mocked Date.now() as timeReceived', () => { const state = makeState(); - jest.spyOn(Date, 'now').mockReturnValue(123456789); + vi.spyOn(Date, 'now').mockReturnValue(123456789); const result = gamesReducer(state, { type: Types.GAME_SAY, gameId: 1, playerId: 2, message: 'gg', }); - jest.restoreAllMocks(); + vi.restoreAllMocks(); expect(result.games[1].messages).toHaveLength(1); expect(result.games[1].messages[0]).toEqual({ playerId: 2, message: 'gg', timeReceived: 123456789 }); diff --git a/webclient/src/store/rooms/rooms.dispatch.spec.ts b/webclient/src/store/rooms/rooms.dispatch.spec.ts index 9da0f9d5c..82ac93f99 100644 --- a/webclient/src/store/rooms/rooms.dispatch.spec.ts +++ b/webclient/src/store/rooms/rooms.dispatch.spec.ts @@ -1,6 +1,6 @@ -jest.mock('store/store', () => ({ store: { dispatch: jest.fn() } })); -jest.mock('redux-form', () => ({ - reset: jest.fn((form) => ({ type: '@@redux-form/RESET', meta: { form } })), +vi.mock('store/store', () => ({ store: { dispatch: vi.fn() } })); +vi.mock('redux-form', () => ({ + reset: vi.fn((form) => ({ type: '@@redux-form/RESET', meta: { form } })), })); import { store } from 'store/store'; @@ -10,7 +10,7 @@ import { Dispatch } from './rooms.dispatch'; import { makeGame, makeMessage, makeRoom, makeUser } from './__mocks__/rooms-fixtures'; import { GameSortField, SortDirection } from 'types'; -beforeEach(() => jest.clearAllMocks()); +beforeEach(() => vi.clearAllMocks()); describe('Dispatch', () => { it('clearStore dispatches Actions.clearStore()', () => { @@ -45,7 +45,7 @@ describe('Dispatch', () => { it('addMessage with message.name truthy → dispatches reset("sayMessage") then Actions.addMessage()', () => { const message = { ...makeMessage(), name: 'Alice' }; Dispatch.addMessage(1, message); - expect(store.dispatch).toHaveBeenNthCalledWith(1, (reset as jest.Mock)('sayMessage')); + expect(store.dispatch).toHaveBeenNthCalledWith(1, (reset as vi.Mock)('sayMessage')); expect(store.dispatch).toHaveBeenNthCalledWith(2, Actions.addMessage(1, message)); }); diff --git a/webclient/src/store/server/server.dispatch.spec.ts b/webclient/src/store/server/server.dispatch.spec.ts index 56f26966f..ca2352ff0 100644 --- a/webclient/src/store/server/server.dispatch.spec.ts +++ b/webclient/src/store/server/server.dispatch.spec.ts @@ -1,6 +1,6 @@ -jest.mock('store/store', () => ({ store: { dispatch: jest.fn() } })); -jest.mock('redux-form', () => ({ - reset: jest.fn((form) => ({ type: '@@redux-form/RESET', meta: { form } })), +vi.mock('store/store', () => ({ store: { dispatch: vi.fn() } })); +vi.mock('redux-form', () => ({ + reset: vi.fn((form) => ({ type: '@@redux-form/RESET', meta: { form } })), })); import { store } from 'store/store'; @@ -18,7 +18,7 @@ import { makeWarnListItem, } from './__mocks__/server-fixtures'; -beforeEach(() => jest.clearAllMocks()); +beforeEach(() => vi.clearAllMocks()); describe('Dispatch', () => { it('initialized dispatches Actions.initialized()', () => { @@ -71,7 +71,7 @@ describe('Dispatch', () => { it('addToBuddyList dispatches reset("addToBuddies") then Actions.addToBuddyList()', () => { const user = makeUser(); Dispatch.addToBuddyList(user); - expect(store.dispatch).toHaveBeenNthCalledWith(1, (reset as jest.Mock)('addToBuddies')); + expect(store.dispatch).toHaveBeenNthCalledWith(1, (reset as vi.Mock)('addToBuddies')); expect(store.dispatch).toHaveBeenNthCalledWith(2, Actions.addToBuddyList(user)); }); @@ -89,7 +89,7 @@ describe('Dispatch', () => { it('addToIgnoreList dispatches reset("addToIgnore") then Actions.addToIgnoreList()', () => { const user = makeUser(); Dispatch.addToIgnoreList(user); - expect(store.dispatch).toHaveBeenNthCalledWith(1, (reset as jest.Mock)('addToIgnore')); + expect(store.dispatch).toHaveBeenNthCalledWith(1, (reset as vi.Mock)('addToIgnore')); expect(store.dispatch).toHaveBeenNthCalledWith(2, Actions.addToIgnoreList(user)); }); diff --git a/webclient/src/vite-env.d.ts b/webclient/src/vite-env.d.ts new file mode 100644 index 000000000..97ce4524c --- /dev/null +++ b/webclient/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/webclient/src/websocket/WebClient.spec.ts b/webclient/src/websocket/WebClient.spec.ts index c9fa0298f..20cac3bcb 100644 --- a/webclient/src/websocket/WebClient.spec.ts +++ b/webclient/src/websocket/WebClient.spec.ts @@ -1,26 +1,28 @@ -jest.mock('./services/WebSocketService', () => ({ - WebSocketService: jest.fn().mockImplementation(() => ({ - message$: { subscribe: jest.fn() }, - connect: jest.fn(), - testConnect: jest.fn(), - disconnect: jest.fn(), +vi.mock('./services/WebSocketService', () => ({ + WebSocketService: vi.fn().mockImplementation(() => ({ + message$: { subscribe: vi.fn() }, + connect: vi.fn(), + testConnect: vi.fn(), + disconnect: vi.fn(), })), })); -jest.mock('./services/ProtobufService', () => ({ - ProtobufService: jest.fn().mockImplementation(() => ({ - handleMessageEvent: jest.fn(), - sendKeepAliveCommand: jest.fn(), - resetCommands: jest.fn(), +vi.mock('./services/ProtobufService', () => ({ + ProtobufService: vi.fn().mockImplementation(() => ({ + handleMessageEvent: vi.fn(), + sendKeepAliveCommand: vi.fn(), + resetCommands: vi.fn(), })), })); -jest.mock('./persistence', () => ({ - RoomPersistence: { clearStore: jest.fn() }, - SessionPersistence: { clearStore: jest.fn() }, +vi.mock('./persistence', () => ({ + RoomPersistence: { clearStore: vi.fn() }, + SessionPersistence: { clearStore: vi.fn() }, })); import { WebClient } from './WebClient'; +import { WebSocketService } from './services/WebSocketService'; +import { ProtobufService } from './services/ProtobufService'; import { RoomPersistence, SessionPersistence } from './persistence'; import { StatusEnum } from 'types'; import { Subject } from 'rxjs'; @@ -30,28 +32,26 @@ describe('WebClient', () => { let messageSubject: Subject; beforeEach(() => { - jest.clearAllMocks(); - const { ProtobufService } = require('./services/ProtobufService'); - ProtobufService.mockImplementation(() => ({ - handleMessageEvent: jest.fn(), - sendKeepAliveCommand: jest.fn(), - resetCommands: jest.fn(), + vi.clearAllMocks(); + (ProtobufService as vi.Mock).mockImplementation(() => ({ + handleMessageEvent: vi.fn(), + sendKeepAliveCommand: vi.fn(), + resetCommands: vi.fn(), })); messageSubject = new Subject(); - const { WebSocketService } = require('./services/WebSocketService'); - WebSocketService.mockImplementation(() => ({ + (WebSocketService as vi.Mock).mockImplementation(() => ({ message$: messageSubject, - connect: jest.fn(), - testConnect: jest.fn(), - disconnect: jest.fn(), + connect: vi.fn(), + testConnect: vi.fn(), + disconnect: vi.fn(), })); // suppress console.log from constructor in non-test-env check - jest.spyOn(console, 'log').mockImplementation(() => {}); + vi.spyOn(console, 'log').mockImplementation(() => {}); client = new WebClient(); }); afterEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); }); describe('constructor', () => { @@ -94,7 +94,7 @@ describe('WebClient', () => { describe('keepAlive', () => { it('delegates to protobuf.sendKeepAliveCommand', () => { - const pingCb = jest.fn(); + const pingCb = vi.fn(); client.keepAlive(pingCb); expect(client.protobuf.sendKeepAliveCommand).toHaveBeenCalledWith(pingCb); }); diff --git a/webclient/src/websocket/WebClient.ts b/webclient/src/websocket/WebClient.ts index 4a471b0e5..378b5fd62 100644 --- a/webclient/src/websocket/WebClient.ts +++ b/webclient/src/websocket/WebClient.ts @@ -47,7 +47,7 @@ export class WebClient { this.protobuf.handleMessageEvent(message); }); - if (process.env.NODE_ENV !== 'test') { + if (import.meta.env.MODE !== 'test') { console.log(this); } } diff --git a/webclient/src/websocket/__mocks__/callbackHelpers.ts b/webclient/src/websocket/__mocks__/callbackHelpers.ts index 964e7df6e..7f7d37ecc 100644 --- a/webclient/src/websocket/__mocks__/callbackHelpers.ts +++ b/webclient/src/websocket/__mocks__/callbackHelpers.ts @@ -1,13 +1,13 @@ /** * Factory for invoking BackendService command callbacks in unit tests. * - * @param mockFn - The jest.Mock for the BackendService send method - * (e.g. BackendService.sendSessionCommand as jest.Mock). + * @param mockFn - The vi.Mock for the BackendService send method + * (e.g. BackendService.sendSessionCommand as vi.Mock). * @param optsArgIndex - Index of the options argument in the mock call. * Defaults to 2 (commandName, params, options). * Use 3 for sendRoomCommand (roomId, commandName, params, options). */ -export function makeCallbackHelpers(mockFn: jest.Mock, optsArgIndex = 2) { +export function makeCallbackHelpers(mockFn: vi.Mock, optsArgIndex = 2) { function getLastSendOpts() { const calls = mockFn.mock.calls; return calls[calls.length - 1]?.[optsArgIndex]; diff --git a/webclient/src/websocket/__mocks__/helpers.ts b/webclient/src/websocket/__mocks__/helpers.ts index a511125a8..dc1da0a73 100644 --- a/webclient/src/websocket/__mocks__/helpers.ts +++ b/webclient/src/websocket/__mocks__/helpers.ts @@ -6,18 +6,18 @@ /** Builds a minimal mock of ProtoController.root */ export function makeMockProtoRoot() { - const encode = { finish: jest.fn().mockReturnValue(new Uint8Array()) }; + const encode = { finish: vi.fn().mockReturnValue(new Uint8Array()) }; return { CommandContainer: { - create: jest.fn(args => ({ ...args })), - encode: jest.fn().mockReturnValue(encode), + create: vi.fn(args => ({ ...args })), + encode: vi.fn().mockReturnValue(encode), }, - SessionCommand: { create: jest.fn(args => ({ ...args })) }, - RoomCommand: { create: jest.fn(args => ({ ...args })) }, - ModeratorCommand: { create: jest.fn(args => ({ ...args })) }, - AdminCommand: { create: jest.fn(args => ({ ...args })) }, + SessionCommand: { create: vi.fn(args => ({ ...args })) }, + RoomCommand: { create: vi.fn(args => ({ ...args })) }, + ModeratorCommand: { create: vi.fn(args => ({ ...args })) }, + AdminCommand: { create: vi.fn(args => ({ ...args })) }, ServerMessage: { - decode: jest.fn(), + decode: vi.fn(), MessageType: { RESPONSE: 'RESPONSE', ROOM_EVENT: 'ROOM_EVENT', @@ -52,8 +52,8 @@ export function makeMockProtoRoot() { /** Builds a mock WebSocket instance */ export function makeMockWebSocketInstance() { return { - send: jest.fn(), - close: jest.fn(), + send: vi.fn(), + close: vi.fn(), readyState: WebSocket.OPEN, binaryType: '' as BinaryType, onopen: null as any, @@ -66,7 +66,7 @@ export function makeMockWebSocketInstance() { /** Installs a mock WebSocket constructor on global. Returns the mock instance. */ export function installMockWebSocket() { const mockInstance = makeMockWebSocketInstance(); - const MockWS = jest.fn(() => mockInstance) as any; + const MockWS = vi.fn(() => mockInstance) as any; MockWS.OPEN = 1; MockWS.CLOSED = 3; (global as any).WebSocket = MockWS; diff --git a/webclient/src/websocket/__mocks__/sessionCommandMocks.ts b/webclient/src/websocket/__mocks__/sessionCommandMocks.ts index 71f84953c..d5bea3a7f 100644 --- a/webclient/src/websocket/__mocks__/sessionCommandMocks.ts +++ b/webclient/src/websocket/__mocks__/sessionCommandMocks.ts @@ -1,10 +1,10 @@ /** * Shared mock shape factories for session command specs. * - * Usage inside jest.mock() factory callbacks (require is used because - * jest.mock() is hoisted above imports): + * Usage inside vi.mock() factory callbacks (require is used because + * vi.mock() is hoisted above imports): * - * jest.mock('../../WebClient', () => { + * vi.mock('../../WebClient', () => { * const { makeWebClientMock } = require('../../__mocks__/sessionCommandMocks'); * return { __esModule: true, default: makeWebClientMock() }; * }); @@ -13,10 +13,10 @@ /** Superset WebClient mock — covers all properties used across both session spec files. */ export function makeWebClientMock() { return { - connect: jest.fn(), - testConnect: jest.fn(), - disconnect: jest.fn(), - updateStatus: jest.fn(), + connect: vi.fn(), + testConnect: vi.fn(), + disconnect: vi.fn(), + updateStatus: vi.fn(), clientConfig: { clientid: 'webatrice', clientver: '1.0', clientfeatures: [] }, options: {}, protocolVersion: 14, @@ -60,72 +60,72 @@ export function makeProtoControllerRootMock() { /** Utils mock with unified return values. */ export function makeUtilsMock() { return { - hashPassword: jest.fn().mockReturnValue('hashed_pw'), - generateSalt: jest.fn().mockReturnValue('randSalt'), - passwordSaltSupported: jest.fn().mockReturnValue(0), + hashPassword: vi.fn().mockReturnValue('hashed_pw'), + generateSalt: vi.fn().mockReturnValue('randSalt'), + passwordSaltSupported: vi.fn().mockReturnValue(0), }; } /** Superset SessionPersistence mock — covers all methods used across both session spec files. */ export function makeSessionPersistenceMock() { return { - loginSuccessful: jest.fn(), - loginFailed: jest.fn(), - updateBuddyList: jest.fn(), - updateIgnoreList: jest.fn(), - updateUser: jest.fn(), - updateUsers: jest.fn(), - accountAwaitingActivation: jest.fn(), - accountActivationSuccess: jest.fn(), - accountActivationFailed: jest.fn(), - updateStatus: jest.fn(), - addToList: jest.fn(), - removeFromList: jest.fn(), - deleteServerDeck: jest.fn(), - deleteServerDeckDir: jest.fn(), - updateServerDecks: jest.fn(), - uploadServerDeck: jest.fn(), - createServerDeckDir: jest.fn(), - getGamesOfUser: jest.fn(), - getUserInfo: jest.fn(), - accountPasswordChange: jest.fn(), - accountEditChanged: jest.fn(), - accountImageChanged: jest.fn(), - replayList: jest.fn(), - replayAdded: jest.fn(), - replayModifyMatch: jest.fn(), - replayDeleteMatch: jest.fn(), - resetPasswordChallenge: jest.fn(), - resetPassword: jest.fn(), - resetPasswordFailed: jest.fn(), - resetPasswordSuccess: jest.fn(), - registrationFailed: jest.fn(), - registrationSuccess: jest.fn(), - registrationUserNameError: jest.fn(), - registrationPasswordError: jest.fn(), - registrationEmailError: jest.fn(), - registrationRequiresEmail: jest.fn(), + loginSuccessful: vi.fn(), + loginFailed: vi.fn(), + updateBuddyList: vi.fn(), + updateIgnoreList: vi.fn(), + updateUser: vi.fn(), + updateUsers: vi.fn(), + accountAwaitingActivation: vi.fn(), + accountActivationSuccess: vi.fn(), + accountActivationFailed: vi.fn(), + updateStatus: vi.fn(), + addToList: vi.fn(), + removeFromList: vi.fn(), + deleteServerDeck: vi.fn(), + deleteServerDeckDir: vi.fn(), + updateServerDecks: vi.fn(), + uploadServerDeck: vi.fn(), + createServerDeckDir: vi.fn(), + getGamesOfUser: vi.fn(), + getUserInfo: vi.fn(), + accountPasswordChange: vi.fn(), + accountEditChanged: vi.fn(), + accountImageChanged: vi.fn(), + replayList: vi.fn(), + replayAdded: vi.fn(), + replayModifyMatch: vi.fn(), + replayDeleteMatch: vi.fn(), + resetPasswordChallenge: vi.fn(), + resetPassword: vi.fn(), + resetPasswordFailed: vi.fn(), + resetPasswordSuccess: vi.fn(), + registrationFailed: vi.fn(), + registrationSuccess: vi.fn(), + registrationUserNameError: vi.fn(), + registrationPasswordError: vi.fn(), + registrationEmailError: vi.fn(), + registrationRequiresEmail: vi.fn(), }; } /** - * Session barrel mock — pure jest.fn() map for all cross-command calls. + * Session barrel mock — pure vi.fn() map for all cross-command calls. * Used as-is by sessionCommands-complex.spec.ts, or spread over jest.requireActual * by sessionCommands-simple.spec.ts to preserve real implementations for * the commands under test. */ export function makeSessionBarrelMock() { return { - login: jest.fn(), - register: jest.fn(), - activate: jest.fn(), - forgotPasswordReset: jest.fn(), - forgotPasswordRequest: jest.fn(), - forgotPasswordChallenge: jest.fn(), - requestPasswordSalt: jest.fn(), - listUsers: jest.fn(), - listRooms: jest.fn(), - updateStatus: jest.fn(), - disconnect: jest.fn(), + login: vi.fn(), + register: vi.fn(), + activate: vi.fn(), + forgotPasswordReset: vi.fn(), + forgotPasswordRequest: vi.fn(), + forgotPasswordChallenge: vi.fn(), + requestPasswordSalt: vi.fn(), + listUsers: vi.fn(), + listRooms: vi.fn(), + updateStatus: vi.fn(), + disconnect: vi.fn(), }; } diff --git a/webclient/src/websocket/commands/admin/adminCommands.spec.ts b/webclient/src/websocket/commands/admin/adminCommands.spec.ts index 9cfc4aa81..be0124736 100644 --- a/webclient/src/websocket/commands/admin/adminCommands.spec.ts +++ b/webclient/src/websocket/commands/admin/adminCommands.spec.ts @@ -1,33 +1,36 @@ -jest.mock('../../services/BackendService', () => ({ +vi.mock('../../services/BackendService', () => ({ BackendService: { - sendAdminCommand: jest.fn(), + sendAdminCommand: vi.fn(), }, })); -jest.mock('../../persistence', () => ({ +vi.mock('../../persistence', () => ({ AdminPersistence: { - adjustMod: jest.fn(), - reloadConfig: jest.fn(), - shutdownServer: jest.fn(), - updateServerMessage: jest.fn(), + adjustMod: vi.fn(), + reloadConfig: vi.fn(), + shutdownServer: vi.fn(), + updateServerMessage: vi.fn(), }, })); import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; import { BackendService } from '../../services/BackendService'; import { AdminPersistence } from '../../persistence'; +import { adjustMod } from './adjustMod'; +import { reloadConfig } from './reloadConfig'; +import { shutdownServer } from './shutdownServer'; +import { updateServerMessage } from './updateServerMessage'; const { getLastSendOpts, invokeOnSuccess } = makeCallbackHelpers( - BackendService.sendAdminCommand as jest.Mock + BackendService.sendAdminCommand as vi.Mock ); -beforeEach(() => jest.clearAllMocks()); +beforeEach(() => vi.clearAllMocks()); // ---------------------------------------------------------------- // adjustMod // ---------------------------------------------------------------- describe('adjustMod', () => { - const { adjustMod } = jest.requireActual('./adjustMod'); it('calls sendAdminCommand with Command_AdjustMod', () => { adjustMod('alice', true, false); @@ -49,7 +52,6 @@ describe('adjustMod', () => { // reloadConfig // ---------------------------------------------------------------- describe('reloadConfig', () => { - const { reloadConfig } = jest.requireActual('./reloadConfig'); it('calls sendAdminCommand with Command_ReloadConfig', () => { reloadConfig(); @@ -67,7 +69,6 @@ describe('reloadConfig', () => { // shutdownServer // ---------------------------------------------------------------- describe('shutdownServer', () => { - const { shutdownServer } = jest.requireActual('./shutdownServer'); it('calls sendAdminCommand with Command_ShutdownServer', () => { shutdownServer('maintenance', 10); @@ -89,7 +90,6 @@ describe('shutdownServer', () => { // updateServerMessage // ---------------------------------------------------------------- describe('updateServerMessage', () => { - const { updateServerMessage } = jest.requireActual('./updateServerMessage'); it('calls sendAdminCommand with Command_UpdateServerMessage', () => { updateServerMessage(); diff --git a/webclient/src/websocket/commands/game/gameCommands.spec.ts b/webclient/src/websocket/commands/game/gameCommands.spec.ts index 8d9597be9..023b5da75 100644 --- a/webclient/src/websocket/commands/game/gameCommands.spec.ts +++ b/webclient/src/websocket/commands/game/gameCommands.spec.ts @@ -33,15 +33,15 @@ import { undoDraw } from './undoDraw'; import { unconcede } from './unconcede'; import { judge } from './judge'; -jest.mock('../../services/BackendService', () => ({ - BackendService: { sendGameCommand: jest.fn() }, +vi.mock('../../services/BackendService', () => ({ + BackendService: { sendGameCommand: vi.fn() }, })); const gameId = 1; const params = {} as any; beforeEach(() => { - (BackendService.sendGameCommand as jest.Mock).mockClear(); + (BackendService.sendGameCommand as vi.Mock).mockClear(); }); describe('Game commands — delegate to BackendService.sendGameCommand', () => { diff --git a/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts b/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts index 4f8b508b1..47dedbfaa 100644 --- a/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts +++ b/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts @@ -1,39 +1,48 @@ -jest.mock('../../services/BackendService', () => ({ +vi.mock('../../services/BackendService', () => ({ BackendService: { - sendModeratorCommand: jest.fn(), + sendModeratorCommand: vi.fn(), }, })); -jest.mock('../../persistence', () => ({ +vi.mock('../../persistence', () => ({ ModeratorPersistence: { - banFromServer: jest.fn(), - forceActivateUser: jest.fn(), - getAdminNotes: jest.fn(), - banHistory: jest.fn(), - warnHistory: jest.fn(), - warnListOptions: jest.fn(), - grantReplayAccess: jest.fn(), - updateAdminNotes: jest.fn(), - viewLogs: jest.fn(), - warnUser: jest.fn(), + banFromServer: vi.fn(), + forceActivateUser: vi.fn(), + getAdminNotes: vi.fn(), + banHistory: vi.fn(), + warnHistory: vi.fn(), + warnListOptions: vi.fn(), + grantReplayAccess: vi.fn(), + updateAdminNotes: vi.fn(), + viewLogs: vi.fn(), + warnUser: vi.fn(), }, })); import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; import { BackendService } from '../../services/BackendService'; import { ModeratorPersistence } from '../../persistence'; +import { banFromServer } from './banFromServer'; +import { forceActivateUser } from './forceActivateUser'; +import { getAdminNotes } from './getAdminNotes'; +import { getBanHistory } from './getBanHistory'; +import { getWarnHistory } from './getWarnHistory'; +import { getWarnList } from './getWarnList'; +import { grantReplayAccess } from './grantReplayAccess'; +import { updateAdminNotes } from './updateAdminNotes'; +import { viewLogHistory } from './viewLogHistory'; +import { warnUser } from './warnUser'; const { getLastSendOpts, invokeOnSuccess } = makeCallbackHelpers( - BackendService.sendModeratorCommand as jest.Mock + BackendService.sendModeratorCommand as vi.Mock ); -beforeEach(() => jest.clearAllMocks()); +beforeEach(() => vi.clearAllMocks()); // ---------------------------------------------------------------- // banFromServer // ---------------------------------------------------------------- describe('banFromServer', () => { - const { banFromServer } = jest.requireActual('./banFromServer'); it('calls sendModeratorCommand with Command_BanFromServer', () => { banFromServer(30, 'alice', '1.2.3.4', 'reason', 'visible', 'cid', 1); @@ -55,7 +64,6 @@ describe('banFromServer', () => { // forceActivateUser // ---------------------------------------------------------------- describe('forceActivateUser', () => { - const { forceActivateUser } = jest.requireActual('./forceActivateUser'); it('calls sendModeratorCommand with Command_ForceActivateUser', () => { forceActivateUser('alice', 'mod1'); @@ -73,7 +81,6 @@ describe('forceActivateUser', () => { // getAdminNotes // ---------------------------------------------------------------- describe('getAdminNotes', () => { - const { getAdminNotes } = jest.requireActual('./getAdminNotes'); it('calls sendModeratorCommand with Command_GetAdminNotes', () => { getAdminNotes('alice'); @@ -96,7 +103,6 @@ describe('getAdminNotes', () => { // getBanHistory // ---------------------------------------------------------------- describe('getBanHistory', () => { - const { getBanHistory } = jest.requireActual('./getBanHistory'); it('calls sendModeratorCommand with Command_GetBanHistory', () => { getBanHistory('alice'); @@ -119,7 +125,6 @@ describe('getBanHistory', () => { // getWarnHistory // ---------------------------------------------------------------- describe('getWarnHistory', () => { - const { getWarnHistory } = jest.requireActual('./getWarnHistory'); it('calls sendModeratorCommand with Command_GetWarnHistory', () => { getWarnHistory('alice'); @@ -142,7 +147,6 @@ describe('getWarnHistory', () => { // getWarnList // ---------------------------------------------------------------- describe('getWarnList', () => { - const { getWarnList } = jest.requireActual('./getWarnList'); it('calls sendModeratorCommand with Command_GetWarnList', () => { getWarnList('mod1', 'alice', 'US'); @@ -165,7 +169,6 @@ describe('getWarnList', () => { // grantReplayAccess // ---------------------------------------------------------------- describe('grantReplayAccess', () => { - const { grantReplayAccess } = jest.requireActual('./grantReplayAccess'); it('calls sendModeratorCommand with Command_GrantReplayAccess', () => { grantReplayAccess(10, 'mod1'); @@ -183,7 +186,6 @@ describe('grantReplayAccess', () => { // updateAdminNotes // ---------------------------------------------------------------- describe('updateAdminNotes', () => { - const { updateAdminNotes } = jest.requireActual('./updateAdminNotes'); it('calls sendModeratorCommand with Command_UpdateAdminNotes', () => { updateAdminNotes('alice', 'new notes'); @@ -201,7 +203,6 @@ describe('updateAdminNotes', () => { // viewLogHistory // ---------------------------------------------------------------- describe('viewLogHistory', () => { - const { viewLogHistory } = jest.requireActual('./viewLogHistory'); it('calls sendModeratorCommand with Command_ViewLogHistory', () => { viewLogHistory({ filters: 'all' } as any); @@ -224,7 +225,6 @@ describe('viewLogHistory', () => { // warnUser // ---------------------------------------------------------------- describe('warnUser', () => { - const { warnUser } = jest.requireActual('./warnUser'); it('calls sendModeratorCommand with Command_WarnUser', () => { warnUser('alice', 'bad behavior', 'cid'); diff --git a/webclient/src/websocket/commands/room/roomCommands.spec.ts b/webclient/src/websocket/commands/room/roomCommands.spec.ts index c2842bd19..d700ced15 100644 --- a/webclient/src/websocket/commands/room/roomCommands.spec.ts +++ b/webclient/src/websocket/commands/room/roomCommands.spec.ts @@ -1,34 +1,37 @@ -jest.mock('../../services/BackendService', () => ({ +vi.mock('../../services/BackendService', () => ({ BackendService: { - sendRoomCommand: jest.fn(), + sendRoomCommand: vi.fn(), }, })); -jest.mock('../../persistence', () => ({ +vi.mock('../../persistence', () => ({ RoomPersistence: { - gameCreated: jest.fn(), - joinedGame: jest.fn(), - leaveRoom: jest.fn(), + gameCreated: vi.fn(), + joinedGame: vi.fn(), + leaveRoom: vi.fn(), }, })); import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; import { BackendService } from '../../services/BackendService'; import { RoomPersistence } from '../../persistence'; +import { createGame } from './createGame'; +import { joinGame } from './joinGame'; +import { leaveRoom } from './leaveRoom'; +import { roomSay } from './roomSay'; const { getLastSendOpts, invokeOnSuccess } = makeCallbackHelpers( - BackendService.sendRoomCommand as jest.Mock, + BackendService.sendRoomCommand as vi.Mock, // sendRoomCommand(roomId, commandName, params, options) — options at index 3 3 ); -beforeEach(() => jest.clearAllMocks()); +beforeEach(() => vi.clearAllMocks()); // ---------------------------------------------------------------- // createGame // ---------------------------------------------------------------- describe('createGame', () => { - const { createGame } = jest.requireActual('./createGame'); it('calls sendRoomCommand with Command_CreateGame', () => { createGame(5, { maxPlayers: 4 } as any); @@ -46,7 +49,6 @@ describe('createGame', () => { // joinGame // ---------------------------------------------------------------- describe('joinGame', () => { - const { joinGame } = jest.requireActual('./joinGame'); it('calls sendRoomCommand with Command_JoinGame', () => { joinGame(7, { gameId: 42, password: '' } as any); @@ -64,7 +66,6 @@ describe('joinGame', () => { // leaveRoom // ---------------------------------------------------------------- describe('leaveRoom', () => { - const { leaveRoom } = jest.requireActual('./leaveRoom'); it('calls sendRoomCommand with Command_LeaveRoom', () => { leaveRoom(3); @@ -82,7 +83,6 @@ describe('leaveRoom', () => { // roomSay // ---------------------------------------------------------------- describe('roomSay', () => { - const { roomSay } = jest.requireActual('./roomSay'); it('calls sendRoomCommand with trimmed message', () => { roomSay(2, ' hello '); diff --git a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts index 97a163081..8e8e2d02a 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts @@ -1,38 +1,38 @@ // Tests for complex session commands that call webClient directly // or have multiple branching callbacks. -jest.mock('../../services/BackendService', () => ({ +vi.mock('../../services/BackendService', () => ({ BackendService: { - sendSessionCommand: jest.fn(), + sendSessionCommand: vi.fn(), }, })); -jest.mock('../../persistence', () => { - const { makeSessionPersistenceMock } = require('../../__mocks__/sessionCommandMocks'); +vi.mock('../../persistence', async () => { + const { makeSessionPersistenceMock } = await import('../../__mocks__/sessionCommandMocks'); return { SessionPersistence: makeSessionPersistenceMock(), RoomPersistence: {}, }; }); -jest.mock('../../WebClient', () => { - const { makeWebClientMock } = require('../../__mocks__/sessionCommandMocks'); +vi.mock('../../WebClient', async () => { + const { makeWebClientMock } = await import('../../__mocks__/sessionCommandMocks'); return { __esModule: true, default: makeWebClientMock() }; }); -jest.mock('../../services/ProtoController', () => { - const { makeProtoControllerRootMock } = require('../../__mocks__/sessionCommandMocks'); +vi.mock('../../services/ProtoController', async () => { + const { makeProtoControllerRootMock } = await import('../../__mocks__/sessionCommandMocks'); return { ProtoController: { root: makeProtoControllerRootMock() } }; }); -jest.mock('../../utils', () => { - const { makeUtilsMock } = require('../../__mocks__/sessionCommandMocks'); +vi.mock('../../utils', async () => { + const { makeUtilsMock } = await import('../../__mocks__/sessionCommandMocks'); return makeUtilsMock(); }); // Intercept all re-exported commands to avoid recursive real invocations -jest.mock('./', () => { - const { makeSessionBarrelMock } = require('../../__mocks__/sessionCommandMocks'); +vi.mock('./', async () => { + const { makeSessionBarrelMock } = await import('../../__mocks__/sessionCommandMocks'); return makeSessionBarrelMock(); }); @@ -43,23 +43,31 @@ import webClient from '../../WebClient'; import * as SessionIndexMocks from './'; import { StatusEnum, WebSocketConnectReason } from 'types'; import { hashPassword, generateSalt, passwordSaltSupported } from '../../utils'; +import { connect } from './connect'; +import { updateStatus } from './updateStatus'; +import { login } from './login'; +import { register } from './register'; +import { activate } from './activate'; +import { forgotPasswordChallenge } from './forgotPasswordChallenge'; +import { forgotPasswordRequest } from './forgotPasswordRequest'; +import { forgotPasswordReset } from './forgotPasswordReset'; +import { requestPasswordSalt } from './requestPasswordSalt'; const { getLastSendOpts, invokeOnSuccess, invokeResponseCode, invokeOnError } = makeCallbackHelpers( - BackendService.sendSessionCommand as jest.Mock + BackendService.sendSessionCommand as vi.Mock ); beforeEach(() => { - jest.clearAllMocks(); - (hashPassword as jest.Mock).mockReturnValue('hashed_pw'); - (generateSalt as jest.Mock).mockReturnValue('randSalt'); - (passwordSaltSupported as jest.Mock).mockReturnValue(0); + vi.clearAllMocks(); + (hashPassword as vi.Mock).mockReturnValue('hashed_pw'); + (generateSalt as vi.Mock).mockReturnValue('randSalt'); + (passwordSaltSupported as vi.Mock).mockReturnValue(0); }); // ---------------------------------------------------------------- // connect.ts // ---------------------------------------------------------------- describe('connect', () => { - const { connect } = jest.requireActual('./connect'); it('calls updateStatus CONNECTING for LOGIN reason', () => { connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.LOGIN); @@ -108,7 +116,6 @@ describe('connect', () => { // updateStatus.ts // ---------------------------------------------------------------- describe('updateStatus', () => { - const { updateStatus } = jest.requireActual('./updateStatus'); it('calls SessionPersistence.updateStatus and webClient.updateStatus', () => { updateStatus(StatusEnum.CONNECTED, 'OK'); @@ -121,7 +128,6 @@ describe('updateStatus', () => { // login.ts // ---------------------------------------------------------------- describe('login', () => { - const { login } = jest.requireActual('./login'); it('sends Command_Login with plain password when no salt', () => { login({ userName: 'alice' } as any, 'pw'); @@ -167,7 +173,7 @@ describe('login', () => { login({ userName: 'alice' } as any, 'secret'); const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } }; invokeOnSuccess(loginResp, { responseCode: 0, '.Response_Login.ext': loginResp }); - const calledWith = (SessionPersistence.loginSuccessful as jest.Mock).mock.calls[0][0]; + const calledWith = (SessionPersistence.loginSuccessful as vi.Mock).mock.calls[0][0]; expect(calledWith).not.toHaveProperty('password'); }); @@ -175,7 +181,7 @@ describe('login', () => { login({ userName: 'alice' } as any, 'pw', 'salt'); const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } }; invokeOnSuccess(loginResp, { responseCode: 0, '.Response_Login.ext': loginResp }); - const calledWith = (SessionPersistence.loginSuccessful as jest.Mock).mock.calls[0][0]; + const calledWith = (SessionPersistence.loginSuccessful as vi.Mock).mock.calls[0][0]; expect(calledWith).toHaveProperty('hashedPassword', 'hashed_pw'); }); @@ -248,7 +254,6 @@ describe('login', () => { // register.ts // ---------------------------------------------------------------- describe('register', () => { - const { register } = jest.requireActual('./register'); it('sends Command_Register with plain password when no salt', () => { register({ userName: 'alice', email: 'a@b.com', country: 'US', realName: 'Al' } as any, 'pw'); @@ -350,7 +355,6 @@ describe('register', () => { // activate.ts // ---------------------------------------------------------------- describe('activate', () => { - const { activate } = jest.requireActual('./activate'); it('sends Command_Activate with userName and token, not password', () => { activate({ userName: 'alice', token: 'tok' } as any, 'pw'); @@ -385,7 +389,6 @@ describe('activate', () => { // forgotPasswordChallenge.ts // ---------------------------------------------------------------- describe('forgotPasswordChallenge', () => { - const { forgotPasswordChallenge } = jest.requireActual('./forgotPasswordChallenge'); it('sends Command_ForgotPasswordChallenge', () => { forgotPasswordChallenge({ userName: 'alice', email: 'a@b.com' } as any); @@ -413,7 +416,6 @@ describe('forgotPasswordChallenge', () => { // forgotPasswordRequest.ts // ---------------------------------------------------------------- describe('forgotPasswordRequest', () => { - const { forgotPasswordRequest } = jest.requireActual('./forgotPasswordRequest'); it('sends Command_ForgotPasswordRequest', () => { forgotPasswordRequest({ userName: 'alice' } as any); @@ -448,7 +450,6 @@ describe('forgotPasswordRequest', () => { // forgotPasswordReset.ts // ---------------------------------------------------------------- describe('forgotPasswordReset', () => { - const { forgotPasswordReset } = jest.requireActual('./forgotPasswordReset'); it('sends Command_ForgotPasswordReset with plain newPassword when no salt', () => { forgotPasswordReset({ userName: 'alice', token: 'tok' } as any, 'newpw'); @@ -487,7 +488,6 @@ describe('forgotPasswordReset', () => { // requestPasswordSalt.ts // ---------------------------------------------------------------- describe('requestPasswordSalt', () => { - const { requestPasswordSalt } = jest.requireActual('./requestPasswordSalt'); it('sends Command_RequestPasswordSalt', () => { requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any, 'pw'); diff --git a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts index 723bdbee9..c6daa0491 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts @@ -1,39 +1,39 @@ // Shared mock setup for session command tests -jest.mock('../../services/BackendService', () => ({ +vi.mock('../../services/BackendService', () => ({ BackendService: { - sendSessionCommand: jest.fn(), + sendSessionCommand: vi.fn(), }, })); -jest.mock('../../persistence', () => { - const { makeSessionPersistenceMock } = require('../../__mocks__/sessionCommandMocks'); +vi.mock('../../persistence', async () => { + const { makeSessionPersistenceMock } = await import('../../__mocks__/sessionCommandMocks'); return { SessionPersistence: makeSessionPersistenceMock(), - RoomPersistence: { joinRoom: jest.fn() }, + RoomPersistence: { joinRoom: vi.fn() }, }; }); -jest.mock('../../WebClient', () => { - const { makeWebClientMock } = require('../../__mocks__/sessionCommandMocks'); +vi.mock('../../WebClient', async () => { + const { makeWebClientMock } = await import('../../__mocks__/sessionCommandMocks'); return { __esModule: true, default: makeWebClientMock() }; }); -jest.mock('../../services/ProtoController', () => { - const { makeProtoControllerRootMock } = require('../../__mocks__/sessionCommandMocks'); +vi.mock('../../services/ProtoController', async () => { + const { makeProtoControllerRootMock } = await import('../../__mocks__/sessionCommandMocks'); return { ProtoController: { root: makeProtoControllerRootMock() } }; }); -jest.mock('../../utils', () => { - const { makeUtilsMock } = require('../../__mocks__/sessionCommandMocks'); +vi.mock('../../utils', async () => { + const { makeUtilsMock } = await import('../../__mocks__/sessionCommandMocks'); return makeUtilsMock(); }); // Mock session commands barrel to allow cross-command calls while keeping real implementations -jest.mock('./', () => { - const actual = jest.requireActual('./'); - const { makeSessionBarrelMock } = require('../../__mocks__/sessionCommandMocks'); - return { ...actual, ...makeSessionBarrelMock() }; +vi.mock('./', async () => { + const actual = await vi.importActual('./'); + const { makeSessionBarrelMock } = await import('../../__mocks__/sessionCommandMocks'); + return { ...(actual as any), ...makeSessionBarrelMock() }; }); import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; @@ -43,23 +43,45 @@ import { RoomPersistence } from '../../persistence'; import webClient from '../../WebClient'; import * as SessionCommands from './'; import { hashPassword, generateSalt, passwordSaltSupported } from '../../utils'; +import { accountEdit } from './accountEdit'; +import { accountImage } from './accountImage'; +import { accountPassword } from './accountPassword'; +import { deckDel } from './deckDel'; +import { deckDelDir } from './deckDelDir'; +import { deckList } from './deckList'; +import { deckNewDir } from './deckNewDir'; +import { deckUpload } from './deckUpload'; +import { disconnect } from './disconnect'; +import { getGamesOfUser } from './getGamesOfUser'; +import { getUserInfo } from './getUserInfo'; +import { joinRoom } from './joinRoom'; +import { listRooms } from './listRooms'; +import { listUsers } from './listUsers'; +import { message } from './message'; +import { ping } from './ping'; +import { replayDeleteMatch } from './replayDeleteMatch'; +import { replayList } from './replayList'; +import { replayModifyMatch } from './replayModifyMatch'; +import { addToList, addToBuddyList, addToIgnoreList } from './addToList'; +import { removeFromList, removeFromBuddyList, removeFromIgnoreList } from './removeFromList'; +import { replayGetCode } from './replayGetCode'; +import { replaySubmitCode } from './replaySubmitCode'; const { invokeOnSuccess, invokeCallback } = makeCallbackHelpers( - BackendService.sendSessionCommand as jest.Mock + BackendService.sendSessionCommand as vi.Mock ); beforeEach(() => { - jest.clearAllMocks(); - (hashPassword as jest.Mock).mockReturnValue('hashed_pw'); - (generateSalt as jest.Mock).mockReturnValue('randSalt'); - (passwordSaltSupported as jest.Mock).mockReturnValue(0); + vi.clearAllMocks(); + (hashPassword as vi.Mock).mockReturnValue('hashed_pw'); + (generateSalt as vi.Mock).mockReturnValue('randSalt'); + (passwordSaltSupported as vi.Mock).mockReturnValue(0); }); // ---------------------------------------------------------------- describe('accountEdit', () => { - const { accountEdit } = jest.requireActual('./accountEdit'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_AccountEdit with correct params', () => { accountEdit('pw', 'Alice', 'a@b.com', 'US'); @@ -78,8 +100,7 @@ describe('accountEdit', () => { }); describe('accountImage', () => { - const { accountImage } = jest.requireActual('./accountImage'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_AccountImage', () => { const img = new Uint8Array([1, 2]); @@ -96,8 +117,7 @@ describe('accountImage', () => { }); describe('accountPassword', () => { - const { accountPassword } = jest.requireActual('./accountPassword'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_AccountPassword', () => { accountPassword('old', 'new', 'hashed'); @@ -116,8 +136,7 @@ describe('accountPassword', () => { }); describe('deckDel', () => { - const { deckDel } = jest.requireActual('./deckDel'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_DeckDel', () => { deckDel(42); @@ -132,8 +151,7 @@ describe('deckDel', () => { }); describe('deckDelDir', () => { - const { deckDelDir } = jest.requireActual('./deckDelDir'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_DeckDelDir', () => { deckDelDir('/path'); @@ -148,8 +166,7 @@ describe('deckDelDir', () => { }); describe('deckList', () => { - const { deckList } = jest.requireActual('./deckList'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_DeckList', () => { deckList(); @@ -165,8 +182,7 @@ describe('deckList', () => { }); describe('deckNewDir', () => { - const { deckNewDir } = jest.requireActual('./deckNewDir'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_DeckNewDir', () => { deckNewDir('/path', 'dir'); @@ -183,8 +199,7 @@ describe('deckNewDir', () => { }); describe('deckUpload', () => { - const { deckUpload } = jest.requireActual('./deckUpload'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_DeckUpload', () => { deckUpload('/path', 1, 'content'); @@ -204,8 +219,7 @@ describe('deckUpload', () => { }); describe('disconnect', () => { - const { disconnect } = jest.requireActual('./disconnect'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('calls webClient.disconnect', () => { disconnect(); @@ -214,8 +228,7 @@ describe('disconnect', () => { }); describe('getGamesOfUser', () => { - const { getGamesOfUser } = jest.requireActual('./getGamesOfUser'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_GetGamesOfUser', () => { getGamesOfUser('alice'); @@ -231,8 +244,7 @@ describe('getGamesOfUser', () => { }); describe('getUserInfo', () => { - const { getUserInfo } = jest.requireActual('./getUserInfo'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_GetUserInfo', () => { getUserInfo('alice'); @@ -248,8 +260,7 @@ describe('getUserInfo', () => { }); describe('joinRoom', () => { - const { joinRoom } = jest.requireActual('./joinRoom'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_JoinRoom', () => { joinRoom(5); @@ -265,8 +276,7 @@ describe('joinRoom', () => { }); describe('listRooms (command)', () => { - const { listRooms } = jest.requireActual('./listRooms'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_ListRooms', () => { listRooms(); @@ -275,8 +285,7 @@ describe('listRooms (command)', () => { }); describe('listUsers', () => { - const { listUsers } = jest.requireActual('./listUsers'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_ListUsers', () => { listUsers(); @@ -292,8 +301,7 @@ describe('listUsers', () => { }); describe('message', () => { - const { message } = jest.requireActual('./message'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_Message', () => { message('bob', 'hi'); @@ -305,17 +313,16 @@ describe('message', () => { }); describe('ping', () => { - const { ping } = jest.requireActual('./ping'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_Ping', () => { - const pingReceived = jest.fn(); + const pingReceived = vi.fn(); ping(pingReceived); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_Ping', {}, expect.any(Object)); }); it('calls pingReceived via onResponse', () => { - const pingReceived = jest.fn(); + const pingReceived = vi.fn(); ping(pingReceived); const raw = {}; invokeCallback('onResponse', raw); @@ -324,8 +331,7 @@ describe('ping', () => { }); describe('replayDeleteMatch', () => { - const { replayDeleteMatch } = jest.requireActual('./replayDeleteMatch'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_ReplayDeleteMatch', () => { replayDeleteMatch(7); @@ -340,8 +346,7 @@ describe('replayDeleteMatch', () => { }); describe('replayList', () => { - const { replayList } = jest.requireActual('./replayList'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_ReplayList', () => { replayList(); @@ -357,8 +362,7 @@ describe('replayList', () => { }); describe('replayModifyMatch', () => { - const { replayModifyMatch } = jest.requireActual('./replayModifyMatch'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_ReplayModifyMatch', () => { replayModifyMatch(7, true); @@ -375,8 +379,7 @@ describe('replayModifyMatch', () => { }); describe('addToList / addToBuddyList / addToIgnoreList', () => { - const { addToList, addToBuddyList, addToIgnoreList } = jest.requireActual('./addToList'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('addToBuddyList sends Command_AddToList with list=buddy', () => { addToBuddyList('alice'); @@ -400,8 +403,7 @@ describe('addToList / addToBuddyList / addToIgnoreList', () => { }); describe('removeFromList / removeFromBuddyList / removeFromIgnoreList', () => { - const { removeFromList, removeFromBuddyList, removeFromIgnoreList } = jest.requireActual('./removeFromList'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('removeFromBuddyList sends Command_RemoveFromList with list=buddy', () => { removeFromBuddyList('alice'); @@ -425,11 +427,10 @@ describe('removeFromList / removeFromBuddyList / removeFromIgnoreList', () => { }); describe('replayGetCode', () => { - const { replayGetCode } = jest.requireActual('./replayGetCode'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_ReplayGetCode with gameId and responseName', () => { - replayGetCode(42, jest.fn()); + replayGetCode(42, vi.fn()); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( 'Command_ReplayGetCode', { gameId: 42 }, @@ -438,7 +439,7 @@ describe('replayGetCode', () => { }); it('calls onCodeReceived with replayCode on success', () => { - const onCodeReceived = jest.fn(); + const onCodeReceived = vi.fn(); replayGetCode(42, onCodeReceived); invokeOnSuccess({ replayCode: 'abc123-xyz' }); expect(onCodeReceived).toHaveBeenCalledWith('abc123-xyz'); @@ -446,8 +447,7 @@ describe('replayGetCode', () => { }); describe('replaySubmitCode', () => { - const { replaySubmitCode } = jest.requireActual('./replaySubmitCode'); - beforeEach(() => jest.clearAllMocks()); + beforeEach(() => vi.clearAllMocks()); it('sends Command_ReplaySubmitCode with replayCode', () => { replaySubmitCode('42-abc123'); @@ -459,14 +459,14 @@ describe('replaySubmitCode', () => { }); it('forwards onSuccess callback', () => { - const onSuccess = jest.fn(); + const onSuccess = vi.fn(); replaySubmitCode('42-abc123', onSuccess); invokeOnSuccess(); expect(onSuccess).toHaveBeenCalled(); }); it('forwards onError callback', () => { - const onError = jest.fn(); + const onError = vi.fn(); replaySubmitCode('42-abc123', undefined, onError); invokeCallback('onError', 404); expect(onError).toHaveBeenCalledWith(404); diff --git a/webclient/src/websocket/events/game/gameEvents.spec.ts b/webclient/src/websocket/events/game/gameEvents.spec.ts index 82c78518d..f1fd3571c 100644 --- a/webclient/src/websocket/events/game/gameEvents.spec.ts +++ b/webclient/src/websocket/events/game/gameEvents.spec.ts @@ -1,34 +1,34 @@ -jest.mock('../../persistence', () => ({ +vi.mock('../../persistence', () => ({ GamePersistence: { - gameStateChanged: jest.fn(), - playerJoined: jest.fn(), - playerLeft: jest.fn(), - playerPropertiesChanged: jest.fn(), - gameClosed: jest.fn(), - gameHostChanged: jest.fn(), - kicked: jest.fn(), - gameSay: jest.fn(), - cardMoved: jest.fn(), - cardFlipped: jest.fn(), - cardDestroyed: jest.fn(), - cardAttached: jest.fn(), - tokenCreated: jest.fn(), - cardAttrChanged: jest.fn(), - cardCounterChanged: jest.fn(), - arrowCreated: jest.fn(), - arrowDeleted: jest.fn(), - counterCreated: jest.fn(), - counterSet: jest.fn(), - counterDeleted: jest.fn(), - cardsDrawn: jest.fn(), - cardsRevealed: jest.fn(), - zoneShuffled: jest.fn(), - dieRolled: jest.fn(), - activePlayerSet: jest.fn(), - activePhaseSet: jest.fn(), - turnReversed: jest.fn(), - zoneDumped: jest.fn(), - zonePropertiesChanged: jest.fn(), + gameStateChanged: vi.fn(), + playerJoined: vi.fn(), + playerLeft: vi.fn(), + playerPropertiesChanged: vi.fn(), + gameClosed: vi.fn(), + gameHostChanged: vi.fn(), + kicked: vi.fn(), + gameSay: vi.fn(), + cardMoved: vi.fn(), + cardFlipped: vi.fn(), + cardDestroyed: vi.fn(), + cardAttached: vi.fn(), + tokenCreated: vi.fn(), + cardAttrChanged: vi.fn(), + cardCounterChanged: vi.fn(), + arrowCreated: vi.fn(), + arrowDeleted: vi.fn(), + counterCreated: vi.fn(), + counterSet: vi.fn(), + counterDeleted: vi.fn(), + cardsDrawn: vi.fn(), + cardsRevealed: vi.fn(), + zoneShuffled: vi.fn(), + dieRolled: vi.fn(), + activePlayerSet: vi.fn(), + activePhaseSet: vi.fn(), + turnReversed: vi.fn(), + zoneDumped: vi.fn(), + zonePropertiesChanged: vi.fn(), }, })); @@ -63,7 +63,7 @@ import { setCardCounter } from './setCardCounter'; import { setCounter } from './setCounter'; import { shuffle } from './shuffle'; -beforeEach(() => jest.clearAllMocks()); +beforeEach(() => vi.clearAllMocks()); const meta = { gameId: 5, playerId: 2, context: null, secondsElapsed: 0, forcedByJudge: 0 }; diff --git a/webclient/src/websocket/events/room/roomEvents.spec.ts b/webclient/src/websocket/events/room/roomEvents.spec.ts index 79b28aada..c554128cc 100644 --- a/webclient/src/websocket/events/room/roomEvents.spec.ts +++ b/webclient/src/websocket/events/room/roomEvents.spec.ts @@ -1,21 +1,25 @@ -jest.mock('../../persistence', () => ({ +vi.mock('../../persistence', () => ({ RoomPersistence: { - userJoined: jest.fn(), - userLeft: jest.fn(), - updateGames: jest.fn(), - removeMessages: jest.fn(), - addMessage: jest.fn(), + userJoined: vi.fn(), + userLeft: vi.fn(), + updateGames: vi.fn(), + removeMessages: vi.fn(), + addMessage: vi.fn(), }, })); import { RoomPersistence } from '../../persistence'; +import { joinRoom } from './joinRoom'; +import { leaveRoom } from './leaveRoom'; +import { listGames } from './listGames'; +import { removeMessages } from './removeMessages'; +import { roomSay } from './roomSay'; const makeRoomEvent = (roomId: number) => ({ roomEvent: { roomId } }); -beforeEach(() => jest.clearAllMocks()); +beforeEach(() => vi.clearAllMocks()); describe('joinRoom room event', () => { - const { joinRoom } = jest.requireActual('./joinRoom'); it('calls RoomPersistence.userJoined with roomId and userInfo', () => { const userInfo = { name: 'alice' } as any; @@ -25,7 +29,6 @@ describe('joinRoom room event', () => { }); describe('leaveRoom room event', () => { - const { leaveRoom } = jest.requireActual('./leaveRoom'); it('calls RoomPersistence.userLeft with roomId and name', () => { leaveRoom({ name: 'alice' }, makeRoomEvent(4)); @@ -34,7 +37,6 @@ describe('leaveRoom room event', () => { }); describe('listGames room event', () => { - const { listGames } = jest.requireActual('./listGames'); it('calls RoomPersistence.updateGames with roomId and gameList', () => { const gameList = [{ gameId: 1 }] as any; @@ -44,7 +46,6 @@ describe('listGames room event', () => { }); describe('removeMessages room event', () => { - const { removeMessages } = jest.requireActual('./removeMessages'); it('calls RoomPersistence.removeMessages with roomId, name, amount', () => { removeMessages({ name: 'bob', amount: 10 }, makeRoomEvent(6)); @@ -53,7 +54,6 @@ describe('removeMessages room event', () => { }); describe('roomSay room event', () => { - const { roomSay } = jest.requireActual('./roomSay'); it('calls RoomPersistence.addMessage with roomId and message', () => { const msg = { text: 'hello' } as any; diff --git a/webclient/src/websocket/events/session/sessionEvents.spec.ts b/webclient/src/websocket/events/session/sessionEvents.spec.ts index 9f8047890..612f4b497 100644 --- a/webclient/src/websocket/events/session/sessionEvents.spec.ts +++ b/webclient/src/websocket/events/session/sessionEvents.spec.ts @@ -1,30 +1,30 @@ // Tests for simple session events that delegate 1:1 to SessionPersistence // or RoomPersistence with minimal logic. -jest.mock('../../persistence', () => ({ +vi.mock('../../persistence', () => ({ SessionPersistence: { - gameJoined: jest.fn(), - notifyUser: jest.fn(), - replayAdded: jest.fn(), - serverMessage: jest.fn(), - serverShutdown: jest.fn(), - updateUsers: jest.fn(), - updateInfo: jest.fn(), - userJoined: jest.fn(), - userLeft: jest.fn(), - userMessage: jest.fn(), - addToBuddyList: jest.fn(), - addToIgnoreList: jest.fn(), - removeFromBuddyList: jest.fn(), - removeFromIgnoreList: jest.fn(), - playerPropertiesChanged: jest.fn(), + gameJoined: vi.fn(), + notifyUser: vi.fn(), + replayAdded: vi.fn(), + serverMessage: vi.fn(), + serverShutdown: vi.fn(), + updateUsers: vi.fn(), + updateInfo: vi.fn(), + userJoined: vi.fn(), + userLeft: vi.fn(), + userMessage: vi.fn(), + addToBuddyList: vi.fn(), + addToIgnoreList: vi.fn(), + removeFromBuddyList: vi.fn(), + removeFromIgnoreList: vi.fn(), + playerPropertiesChanged: vi.fn(), }, RoomPersistence: { - updateRooms: jest.fn(), + updateRooms: vi.fn(), }, })); -jest.mock('../../WebClient', () => ({ +vi.mock('../../WebClient', () => ({ __esModule: true, default: { clientOptions: { autojoinrooms: false }, @@ -33,25 +33,25 @@ jest.mock('../../WebClient', () => ({ }, })); -jest.mock('../../commands/session', () => ({ - joinRoom: jest.fn(), - updateStatus: jest.fn(), - disconnect: jest.fn(), - login: jest.fn(), - register: jest.fn(), - activate: jest.fn(), - requestPasswordSalt: jest.fn(), - forgotPasswordRequest: jest.fn(), - forgotPasswordChallenge: jest.fn(), - forgotPasswordReset: jest.fn(), +vi.mock('../../commands/session', () => ({ + joinRoom: vi.fn(), + updateStatus: vi.fn(), + disconnect: vi.fn(), + login: vi.fn(), + register: vi.fn(), + activate: vi.fn(), + requestPasswordSalt: vi.fn(), + forgotPasswordRequest: vi.fn(), + forgotPasswordChallenge: vi.fn(), + forgotPasswordReset: vi.fn(), })); -jest.mock('../../utils', () => ({ - generateSalt: jest.fn().mockReturnValue('newSalt'), - passwordSaltSupported: jest.fn().mockReturnValue(0), +vi.mock('../../utils', () => ({ + generateSalt: vi.fn().mockReturnValue('newSalt'), + passwordSaltSupported: vi.fn().mockReturnValue(0), })); -jest.mock('../../services/ProtoController', () => ({ +vi.mock('../../services/ProtoController', () => ({ ProtoController: { root: { Event_ConnectionClosed: { @@ -76,18 +76,31 @@ import { SessionPersistence, RoomPersistence } from '../../persistence'; import webClient from '../../WebClient'; import * as SessionCmds from '../../commands/session'; import * as Utils from '../../utils'; +import { gameJoined } from './gameJoined'; +import { notifyUser } from './notifyUser'; +import { replayAdded } from './replayAdded'; +import { serverCompleteList } from './serverCompleteList'; +import { serverMessage } from './serverMessage'; +import { serverShutdown } from './serverShutdown'; +import { userJoined } from './userJoined'; +import { userLeft } from './userLeft'; +import { userMessage } from './userMessage'; +import { addToList } from './addToList'; +import { removeFromList } from './removeFromList'; +import { listRooms } from './listRooms'; +import { connectionClosed } from './connectionClosed'; +import { serverIdentification } from './serverIdentification'; beforeEach(() => { - jest.clearAllMocks(); - (Utils.generateSalt as jest.Mock).mockReturnValue('newSalt'); - (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0); + vi.clearAllMocks(); + (Utils.generateSalt as vi.Mock).mockReturnValue('newSalt'); + (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(0); }); // ---------------------------------------------------------------- // gameJoined // ---------------------------------------------------------------- describe('gameJoined', () => { - const { gameJoined } = jest.requireActual('./gameJoined'); it('calls SessionPersistence.gameJoined', () => { const data = { gameId: 1 } as any; @@ -100,7 +113,6 @@ describe('gameJoined', () => { // notifyUser // ---------------------------------------------------------------- describe('notifyUser', () => { - const { notifyUser } = jest.requireActual('./notifyUser'); it('calls SessionPersistence.notifyUser', () => { const data = { message: 'yo' } as any; @@ -113,7 +125,6 @@ describe('notifyUser', () => { // replayAdded // ---------------------------------------------------------------- describe('replayAdded', () => { - const { replayAdded } = jest.requireActual('./replayAdded'); it('calls SessionPersistence.replayAdded with matchInfo', () => { replayAdded({ matchInfo: { id: 42 } } as any); @@ -125,7 +136,6 @@ describe('replayAdded', () => { // serverCompleteList // ---------------------------------------------------------------- describe('serverCompleteList', () => { - const { serverCompleteList } = jest.requireActual('./serverCompleteList'); it('calls SessionPersistence.updateUsers and RoomPersistence.updateRooms', () => { serverCompleteList({ userList: ['u'], roomList: ['r'] } as any); @@ -138,7 +148,6 @@ describe('serverCompleteList', () => { // serverMessage // ---------------------------------------------------------------- describe('serverMessage', () => { - const { serverMessage } = jest.requireActual('./serverMessage'); it('calls SessionPersistence.serverMessage with message', () => { serverMessage({ message: 'hello server' }); @@ -150,7 +159,6 @@ describe('serverMessage', () => { // serverShutdown // ---------------------------------------------------------------- describe('serverShutdown', () => { - const { serverShutdown } = jest.requireActual('./serverShutdown'); it('calls SessionPersistence.serverShutdown', () => { const payload = { reason: 'maintenance' } as any; @@ -163,7 +171,6 @@ describe('serverShutdown', () => { // userJoined // ---------------------------------------------------------------- describe('userJoined', () => { - const { userJoined } = jest.requireActual('./userJoined'); it('calls SessionPersistence.userJoined with userInfo', () => { userJoined({ userInfo: { name: 'alice' } } as any); @@ -175,7 +182,6 @@ describe('userJoined', () => { // userLeft // ---------------------------------------------------------------- describe('userLeft', () => { - const { userLeft } = jest.requireActual('./userLeft'); it('calls SessionPersistence.userLeft with name', () => { userLeft({ name: 'bob' }); @@ -187,7 +193,6 @@ describe('userLeft', () => { // userMessage // ---------------------------------------------------------------- describe('userMessage', () => { - const { userMessage } = jest.requireActual('./userMessage'); it('calls SessionPersistence.userMessage', () => { const payload = { userName: 'alice', message: 'hi' } as any; @@ -200,8 +205,7 @@ describe('userMessage', () => { // addToList // ---------------------------------------------------------------- describe('addToList', () => { - const { addToList } = jest.requireActual('./addToList'); - const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); afterAll(() => logSpy.mockRestore()); it('buddy list → addToBuddyList', () => { @@ -224,7 +228,6 @@ describe('addToList', () => { // removeFromList // ---------------------------------------------------------------- describe('removeFromList', () => { - const { removeFromList } = jest.requireActual('./removeFromList'); it('buddy list → removeFromBuddyList', () => { removeFromList({ listName: 'buddy', userName: 'alice' } as any); @@ -237,7 +240,7 @@ describe('removeFromList', () => { }); it('unknown list → console.log', () => { - const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); removeFromList({ listName: 'other', userName: 'x' } as any); expect(logSpy).toHaveBeenCalled(); logSpy.mockRestore(); @@ -248,7 +251,6 @@ describe('removeFromList', () => { // listRooms // ---------------------------------------------------------------- describe('listRooms', () => { - const { listRooms } = jest.requireActual('./listRooms'); it('calls RoomPersistence.updateRooms', () => { listRooms({ roomList: [] }); @@ -273,7 +275,6 @@ describe('listRooms', () => { // connectionClosed // ---------------------------------------------------------------- describe('connectionClosed', () => { - const { connectionClosed } = jest.requireActual('./connectionClosed'); it('uses reasonStr when provided', () => { connectionClosed({ reason: 0, reasonStr: 'custom' } as any); @@ -361,7 +362,6 @@ describe('connectionClosed', () => { // serverIdentification // ---------------------------------------------------------------- describe('serverIdentification', () => { - const { serverIdentification } = jest.requireActual('./serverIdentification'); beforeEach(() => { (webClient as any).protocolVersion = 14; @@ -376,7 +376,7 @@ describe('serverIdentification', () => { it('LOGIN reason without salt → calls login with password as separate param', () => { (webClient as any).options = { reason: WebSocketConnectReason.LOGIN, password: 'secret' }; - (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0); + (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(0); serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); expect(SessionCmds.login).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), @@ -386,7 +386,7 @@ describe('serverIdentification', () => { it('LOGIN reason with salt → calls requestPasswordSalt with password as separate param', () => { (webClient as any).options = { reason: WebSocketConnectReason.LOGIN, password: 'secret' }; - (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(1); + (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(1); serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any); expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), @@ -396,7 +396,7 @@ describe('serverIdentification', () => { it('REGISTER reason without salt → calls register with password and null salt', () => { (webClient as any).options = { reason: WebSocketConnectReason.REGISTER, password: 'secret' }; - (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0); + (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(0); serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); expect(SessionCmds.register).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), @@ -407,7 +407,7 @@ describe('serverIdentification', () => { it('REGISTER reason with salt → calls register with password and generated salt', () => { (webClient as any).options = { reason: WebSocketConnectReason.REGISTER, password: 'secret' }; - (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(1); + (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(1); serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any); expect(SessionCmds.register).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), @@ -418,7 +418,7 @@ describe('serverIdentification', () => { it('ACTIVATE_ACCOUNT reason without salt → calls activate with password as separate param', () => { (webClient as any).options = { reason: WebSocketConnectReason.ACTIVATE_ACCOUNT, password: 'secret' }; - (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0); + (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(0); serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); expect(SessionCmds.activate).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), @@ -428,7 +428,7 @@ describe('serverIdentification', () => { it('ACTIVATE_ACCOUNT reason with salt → calls requestPasswordSalt with password as separate param', () => { (webClient as any).options = { reason: WebSocketConnectReason.ACTIVATE_ACCOUNT, password: 'secret' }; - (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(1); + (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(1); serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any); expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), @@ -450,7 +450,7 @@ describe('serverIdentification', () => { it('PASSWORD_RESET reason without salt → calls forgotPasswordReset with newPassword as separate param', () => { (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET, newPassword: 'newpw' }; - (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0); + (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(0); serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); expect(SessionCmds.forgotPasswordReset).toHaveBeenCalledWith( expect.not.objectContaining({ newPassword: expect.anything() }), @@ -460,7 +460,7 @@ describe('serverIdentification', () => { it('PASSWORD_RESET reason with salt → calls requestPasswordSalt with newPassword as separate param', () => { (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET, newPassword: 'newpw' }; - (Utils.passwordSaltSupported as jest.Mock).mockReturnValue(1); + (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(1); serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any); expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith( expect.not.objectContaining({ newPassword: expect.anything() }), diff --git a/webclient/src/websocket/persistence/AdminPersistence.spec.ts b/webclient/src/websocket/persistence/AdminPersistence.spec.ts index 8e34e2baf..6eb54f7e1 100644 --- a/webclient/src/websocket/persistence/AdminPersistence.spec.ts +++ b/webclient/src/websocket/persistence/AdminPersistence.spec.ts @@ -1,9 +1,9 @@ -jest.mock('store', () => ({ +vi.mock('store', () => ({ ServerDispatch: { - adjustMod: jest.fn(), - reloadConfig: jest.fn(), - shutdownServer: jest.fn(), - updateServerMessage: jest.fn(), + adjustMod: vi.fn(), + reloadConfig: vi.fn(), + shutdownServer: vi.fn(), + updateServerMessage: vi.fn(), }, })); @@ -11,7 +11,7 @@ import { AdminPersistence } from './AdminPersistence'; import { ServerDispatch } from 'store'; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('AdminPersistence', () => { diff --git a/webclient/src/websocket/persistence/GamePersistence.spec.ts b/webclient/src/websocket/persistence/GamePersistence.spec.ts index 43e7e86b0..d6983e5eb 100644 --- a/webclient/src/websocket/persistence/GamePersistence.spec.ts +++ b/webclient/src/websocket/persistence/GamePersistence.spec.ts @@ -1,42 +1,42 @@ import { GamePersistence } from './GamePersistence'; -jest.mock('store', () => ({ +vi.mock('store', () => ({ GameDispatch: { - gameStateChanged: jest.fn(), - playerJoined: jest.fn(), - playerLeft: jest.fn(), - playerPropertiesChanged: jest.fn(), - gameClosed: jest.fn(), - gameHostChanged: jest.fn(), - kicked: jest.fn(), - gameSay: jest.fn(), - cardMoved: jest.fn(), - cardFlipped: jest.fn(), - cardDestroyed: jest.fn(), - cardAttached: jest.fn(), - tokenCreated: jest.fn(), - cardAttrChanged: jest.fn(), - cardCounterChanged: jest.fn(), - arrowCreated: jest.fn(), - arrowDeleted: jest.fn(), - counterCreated: jest.fn(), - counterSet: jest.fn(), - counterDeleted: jest.fn(), - cardsDrawn: jest.fn(), - cardsRevealed: jest.fn(), - zoneShuffled: jest.fn(), - dieRolled: jest.fn(), - activePlayerSet: jest.fn(), - activePhaseSet: jest.fn(), - turnReversed: jest.fn(), - zoneDumped: jest.fn(), - zonePropertiesChanged: jest.fn(), + gameStateChanged: vi.fn(), + playerJoined: vi.fn(), + playerLeft: vi.fn(), + playerPropertiesChanged: vi.fn(), + gameClosed: vi.fn(), + gameHostChanged: vi.fn(), + kicked: vi.fn(), + gameSay: vi.fn(), + cardMoved: vi.fn(), + cardFlipped: vi.fn(), + cardDestroyed: vi.fn(), + cardAttached: vi.fn(), + tokenCreated: vi.fn(), + cardAttrChanged: vi.fn(), + cardCounterChanged: vi.fn(), + arrowCreated: vi.fn(), + arrowDeleted: vi.fn(), + counterCreated: vi.fn(), + counterSet: vi.fn(), + counterDeleted: vi.fn(), + cardsDrawn: vi.fn(), + cardsRevealed: vi.fn(), + zoneShuffled: vi.fn(), + dieRolled: vi.fn(), + activePlayerSet: vi.fn(), + activePhaseSet: vi.fn(), + turnReversed: vi.fn(), + zoneDumped: vi.fn(), + zonePropertiesChanged: vi.fn(), }, })); import { GameDispatch } from 'store'; -beforeEach(() => jest.clearAllMocks()); +beforeEach(() => vi.clearAllMocks()); describe('GamePersistence', () => { it('gameStateChanged dispatches via GameDispatch', () => { diff --git a/webclient/src/websocket/persistence/ModeratorPersistence.spec.ts b/webclient/src/websocket/persistence/ModeratorPersistence.spec.ts index 317fa422c..6c0a96be7 100644 --- a/webclient/src/websocket/persistence/ModeratorPersistence.spec.ts +++ b/webclient/src/websocket/persistence/ModeratorPersistence.spec.ts @@ -1,22 +1,22 @@ -jest.mock('store', () => ({ +vi.mock('store', () => ({ ServerDispatch: { - banFromServer: jest.fn(), - banHistory: jest.fn(), - viewLogs: jest.fn(), - warnHistory: jest.fn(), - warnListOptions: jest.fn(), - warnUser: jest.fn(), - grantReplayAccess: jest.fn(), - forceActivateUser: jest.fn(), - getAdminNotes: jest.fn(), - updateAdminNotes: jest.fn(), + banFromServer: vi.fn(), + banHistory: vi.fn(), + viewLogs: vi.fn(), + warnHistory: vi.fn(), + warnListOptions: vi.fn(), + warnUser: vi.fn(), + grantReplayAccess: vi.fn(), + forceActivateUser: vi.fn(), + getAdminNotes: vi.fn(), + updateAdminNotes: vi.fn(), }, })); -jest.mock('../utils/NormalizeService', () => ({ +vi.mock('../utils/NormalizeService', () => ({ __esModule: true, default: { - normalizeLogs: jest.fn((logs: any) => ({ normalized: logs })), + normalizeLogs: vi.fn((logs: any) => ({ normalized: logs })), }, })); @@ -25,8 +25,8 @@ import { ServerDispatch } from 'store'; import NormalizeService from '../utils/NormalizeService'; beforeEach(() => { - jest.clearAllMocks(); - (NormalizeService.normalizeLogs as jest.Mock).mockImplementation((logs: any) => ({ normalized: logs })); + vi.clearAllMocks(); + (NormalizeService.normalizeLogs as vi.Mock).mockImplementation((logs: any) => ({ normalized: logs })); }); describe('ModeratorPersistence', () => { diff --git a/webclient/src/websocket/persistence/RoomPersistence.spec.ts b/webclient/src/websocket/persistence/RoomPersistence.spec.ts index 1130ec890..9e9133b58 100644 --- a/webclient/src/websocket/persistence/RoomPersistence.spec.ts +++ b/webclient/src/websocket/persistence/RoomPersistence.spec.ts @@ -1,29 +1,29 @@ -jest.mock('store', () => ({ - store: { getState: jest.fn().mockReturnValue({}) }, +vi.mock('store', () => ({ + store: { getState: vi.fn().mockReturnValue({}) }, RoomsDispatch: { - clearStore: jest.fn(), - joinRoom: jest.fn(), - leaveRoom: jest.fn(), - updateRooms: jest.fn(), - updateGames: jest.fn(), - addMessage: jest.fn(), - userJoined: jest.fn(), - userLeft: jest.fn(), - removeMessages: jest.fn(), - gameCreated: jest.fn(), - joinedGame: jest.fn(), + clearStore: vi.fn(), + joinRoom: vi.fn(), + leaveRoom: vi.fn(), + updateRooms: vi.fn(), + updateGames: vi.fn(), + addMessage: vi.fn(), + userJoined: vi.fn(), + userLeft: vi.fn(), + removeMessages: vi.fn(), + gameCreated: vi.fn(), + joinedGame: vi.fn(), }, RoomsSelectors: { - getRoom: jest.fn(), + getRoom: vi.fn(), }, })); -jest.mock('../utils/NormalizeService', () => ({ +vi.mock('../utils/NormalizeService', () => ({ __esModule: true, default: { - normalizeRoomInfo: jest.fn(), - normalizeGameObject: jest.fn(), - normalizeUserMessage: jest.fn(), + normalizeRoomInfo: vi.fn(), + normalizeGameObject: vi.fn(), + normalizeUserMessage: vi.fn(), }, })); @@ -32,7 +32,7 @@ import { store, RoomsDispatch, RoomsSelectors } from 'store'; import NormalizeService from '../utils/NormalizeService'; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('RoomPersistence', () => { @@ -62,7 +62,7 @@ describe('RoomPersistence', () => { it('normalizes game when gameType is missing and room exists', () => { const game = { gameType: null, gameTypes: [1] } as any; const room = { gametypeMap: { 1: 'Standard' } } as any; - (RoomsSelectors.getRoom as jest.Mock).mockReturnValue(room); + (RoomsSelectors.getRoom as vi.Mock).mockReturnValue(room); RoomPersistence.updateGames(1, [game]); expect(NormalizeService.normalizeGameObject).toHaveBeenCalledWith(game, room.gametypeMap); expect(RoomsDispatch.updateGames).toHaveBeenCalledWith(1, [game]); @@ -76,7 +76,7 @@ describe('RoomPersistence', () => { it('does not normalize when room is not found', () => { const game = { gameType: null } as any; - (RoomsSelectors.getRoom as jest.Mock).mockReturnValue(null); + (RoomsSelectors.getRoom as vi.Mock).mockReturnValue(null); RoomPersistence.updateGames(1, [game]); expect(NormalizeService.normalizeGameObject).not.toHaveBeenCalled(); }); diff --git a/webclient/src/websocket/persistence/SessionPersistence.spec.ts b/webclient/src/websocket/persistence/SessionPersistence.spec.ts index 7a35c58a9..67a33fd9a 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.spec.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.spec.ts @@ -1,74 +1,74 @@ -jest.mock('store', () => ({ +vi.mock('store', () => ({ ServerDispatch: { - initialized: jest.fn(), - clearStore: jest.fn(), - loginSuccessful: jest.fn(), - loginFailed: jest.fn(), - connectionClosed: jest.fn(), - connectionFailed: jest.fn(), - testConnectionSuccessful: jest.fn(), - testConnectionFailed: jest.fn(), - updateBuddyList: jest.fn(), - addToBuddyList: jest.fn(), - removeFromBuddyList: jest.fn(), - updateIgnoreList: jest.fn(), - addToIgnoreList: jest.fn(), - removeFromIgnoreList: jest.fn(), - updateInfo: jest.fn(), - updateStatus: jest.fn(), - updateUser: jest.fn(), - updateUsers: jest.fn(), - userJoined: jest.fn(), - userLeft: jest.fn(), - serverMessage: jest.fn(), - accountAwaitingActivation: jest.fn(), - accountActivationSuccess: jest.fn(), - accountActivationFailed: jest.fn(), - registrationRequiresEmail: jest.fn(), - registrationSuccess: jest.fn(), - registrationFailed: jest.fn(), - registrationEmailError: jest.fn(), - registrationPasswordError: jest.fn(), - registrationUserNameError: jest.fn(), - resetPasswordChallenge: jest.fn(), - resetPassword: jest.fn(), - resetPasswordSuccess: jest.fn(), - resetPasswordFailed: jest.fn(), - accountPasswordChange: jest.fn(), - accountEditChanged: jest.fn(), - accountImageChanged: jest.fn(), - getUserInfo: jest.fn(), - notifyUser: jest.fn(), - serverShutdown: jest.fn(), - userMessage: jest.fn(), - addToList: jest.fn(), - removeFromList: jest.fn(), - deckDelete: jest.fn(), - backendDecks: jest.fn(), - deckUpload: jest.fn(), - deckNewDir: jest.fn(), - deckDelDir: jest.fn(), - replayList: jest.fn(), - replayAdded: jest.fn(), - replayModifyMatch: jest.fn(), - replayDeleteMatch: jest.fn(), - gamesOfUser: jest.fn(), + initialized: vi.fn(), + clearStore: vi.fn(), + loginSuccessful: vi.fn(), + loginFailed: vi.fn(), + connectionClosed: vi.fn(), + connectionFailed: vi.fn(), + testConnectionSuccessful: vi.fn(), + testConnectionFailed: vi.fn(), + updateBuddyList: vi.fn(), + addToBuddyList: vi.fn(), + removeFromBuddyList: vi.fn(), + updateIgnoreList: vi.fn(), + addToIgnoreList: vi.fn(), + removeFromIgnoreList: vi.fn(), + updateInfo: vi.fn(), + updateStatus: vi.fn(), + updateUser: vi.fn(), + updateUsers: vi.fn(), + userJoined: vi.fn(), + userLeft: vi.fn(), + serverMessage: vi.fn(), + accountAwaitingActivation: vi.fn(), + accountActivationSuccess: vi.fn(), + accountActivationFailed: vi.fn(), + registrationRequiresEmail: vi.fn(), + registrationSuccess: vi.fn(), + registrationFailed: vi.fn(), + registrationEmailError: vi.fn(), + registrationPasswordError: vi.fn(), + registrationUserNameError: vi.fn(), + resetPasswordChallenge: vi.fn(), + resetPassword: vi.fn(), + resetPasswordSuccess: vi.fn(), + resetPasswordFailed: vi.fn(), + accountPasswordChange: vi.fn(), + accountEditChanged: vi.fn(), + accountImageChanged: vi.fn(), + getUserInfo: vi.fn(), + notifyUser: vi.fn(), + serverShutdown: vi.fn(), + userMessage: vi.fn(), + addToList: vi.fn(), + removeFromList: vi.fn(), + deckDelete: vi.fn(), + backendDecks: vi.fn(), + deckUpload: vi.fn(), + deckNewDir: vi.fn(), + deckDelDir: vi.fn(), + replayList: vi.fn(), + replayAdded: vi.fn(), + replayModifyMatch: vi.fn(), + replayDeleteMatch: vi.fn(), + gamesOfUser: vi.fn(), }, GameDispatch: { - gameJoined: jest.fn(), - playerPropertiesChanged: jest.fn(), + gameJoined: vi.fn(), + playerPropertiesChanged: vi.fn(), }, })); -jest.mock('websocket/utils', () => ({ - sanitizeHtml: jest.fn((msg: string) => `sanitized:${msg}`), +vi.mock('websocket/utils', () => ({ + sanitizeHtml: vi.fn((msg: string) => `sanitized:${msg}`), })); -jest.mock('../utils/NormalizeService', () => ({ +vi.mock('../utils/NormalizeService', () => ({ __esModule: true, default: { - normalizeBannedUserError: jest.fn((r: string, t: number) => `banned:${r}:${t}`), - normalizeGameObject: jest.fn(), + normalizeBannedUserError: vi.fn((r: string, t: number) => `banned:${r}:${t}`), + normalizeGameObject: vi.fn(), }, })); @@ -79,9 +79,9 @@ import NormalizeService from '../utils/NormalizeService'; import { StatusEnum } from 'types'; beforeEach(() => { - jest.clearAllMocks(); - (sanitizeHtml as jest.Mock).mockImplementation((msg: string) => `sanitized:${msg}`); - (NormalizeService.normalizeBannedUserError as jest.Mock).mockImplementation( + vi.clearAllMocks(); + (sanitizeHtml as vi.Mock).mockImplementation((msg: string) => `sanitized:${msg}`); + (NormalizeService.normalizeBannedUserError as vi.Mock).mockImplementation( (r: string, t: number) => `banned:${r}:${t}` ); }); diff --git a/webclient/src/websocket/services/BackendService.spec.ts b/webclient/src/websocket/services/BackendService.spec.ts index 1619888dc..85545f3cf 100644 --- a/webclient/src/websocket/services/BackendService.spec.ts +++ b/webclient/src/websocket/services/BackendService.spec.ts @@ -1,16 +1,16 @@ import { makeMockProtoRoot } from '../__mocks__/helpers'; -jest.mock('./ProtoController', () => ({ +vi.mock('./ProtoController', () => ({ ProtoController: { root: null }, })); -jest.mock('../WebClient', () => { +vi.mock('../WebClient', () => { const mockProtobuf = { - sendGameCommand: jest.fn(), - sendSessionCommand: jest.fn(), - sendRoomCommand: jest.fn(), - sendModeratorCommand: jest.fn(), - sendAdminCommand: jest.fn(), + sendGameCommand: vi.fn(), + sendSessionCommand: vi.fn(), + sendRoomCommand: vi.fn(), + sendModeratorCommand: vi.fn(), + sendAdminCommand: vi.fn(), }; return { __esModule: true, default: { protobuf: mockProtobuf } }; }); @@ -20,18 +20,18 @@ import { ProtoController } from './ProtoController'; import webClient from '../WebClient'; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); ProtoController.root = makeMockProtoRoot(); - ProtoController.root.GameCommand = { create: jest.fn(args => ({ ...args })) }; - ProtoController.root['Command_Game'] = { create: jest.fn(p => ({ ...p })) }; - ProtoController.root['Command_Test'] = { create: jest.fn(p => ({ ...p })) }; - ProtoController.root['Command_Room'] = { create: jest.fn(p => ({ ...p })) }; - ProtoController.root['Command_Mod'] = { create: jest.fn(p => ({ ...p })) }; - ProtoController.root['Command_Admin'] = { create: jest.fn(p => ({ ...p })) }; + ProtoController.root.GameCommand = { create: vi.fn(args => ({ ...args })) }; + ProtoController.root['Command_Game'] = { create: vi.fn(p => ({ ...p })) }; + ProtoController.root['Command_Test'] = { create: vi.fn(p => ({ ...p })) }; + ProtoController.root['Command_Room'] = { create: vi.fn(p => ({ ...p })) }; + ProtoController.root['Command_Mod'] = { create: vi.fn(p => ({ ...p })) }; + ProtoController.root['Command_Admin'] = { create: vi.fn(p => ({ ...p })) }; ProtoController.root['Response_Test'] = {}; }); -function captureCallback(sendFn: jest.Mock) { +function captureCallback(sendFn: vi.Mock) { return sendFn.mock.calls[0][sendFn === (webClient.protobuf as any).sendRoomCommand ? 2 : 1]; } @@ -51,7 +51,7 @@ describe('BackendService', () => { describe('handleResponse via non-session command callbacks', () => { it('sendGameCommand callback invokes handleResponse', () => { - const onSuccess = jest.fn(); + const onSuccess = vi.fn(); BackendService.sendGameCommand(7, 'Command_Game', {}, { onSuccess }); const cb = (webClient.protobuf as any).sendGameCommand.mock.calls[0][2]; cb({ responseCode: 0 }); @@ -59,21 +59,21 @@ describe('BackendService', () => { }); it('sendRoomCommand callback invokes handleResponse', () => { - const onSuccess = jest.fn(); + const onSuccess = vi.fn(); BackendService.sendRoomCommand(5, 'Command_Room', {}, { onSuccess }); captureCallback((webClient.protobuf as any).sendRoomCommand)({ responseCode: 0 }); expect(onSuccess).toHaveBeenCalled(); }); it('sendModeratorCommand callback invokes handleResponse', () => { - const onSuccess = jest.fn(); + const onSuccess = vi.fn(); BackendService.sendModeratorCommand('Command_Mod', {}, { onSuccess }); captureCallback((webClient.protobuf as any).sendModeratorCommand)({ responseCode: 0 }); expect(onSuccess).toHaveBeenCalled(); }); it('sendAdminCommand callback invokes handleResponse', () => { - const onSuccess = jest.fn(); + const onSuccess = vi.fn(); BackendService.sendAdminCommand('Command_Admin', {}, { onSuccess }); captureCallback((webClient.protobuf as any).sendAdminCommand)({ responseCode: 0 }); expect(onSuccess).toHaveBeenCalled(); @@ -88,41 +88,41 @@ describe('BackendService', () => { } it('calls onResponse and returns early when provided', () => { - const onResponse = jest.fn(); - const onSuccess = jest.fn(); + const onResponse = vi.fn(); + const onSuccess = vi.fn(); invokeCallback({ onResponse, onSuccess }, { responseCode: 99 }); expect(onResponse).toHaveBeenCalled(); expect(onSuccess).not.toHaveBeenCalled(); }); it('calls onSuccess with raw when responseCode is RespOk and no responseName', () => { - const onSuccess = jest.fn(); + const onSuccess = vi.fn(); const raw = { responseCode: 0 }; invokeCallback({ onSuccess }, raw); expect(onSuccess).toHaveBeenCalledWith(raw, raw); }); it('calls onSuccess with nested response when responseName is set', () => { - const onSuccess = jest.fn(); + const onSuccess = vi.fn(); const raw = { responseCode: 0, '.Response_Test.ext': { nested: true } }; invokeCallback({ onSuccess, responseName: 'Response_Test' }, raw); expect(onSuccess).toHaveBeenCalledWith({ nested: true }, raw); }); it('calls onResponseCode handler when code matches', () => { - const specificHandler = jest.fn(); + const specificHandler = vi.fn(); invokeCallback({ onResponseCode: { 5: specificHandler } }, { responseCode: 5 }); expect(specificHandler).toHaveBeenCalled(); }); it('calls onError when responseCode is not RespOk and no specific handler', () => { - const onError = jest.fn(); + const onError = vi.fn(); invokeCallback({ onError }, { responseCode: 99 }); expect(onError).toHaveBeenCalledWith(99, { responseCode: 99 }); }); it('logs error to console when no callbacks for non-RespOk response', () => { - const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); invokeCallback({}, { responseCode: 42 }); expect(consoleSpy).toHaveBeenCalled(); consoleSpy.mockRestore(); diff --git a/webclient/src/websocket/services/KeepAliveService.spec.ts b/webclient/src/websocket/services/KeepAliveService.spec.ts index df5169b4c..df24f45da 100644 --- a/webclient/src/websocket/services/KeepAliveService.spec.ts +++ b/webclient/src/websocket/services/KeepAliveService.spec.ts @@ -6,7 +6,7 @@ describe('KeepAliveService', () => { let service: KeepAliveService; beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); service = new KeepAliveService(webClient.socket); }); @@ -27,11 +27,11 @@ describe('KeepAliveService', () => { promise = new Promise(resolve => resolvePing = resolve); ping = (done) => promise.then(done); - checkReadyStateSpy = jest.spyOn(webClient.socket, 'checkReadyState'); + checkReadyStateSpy = vi.spyOn(webClient.socket, 'checkReadyState'); checkReadyStateSpy.mockImplementation(() => true); service.startPingLoop(interval, ping); - jest.advanceTimersByTime(interval); + vi.advanceTimersByTime(interval); }); it('should start ping loop', () => { @@ -39,28 +39,27 @@ describe('KeepAliveService', () => { expect((service as any).lastPingPending).toBeTruthy(); }); - it('should call ping callback when done', (done: jest.DoneCallback) => { + it('should call ping callback when done', () => { resolvePing(); - promise.then(() => { + return promise.then(() => { expect((service as any).lastPingPending).toBeFalsy(); - done(); }); }); it('should fire disconnected$ if lastPingPending is still true', () => { - jest.spyOn(service.disconnected$, 'next').mockImplementation(() => {}); - jest.advanceTimersByTime(interval); + vi.spyOn(service.disconnected$, 'next').mockImplementation(() => {}); + vi.advanceTimersByTime(interval); expect(service.disconnected$.next).toHaveBeenCalled(); }); it('should endPingLoop if socket is not open', () => { - jest.spyOn(service, 'endPingLoop').mockImplementation(() => {}); + vi.spyOn(service, 'endPingLoop').mockImplementation(() => {}); checkReadyStateSpy.mockImplementation(() => false); resolvePing(); - jest.advanceTimersByTime(interval); + vi.advanceTimersByTime(interval); expect(service.endPingLoop).toHaveBeenCalled(); }); diff --git a/webclient/src/websocket/services/ProtoController.spec.ts b/webclient/src/websocket/services/ProtoController.spec.ts index b2460d8e4..b16b3bedb 100644 --- a/webclient/src/websocket/services/ProtoController.spec.ts +++ b/webclient/src/websocket/services/ProtoController.spec.ts @@ -1,17 +1,16 @@ -jest.mock('../persistence', () => ({ - SessionPersistence: { initialized: jest.fn() }, +vi.mock('../persistence', () => ({ + SessionPersistence: { initialized: vi.fn() }, })); -jest.mock('../../proto-files.json', () => ['test.proto'], { virtual: true }); +vi.mock('../../proto-files.json', () => ({ default: ['test.proto'] })); import { ProtoController } from './ProtoController'; import { SessionPersistence } from '../persistence'; import protobuf from 'protobufjs'; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); ProtoController.root = null; - (process.env as any).PUBLIC_URL = ''; }); describe('ProtoController', () => { @@ -22,7 +21,7 @@ describe('ProtoController', () => { }); it('calls initialized when callback succeeds', () => { - const loadSpy = jest.spyOn(protobuf.Root.prototype, 'load').mockImplementation( + const loadSpy = vi.spyOn(protobuf.Root.prototype, 'load').mockImplementation( ((_files: any, _opts: any, cb: any) => cb(null)) as any ); ProtoController.load(); @@ -31,7 +30,7 @@ describe('ProtoController', () => { }); it('throws when callback receives an error', () => { - const loadSpy = jest.spyOn(protobuf.Root.prototype, 'load').mockImplementation( + const loadSpy = vi.spyOn(protobuf.Root.prototype, 'load').mockImplementation( ((_files: any, _opts: any, cb: any) => cb(new Error('load failed'))) as any ); expect(() => ProtoController.load()).toThrow('load failed'); diff --git a/webclient/src/websocket/services/ProtoController.ts b/webclient/src/websocket/services/ProtoController.ts index 130d5e196..4f6b37a19 100644 --- a/webclient/src/websocket/services/ProtoController.ts +++ b/webclient/src/websocket/services/ProtoController.ts @@ -3,7 +3,7 @@ import protobuf from 'protobufjs'; import { SessionPersistence } from '../persistence'; import ProtoFiles from '../../proto-files.json'; -const PB_FILE_DIR = `${process.env.PUBLIC_URL}/pb`; +const PB_FILE_DIR = `${import.meta.env.BASE_URL}pb`; // Leaf module — no imports from the websocket layer other than persistence. // Both BackendService and ProtobufService import this; neither should import diff --git a/webclient/src/websocket/services/ProtobufService.spec.ts b/webclient/src/websocket/services/ProtobufService.spec.ts index 0050f645c..5e933f118 100644 --- a/webclient/src/websocket/services/ProtobufService.spec.ts +++ b/webclient/src/websocket/services/ProtobufService.spec.ts @@ -1,21 +1,24 @@ import { makeMockProtoRoot } from '../__mocks__/helpers'; -jest.mock('./ProtoController', () => ({ - ProtoController: { root: null, load: jest.fn() }, +vi.mock('./ProtoController', () => ({ + ProtoController: { root: null, load: vi.fn() }, })); -jest.mock('../commands/session', () => ({ - SessionCommands: { ping: jest.fn() }, - ping: jest.fn(), +vi.mock('../commands/session', () => ({ + SessionCommands: { ping: vi.fn() }, + ping: vi.fn(), })); -jest.mock('../events', () => ({ - GameEvents: { '.Event_Game.ext': jest.fn() }, - RoomEvents: { '.Event_Room.ext': jest.fn() }, - SessionEvents: { '.Event_Session.ext': jest.fn() }, +vi.mock('../events', () => ({ + GameEvents: { '.Event_Game.ext': vi.fn() }, + RoomEvents: { '.Event_Room.ext': vi.fn() }, + SessionEvents: { '.Event_Session.ext': vi.fn() }, })); -jest.mock('../WebClient'); +vi.mock('../WebClient', () => ({ + __esModule: true, + default: {}, +})); import { ProtobufService } from './ProtobufService'; import { ProtoController } from './ProtoController'; @@ -26,15 +29,15 @@ let mockSocket: any; let mockWebClient: any; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); ProtoController.root = makeMockProtoRoot(); - const encodeResult = { finish: jest.fn().mockReturnValue(new Uint8Array([1, 2])) }; - ProtoController.root.CommandContainer.encode = jest.fn().mockReturnValue(encodeResult); + const encodeResult = { finish: vi.fn().mockReturnValue(new Uint8Array([1, 2])) }; + ProtoController.root.CommandContainer.encode = vi.fn().mockReturnValue(encodeResult); mockSocket = { - checkReadyState: jest.fn().mockReturnValue(true), - send: jest.fn(), + checkReadyState: vi.fn().mockReturnValue(true), + send: vi.fn(), }; mockWebClient = { @@ -52,7 +55,7 @@ describe('ProtobufService', () => { it('resets cmdId and pendingCommands', () => { const service = new ProtobufService(mockWebClient); // add a pending command - service.sendSessionCommand({}, jest.fn()); + service.sendSessionCommand({}, vi.fn()); expect((service as any).cmdId).toBe(1); service.resetCommands(); expect((service as any).cmdId).toBe(0); @@ -63,7 +66,7 @@ describe('ProtobufService', () => { describe('sendCommand', () => { it('increments cmdId and stores callback', () => { const service = new ProtobufService(mockWebClient); - const cb = jest.fn(); + const cb = vi.fn(); service.sendCommand({}, cb); expect((service as any).cmdId).toBe(1); expect((service as any).pendingCommands[1]).toBe(cb); @@ -72,14 +75,14 @@ describe('ProtobufService', () => { it('sends encoded data when socket is OPEN', () => { const service = new ProtobufService(mockWebClient); mockSocket.checkReadyState.mockReturnValue(true); - service.sendCommand({}, jest.fn()); + service.sendCommand({}, vi.fn()); expect(mockSocket.send).toHaveBeenCalled(); }); it('does not send when socket is not OPEN', () => { const service = new ProtobufService(mockWebClient); mockSocket.checkReadyState.mockReturnValue(false); - service.sendCommand({}, jest.fn()); + service.sendCommand({}, vi.fn()); expect(mockSocket.send).not.toHaveBeenCalled(); }); }); @@ -87,7 +90,7 @@ describe('ProtobufService', () => { describe('sendSessionCommand', () => { it('creates a CommandContainer and calls sendCommand', () => { const service = new ProtobufService(mockWebClient); - const cb = jest.fn(); + const cb = vi.fn(); service.sendSessionCommand({ cmdType: 'test' }, cb); expect(ProtoController.root.CommandContainer.create).toHaveBeenCalledWith( expect.objectContaining({ sessionCommand: expect.anything() }) @@ -96,7 +99,7 @@ describe('ProtobufService', () => { it('invokes callback with raw response when the pending command is triggered', () => { const service = new ProtobufService(mockWebClient); - const cb = jest.fn(); + const cb = vi.fn(); service.sendSessionCommand({ cmdType: 'test' }, cb); const storedCb = (service as any).pendingCommands[1]; @@ -117,7 +120,7 @@ describe('ProtobufService', () => { describe('sendRoomCommand', () => { it('creates a CommandContainer with roomId and calls sendCommand', () => { const service = new ProtobufService(mockWebClient); - service.sendRoomCommand(42, { roomCmdType: 'test' }, jest.fn()); + service.sendRoomCommand(42, { roomCmdType: 'test' }, vi.fn()); expect(ProtoController.root.CommandContainer.create).toHaveBeenCalledWith( expect.objectContaining({ roomId: 42 }) ); @@ -125,7 +128,7 @@ describe('ProtobufService', () => { it('invokes callback with raw response when the pending command is triggered', () => { const service = new ProtobufService(mockWebClient); - const cb = jest.fn(); + const cb = vi.fn(); service.sendRoomCommand(42, { roomCmdType: 'test' }, cb); const storedCb = (service as any).pendingCommands[1]; @@ -146,7 +149,7 @@ describe('ProtobufService', () => { describe('sendGameCommand', () => { it('creates a CommandContainer with gameId and gameCommand', () => { const service = new ProtobufService(mockWebClient); - service.sendGameCommand(7, { gameCmdType: 'test' }, jest.fn()); + service.sendGameCommand(7, { gameCmdType: 'test' }, vi.fn()); expect(ProtoController.root.CommandContainer.create).toHaveBeenCalledWith( expect.objectContaining({ gameId: 7, gameCommand: expect.anything() }) ); @@ -154,7 +157,7 @@ describe('ProtobufService', () => { it('invokes callback with raw response when the pending command is triggered', () => { const service = new ProtobufService(mockWebClient); - const cb = jest.fn(); + const cb = vi.fn(); service.sendGameCommand(7, { gameCmdType: 'test' }, cb); const storedCb = (service as any).pendingCommands[1]; @@ -175,7 +178,7 @@ describe('ProtobufService', () => { describe('sendModeratorCommand', () => { it('creates a CommandContainer with moderatorCommand', () => { const service = new ProtobufService(mockWebClient); - service.sendModeratorCommand({ modCmdType: 'test' }, jest.fn()); + service.sendModeratorCommand({ modCmdType: 'test' }, vi.fn()); expect(ProtoController.root.CommandContainer.create).toHaveBeenCalledWith( expect.objectContaining({ moderatorCommand: expect.anything() }) ); @@ -183,7 +186,7 @@ describe('ProtobufService', () => { it('invokes callback with raw response when the pending command is triggered', () => { const service = new ProtobufService(mockWebClient); - const cb = jest.fn(); + const cb = vi.fn(); service.sendModeratorCommand({ modCmdType: 'test' }, cb); const storedCb = (service as any).pendingCommands[1]; @@ -204,7 +207,7 @@ describe('ProtobufService', () => { describe('sendAdminCommand', () => { it('creates a CommandContainer with adminCommand', () => { const service = new ProtobufService(mockWebClient); - service.sendAdminCommand({ adminCmdType: 'test' }, jest.fn()); + service.sendAdminCommand({ adminCmdType: 'test' }, vi.fn()); expect(ProtoController.root.CommandContainer.create).toHaveBeenCalledWith( expect.objectContaining({ adminCommand: expect.anything() }) ); @@ -212,7 +215,7 @@ describe('ProtobufService', () => { it('invokes callback with raw response when the pending command is triggered', () => { const service = new ProtobufService(mockWebClient); - const cb = jest.fn(); + const cb = vi.fn(); service.sendAdminCommand({ adminCmdType: 'test' }, cb); const storedCb = (service as any).pendingCommands[1]; @@ -233,7 +236,7 @@ describe('ProtobufService', () => { describe('sendKeepAliveCommand', () => { it('delegates to SessionCommands.ping', () => { const service = new ProtobufService(mockWebClient); - const pingReceived = jest.fn(); + const pingReceived = vi.fn(); service.sendKeepAliveCommand(pingReceived); expect(sessionPing).toHaveBeenCalledWith(pingReceived); }); @@ -242,13 +245,13 @@ describe('ProtobufService', () => { describe('handleMessageEvent', () => { it('routes RESPONSE message to processServerResponse', () => { const service = new ProtobufService(mockWebClient); - const cb = jest.fn(); + const cb = vi.fn(); // store a callback for cmdId 1 (service as any).cmdId = 1; (service as any).pendingCommands[1] = cb; const response = { cmdId: 1 }; - ProtoController.root.ServerMessage.decode = jest.fn().mockReturnValue({ + ProtoController.root.ServerMessage.decode = vi.fn().mockReturnValue({ messageType: ProtoController.root.ServerMessage.MessageType.RESPONSE, response, }); @@ -260,14 +263,14 @@ describe('ProtobufService', () => { it('resolves pending command when response cmdId is a protobufjs Long object', () => { const service = new ProtobufService(mockWebClient); - const cb = jest.fn(); + const cb = vi.fn(); (service as any).cmdId = 1; (service as any).pendingCommands[1] = cb; // Simulate protobufjs decoding cmdId as a Long object (low=1, high=0) const longCmdId = { low: 1, high: 0, unsigned: false, toString: () => '1' }; const response = { cmdId: longCmdId }; - ProtoController.root.ServerMessage.decode = jest.fn().mockReturnValue({ + ProtoController.root.ServerMessage.decode = vi.fn().mockReturnValue({ messageType: ProtoController.root.ServerMessage.MessageType.RESPONSE, response, }); @@ -279,8 +282,8 @@ describe('ProtobufService', () => { it('routes ROOM_EVENT message', () => { const service = new ProtobufService(mockWebClient); - const processRoomEvent = jest.spyOn(service as any, 'processRoomEvent'); - ProtoController.root.ServerMessage.decode = jest.fn().mockReturnValue({ + const processRoomEvent = vi.spyOn(service as any, 'processRoomEvent'); + ProtoController.root.ServerMessage.decode = vi.fn().mockReturnValue({ messageType: ProtoController.root.ServerMessage.MessageType.ROOM_EVENT, roomEvent: { '.Event_Room.ext': {} }, }); @@ -290,8 +293,8 @@ describe('ProtobufService', () => { it('routes SESSION_EVENT message', () => { const service = new ProtobufService(mockWebClient); - const processSessionEvent = jest.spyOn(service as any, 'processSessionEvent'); - ProtoController.root.ServerMessage.decode = jest.fn().mockReturnValue({ + const processSessionEvent = vi.spyOn(service as any, 'processSessionEvent'); + ProtoController.root.ServerMessage.decode = vi.fn().mockReturnValue({ messageType: ProtoController.root.ServerMessage.MessageType.SESSION_EVENT, sessionEvent: { '.Event_Session.ext': {} }, }); @@ -301,8 +304,8 @@ describe('ProtobufService', () => { it('routes GAME_EVENT_CONTAINER message', () => { const service = new ProtobufService(mockWebClient); - const processGameEvent = jest.spyOn(service as any, 'processGameEvent'); - ProtoController.root.ServerMessage.decode = jest.fn().mockReturnValue({ + const processGameEvent = vi.spyOn(service as any, 'processGameEvent'); + ProtoController.root.ServerMessage.decode = vi.fn().mockReturnValue({ messageType: ProtoController.root.ServerMessage.MessageType.GAME_EVENT_CONTAINER, gameEvent: { '.Event_Game.ext': {} }, }); @@ -312,8 +315,8 @@ describe('ProtobufService', () => { it('logs unknown message types (default case)', () => { const service = new ProtobufService(mockWebClient); - const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); - ProtoController.root.ServerMessage.decode = jest.fn().mockReturnValue({ + const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + ProtoController.root.ServerMessage.decode = vi.fn().mockReturnValue({ messageType: 'UNKNOWN_TYPE', }); service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); @@ -323,14 +326,14 @@ describe('ProtobufService', () => { it('does nothing when decoded message is null', () => { const service = new ProtobufService(mockWebClient); - ProtoController.root.ServerMessage.decode = jest.fn().mockReturnValue(null); + ProtoController.root.ServerMessage.decode = vi.fn().mockReturnValue(null); expect(() => service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent)).not.toThrow(); }); it('catches and logs decode errors', () => { const service = new ProtobufService(mockWebClient); - const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - ProtoController.root.ServerMessage.decode = jest.fn().mockImplementation(() => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + ProtoController.root.ServerMessage.decode = vi.fn().mockImplementation(() => { throw new Error('decode error'); }); expect(() => service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent)).not.toThrow(); @@ -342,14 +345,14 @@ describe('ProtobufService', () => { describe('processGameEvent', () => { it('returns early when container has no eventList', () => { const service = new ProtobufService(mockWebClient); - const gameEventHandler = (GameEvents as any)['.Event_Game.ext'] as jest.Mock; + const gameEventHandler = (GameEvents as any)['.Event_Game.ext'] as vi.Mock; (service as any).processGameEvent(null, {}); expect(gameEventHandler).not.toHaveBeenCalled(); }); it('dispatches to a GameEvents handler when event key matches', () => { const service = new ProtobufService(mockWebClient); - const gameEventHandler = (GameEvents as any)['.Event_Game.ext'] as jest.Mock; + const gameEventHandler = (GameEvents as any)['.Event_Game.ext'] as vi.Mock; const payload = { someData: 1 }; (service as any).processGameEvent({ gameId: 42, @@ -366,7 +369,7 @@ describe('ProtobufService', () => { describe('processEvent', () => { it('calls matching event handler with payload and raw', () => { const service = new ProtobufService(mockWebClient); - const handler = jest.fn(); + const handler = vi.fn(); const events = { '.Event_Test.ext': handler }; const payload = { someData: 1 }; const response = { '.Event_Test.ext': payload }; @@ -379,8 +382,8 @@ describe('ProtobufService', () => { it('stops after first matching event', () => { const service = new ProtobufService(mockWebClient); - const handler1 = jest.fn(); - const handler2 = jest.fn(); + const handler1 = vi.fn(); + const handler2 = vi.fn(); const events = { '.Event_A.ext': handler1, '.Event_B.ext': handler2 }; const response = { '.Event_A.ext': { x: 1 } }; diff --git a/webclient/src/websocket/services/WebSocketService.spec.ts b/webclient/src/websocket/services/WebSocketService.spec.ts index 828b60213..ce511ae31 100644 --- a/webclient/src/websocket/services/WebSocketService.spec.ts +++ b/webclient/src/websocket/services/WebSocketService.spec.ts @@ -1,14 +1,14 @@ import { installMockWebSocket } from '../__mocks__/helpers'; -jest.mock('../commands/session', () => ({ - updateStatus: jest.fn(), +vi.mock('../commands/session', () => ({ + updateStatus: vi.fn(), })); -jest.mock('../persistence', () => ({ +vi.mock('../persistence', () => ({ SessionPersistence: { - connectionFailed: jest.fn(), - testConnectionSuccessful: jest.fn(), - testConnectionFailed: jest.fn(), + connectionFailed: vi.fn(), + testConnectionSuccessful: vi.fn(), + testConnectionFailed: vi.fn(), }, })); @@ -17,13 +17,13 @@ import { SessionPersistence } from '../persistence'; import { updateStatus } from '../commands/session'; import { StatusEnum } from 'types'; -let MockWS: jest.Mock; +let MockWS: vi.Mock; let mockInstance: ReturnType['mockInstance']; let mockWebClient: any; beforeEach(() => { - jest.useFakeTimers(); - jest.clearAllMocks(); + vi.useFakeTimers(); + vi.clearAllMocks(); const installed = installMockWebSocket(); MockWS = installed.MockWS; @@ -32,12 +32,12 @@ beforeEach(() => { mockWebClient = { status: StatusEnum.CONNECTED, clientOptions: { keepalive: 1000 }, - keepAlive: jest.fn(), + keepAlive: vi.fn(), }; }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); describe('WebSocketService', () => { @@ -99,14 +99,14 @@ describe('WebSocketService', () => { it('fires socket.close after keepalive timeout', () => { createConnectedService(); - jest.advanceTimersByTime(1000); + vi.advanceTimersByTime(1000); expect(mockInstance.close).toHaveBeenCalled(); }); }); describe('socket event handlers (onopen)', () => { it('clears the connection timeout when socket opens', () => { - const clearSpy = jest.spyOn(global, 'clearTimeout'); + const clearSpy = vi.spyOn(global, 'clearTimeout'); createConnectedService(); mockInstance.onopen(); expect(clearSpy).toHaveBeenCalled(); @@ -120,7 +120,7 @@ describe('WebSocketService', () => { it('starts the ping loop with the keepalive interval', () => { const service = new WebSocketService(mockWebClient); - const startSpy = jest.spyOn((service as any).keepAliveService, 'startPingLoop'); + const startSpy = vi.spyOn((service as any).keepAliveService, 'startPingLoop'); service.connect({ host: 'h', port: 1 } as any, 'ws'); mockInstance.onopen(); expect(startSpy).toHaveBeenCalledWith(1000, expect.any(Function)); @@ -128,11 +128,11 @@ describe('WebSocketService', () => { it('ping loop callback calls webClient.keepAlive', () => { const service = new WebSocketService(mockWebClient); - const startSpy = jest.spyOn((service as any).keepAliveService, 'startPingLoop'); + const startSpy = vi.spyOn((service as any).keepAliveService, 'startPingLoop'); service.connect({ host: 'h', port: 1 } as any, 'ws'); mockInstance.onopen(); const pingCb = startSpy.mock.calls[0][1] as (done: Function) => void; - const done = jest.fn(); + const done = vi.fn(); pingCb(done); expect(mockWebClient.keepAlive).toHaveBeenCalledWith(done); }); @@ -154,7 +154,7 @@ describe('WebSocketService', () => { it('ends the ping loop on close', () => { const service = new WebSocketService(mockWebClient); - const endSpy = jest.spyOn((service as any).keepAliveService, 'endPingLoop'); + const endSpy = vi.spyOn((service as any).keepAliveService, 'endPingLoop'); service.connect({ host: 'h', port: 1 } as any, 'ws'); mockInstance.onclose(); expect(endSpy).toHaveBeenCalled(); @@ -178,7 +178,7 @@ describe('WebSocketService', () => { describe('socket event handlers (onmessage)', () => { it('emits on message$ subject', () => { const service = createConnectedService(); - const handler = jest.fn(); + const handler = vi.fn(); service.message$.subscribe(handler); const event = { data: new ArrayBuffer(4) } as MessageEvent; mockInstance.onmessage(event); @@ -262,7 +262,7 @@ describe('WebSocketService', () => { it('calls SessionPersistence.testConnectionSuccessful on open', () => { createTestConnectedService(); - const timer = jest.spyOn(global, 'clearTimeout'); + const timer = vi.spyOn(global, 'clearTimeout'); mockInstance.onopen(); expect(SessionPersistence.testConnectionSuccessful).toHaveBeenCalled(); expect(mockInstance.close).toHaveBeenCalled(); @@ -270,7 +270,7 @@ describe('WebSocketService', () => { it('fires socket.close after keepalive timeout for testConnect', () => { createTestConnectedService(); - jest.advanceTimersByTime(1000); + vi.advanceTimersByTime(1000); expect(mockInstance.close).toHaveBeenCalled(); }); diff --git a/webclient/src/websocket/utils/guid.util.spec.ts b/webclient/src/websocket/utils/guid.util.spec.ts index 4ed8d4955..4af74a720 100644 --- a/webclient/src/websocket/utils/guid.util.spec.ts +++ b/webclient/src/websocket/utils/guid.util.spec.ts @@ -11,7 +11,7 @@ describe('guid', () => { }); it('returns deterministic value when Math.random is mocked', () => { - const spy = jest.spyOn(Math, 'random').mockReturnValue(0.5); + const spy = vi.spyOn(Math, 'random').mockReturnValue(0.5); const result = guid(); expect(result).toBe(guid()); spy.mockRestore(); diff --git a/webclient/src/websocket/utils/passwordHasher.spec.ts b/webclient/src/websocket/utils/passwordHasher.spec.ts index d4275fa43..a8a4148af 100644 --- a/webclient/src/websocket/utils/passwordHasher.spec.ts +++ b/webclient/src/websocket/utils/passwordHasher.spec.ts @@ -1,6 +1,6 @@ import { makeMockProtoRoot } from '../__mocks__/helpers'; -jest.mock('../services/ProtoController', () => ({ +vi.mock('../services/ProtoController', () => ({ ProtoController: { root: null }, })); diff --git a/webclient/src/websocket/utils/sanitizeHtml.util.spec.ts b/webclient/src/websocket/utils/sanitizeHtml.util.spec.ts index 86c547548..8cf5d90f3 100644 --- a/webclient/src/websocket/utils/sanitizeHtml.util.spec.ts +++ b/webclient/src/websocket/utils/sanitizeHtml.util.spec.ts @@ -6,7 +6,7 @@ describe('sanitizeHtml', () => { }); it('allows
tag', () => { - expect(sanitizeHtml('line1
line2')).toBe('line1
line2'); + expect(sanitizeHtml('line1
line2')).toBe('line1
line2'); }); it('allows tag', () => { @@ -14,7 +14,7 @@ describe('sanitizeHtml', () => { }); it('allows tag', () => { - expect(sanitizeHtml('')).toBe(''); + expect(sanitizeHtml('')).toBe(''); }); it('allows
tag', () => { diff --git a/webclient/src/websocket/utils/sanitizeHtml.util.ts b/webclient/src/websocket/utils/sanitizeHtml.util.ts index 886a56e48..b8deb0ec7 100644 --- a/webclient/src/websocket/utils/sanitizeHtml.util.ts +++ b/webclient/src/websocket/utils/sanitizeHtml.util.ts @@ -1,18 +1,17 @@ -import sanitize from 'sanitize-html'; +import DOMPurify from 'dompurify'; + +DOMPurify.addHook('afterSanitizeAttributes', (node) => { + if (node.tagName === 'A') { + node.setAttribute('target', '_blank'); + node.setAttribute('rel', 'noopener noreferrer'); + } +}); export function sanitizeHtml(msg: string): string { - return sanitize(msg, { - allowedTags: ['br', 'a', 'img', 'center', 'b', 'font'], - allowedAttributes: { - '*': ['href', 'color', 'rel', 'target'], - 'img': ['src', 'alt'], - }, - allowedSchemes: ['http', 'https', 'ftp'], - transformTags: { - 'a': sanitize.simpleTransform('a', { - target: '_blank', - rel: 'noopener noreferrer', - }), - } + return DOMPurify.sanitize(msg, { + ALLOWED_TAGS: ['br', 'a', 'img', 'center', 'b', 'font'], + ALLOWED_ATTR: ['href', 'color', 'rel', 'target', 'src', 'alt'], + ADD_URI_SAFE_ATTR: ['color'], + ALLOWED_URI_REGEXP: /^(?:(?:https?|ftp):)/i, }); } diff --git a/webclient/tsconfig.json b/webclient/tsconfig.json index 0e35af354..1287c0521 100644 --- a/webclient/tsconfig.json +++ b/webclient/tsconfig.json @@ -17,7 +17,7 @@ "strict": false, "forceConsistentCasingInFileNames": true, "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, diff --git a/webclient/vite.config.ts b/webclient/vite.config.ts new file mode 100644 index 000000000..59a21c05b --- /dev/null +++ b/webclient/vite.config.ts @@ -0,0 +1,21 @@ +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + plugins: [react(), tsconfigPaths()], + publicDir: 'public', + build: { + outDir: 'build', + }, + server: { + open: true, + }, + test: { + globals: true, + environment: 'jsdom', + setupFiles: ['./src/setupTests.ts'], + include: ['src/**/*.spec.{ts,tsx}'], + css: true, + }, +}); From fd55f4fb7ff25a9c4c789aefb5f40d048880f814 Mon Sep 17 00:00:00 2001 From: seavor Date: Mon, 13 Apr 2026 15:03:57 -0500 Subject: [PATCH 10/38] migrate to Protobuf ES --- webclient/.gitignore | 1 + webclient/buf.gen.yaml | 8 + webclient/package-lock.json | 354 ++++++++++- webclient/package.json | 7 +- webclient/prebuild.js | 18 - .../src/api/AuthenticationService.spec.ts | 12 +- webclient/src/api/AuthenticationService.tsx | 4 +- webclient/src/setupTests.ts | 8 - webclient/src/store/common/SortUtil.spec.ts | 10 +- .../store/rooms/__mocks__/rooms-fixtures.ts | 5 +- .../store/server/__mocks__/server-fixtures.ts | 5 +- webclient/src/types/game.ts | 597 +++++------------- webclient/src/types/message.ts | 10 +- webclient/src/types/replay.ts | 19 +- webclient/src/types/room.ts | 25 +- webclient/src/types/session.ts | 8 +- webclient/src/types/user.ts | 26 +- webclient/src/websocket/WebClient.spec.ts | 6 +- webclient/src/websocket/WebClient.ts | 2 + .../websocket/__mocks__/callbackHelpers.ts | 4 +- webclient/src/websocket/__mocks__/helpers.ts | 47 +- .../__mocks__/sessionCommandMocks.ts | 32 - .../src/websocket/commands/admin/adjustMod.ts | 4 +- .../commands/admin/adminCommands.spec.ts | 19 +- .../websocket/commands/admin/reloadConfig.ts | 4 +- .../commands/admin/shutdownServer.ts | 4 +- .../commands/admin/updateServerMessage.ts | 4 +- .../src/websocket/commands/game/attachCard.ts | 4 +- .../commands/game/changeZoneProperties.ts | 4 +- .../src/websocket/commands/game/concede.ts | 4 +- .../websocket/commands/game/createArrow.ts | 4 +- .../websocket/commands/game/createCounter.ts | 4 +- .../websocket/commands/game/createToken.ts | 4 +- .../src/websocket/commands/game/deckSelect.ts | 4 +- .../src/websocket/commands/game/delCounter.ts | 4 +- .../websocket/commands/game/deleteArrow.ts | 4 +- .../src/websocket/commands/game/drawCards.ts | 4 +- .../src/websocket/commands/game/dumpZone.ts | 4 +- .../src/websocket/commands/game/flipCard.ts | 4 +- .../commands/game/gameCommands.spec.ts | 198 ++++-- .../src/websocket/commands/game/gameSay.ts | 4 +- .../websocket/commands/game/incCardCounter.ts | 4 +- .../src/websocket/commands/game/incCounter.ts | 4 +- .../src/websocket/commands/game/judge.ts | 10 +- .../websocket/commands/game/kickFromGame.ts | 4 +- .../src/websocket/commands/game/leaveGame.ts | 4 +- .../src/websocket/commands/game/moveCard.ts | 4 +- .../src/websocket/commands/game/mulligan.ts | 4 +- .../src/websocket/commands/game/nextTurn.ts | 4 +- .../src/websocket/commands/game/readyStart.ts | 4 +- .../websocket/commands/game/revealCards.ts | 4 +- .../websocket/commands/game/reverseTurn.ts | 4 +- .../websocket/commands/game/setActivePhase.ts | 4 +- .../websocket/commands/game/setCardAttr.ts | 4 +- .../websocket/commands/game/setCardCounter.ts | 4 +- .../src/websocket/commands/game/setCounter.ts | 4 +- .../commands/game/setSideboardLock.ts | 4 +- .../commands/game/setSideboardPlan.ts | 4 +- .../src/websocket/commands/game/shuffle.ts | 4 +- .../src/websocket/commands/game/unconcede.ts | 4 +- .../src/websocket/commands/game/undoDraw.ts | 4 +- .../commands/moderator/banFromServer.ts | 6 +- .../commands/moderator/forceActivateUser.ts | 7 +- .../commands/moderator/getAdminNotes.ts | 7 +- .../commands/moderator/getBanHistory.ts | 7 +- .../commands/moderator/getWarnHistory.ts | 7 +- .../commands/moderator/getWarnList.ts | 7 +- .../commands/moderator/grantReplayAccess.ts | 6 +- .../moderator/moderatorCommands.spec.ts | 64 +- .../commands/moderator/updateAdminNotes.ts | 6 +- .../commands/moderator/viewLogHistory.ts | 7 +- .../websocket/commands/moderator/warnUser.ts | 5 +- .../src/websocket/commands/room/createGame.ts | 4 +- .../src/websocket/commands/room/joinGame.ts | 4 +- .../src/websocket/commands/room/leaveRoom.ts | 4 +- .../commands/room/roomCommands.spec.ts | 19 +- .../src/websocket/commands/room/roomSay.ts | 4 +- .../websocket/commands/session/accountEdit.ts | 5 +- .../commands/session/accountImage.ts | 4 +- .../commands/session/accountPassword.ts | 5 +- .../websocket/commands/session/activate.ts | 10 +- .../websocket/commands/session/addToList.ts | 4 +- .../src/websocket/commands/session/deckDel.ts | 4 +- .../websocket/commands/session/deckDelDir.ts | 4 +- .../websocket/commands/session/deckList.ts | 7 +- .../websocket/commands/session/deckNewDir.ts | 4 +- .../websocket/commands/session/deckUpload.ts | 7 +- .../session/forgotPasswordChallenge.ts | 8 +- .../commands/session/forgotPasswordRequest.ts | 11 +- .../commands/session/forgotPasswordReset.ts | 18 +- .../commands/session/getGamesOfUser.ts | 7 +- .../websocket/commands/session/getUserInfo.ts | 7 +- .../websocket/commands/session/joinRoom.ts | 7 +- .../websocket/commands/session/listRooms.ts | 4 +- .../websocket/commands/session/listUsers.ts | 7 +- .../src/websocket/commands/session/login.ts | 41 +- .../src/websocket/commands/session/message.ts | 4 +- .../src/websocket/commands/session/ping.ts | 4 +- .../websocket/commands/session/register.ts | 40 +- .../commands/session/removeFromList.ts | 4 +- .../commands/session/replayDeleteMatch.ts | 4 +- .../commands/session/replayGetCode.ts | 7 +- .../websocket/commands/session/replayList.ts | 7 +- .../commands/session/replayModifyMatch.ts | 4 +- .../commands/session/replaySubmitCode.ts | 4 +- .../commands/session/requestPasswordSalt.ts | 15 +- .../session/sessionCommands-complex.spec.ts | 125 ++-- .../session/sessionCommands-simple.spec.ts | 162 +++-- .../events/common/commonEvents.spec.ts | 2 +- .../src/websocket/events/common/index.ts | 4 +- webclient/src/websocket/events/game/index.ts | 95 ++- webclient/src/websocket/events/room/index.ts | 23 +- .../src/websocket/events/room/interfaces.ts | 33 +- .../src/websocket/events/room/joinRoom.ts | 2 +- .../src/websocket/events/room/leaveRoom.ts | 2 +- .../src/websocket/events/room/listGames.ts | 2 +- .../websocket/events/room/removeMessages.ts | 2 +- .../websocket/events/room/roomEvents.spec.ts | 2 +- .../src/websocket/events/room/roomSay.ts | 2 +- .../events/session/connectionClosed.ts | 19 +- .../src/websocket/events/session/index.ts | 50 +- .../websocket/events/session/interfaces.ts | 119 +--- .../events/session/sessionEvents.spec.ts | 50 +- .../persistence/SessionPersistence.ts | 12 +- .../websocket/services/BackendService.spec.ts | 68 +- .../src/websocket/services/BackendService.ts | 97 ++- .../services/ProtoController.spec.ts | 40 -- .../src/websocket/services/ProtoController.ts | 24 - .../services/ProtobufService.spec.ts | 292 +++++---- .../src/websocket/services/ProtobufService.ts | 122 ++-- .../websocket/utils/passwordHasher.spec.ts | 11 +- .../src/websocket/utils/passwordHasher.ts | 4 +- webclient/tsconfig.json | 2 +- 133 files changed, 1745 insertions(+), 1621 deletions(-) create mode 100644 webclient/buf.gen.yaml delete mode 100644 webclient/src/websocket/services/ProtoController.spec.ts delete mode 100644 webclient/src/websocket/services/ProtoController.ts diff --git a/webclient/.gitignore b/webclient/.gitignore index 2b30e2c8d..d0eec90c8 100644 --- a/webclient/.gitignore +++ b/webclient/.gitignore @@ -8,6 +8,7 @@ # generated ./src files /src/proto-files.json /src/server-props.json +/src/generated/ # testing /coverage diff --git a/webclient/buf.gen.yaml b/webclient/buf.gen.yaml new file mode 100644 index 000000000..eba5ea4ad --- /dev/null +++ b/webclient/buf.gen.yaml @@ -0,0 +1,8 @@ +version: v2 +inputs: + - directory: ../libcockatrice_protocol/libcockatrice/protocol/pb +plugins: + - local: protoc-gen-es + out: src/generated/proto + opt: + - target=ts diff --git a/webclient/package-lock.json b/webclient/package-lock.json index af3f74e50..dd410fe16 100644 --- a/webclient/package-lock.json +++ b/webclient/package-lock.json @@ -8,6 +8,7 @@ "name": "webclient", "version": "1.0.0", "dependencies": { + "@bufbuild/protobuf": "^2.11.0", "@emotion/react": "^11.8.2", "@emotion/styled": "^11.8.1", "@mui/icons-material": "^5.5.1", @@ -39,6 +40,8 @@ "rxjs": "^7.5.4" }, "devDependencies": { + "@bufbuild/buf": "^1.67.0", + "@bufbuild/protoc-gen-es": "^2.11.0", "@mui/types": "^7.1.3", "@testing-library/jest-dom": "^6.4.0", "@testing-library/react": "^13.4.0", @@ -433,6 +436,207 @@ "dev": true, "license": "MIT" }, + "node_modules/@bufbuild/buf": { + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.67.0.tgz", + "integrity": "sha512-BLfgGmNFiHM79PcaafFNiP/+xxbdyFp1neDDdJd6R0tu7McO+WgJHM6vyNYRm7vXOSgO1uUPE4X3YFdBgcWk2Q==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "buf": "bin/buf", + "protoc-gen-buf-breaking": "bin/protoc-gen-buf-breaking", + "protoc-gen-buf-lint": "bin/protoc-gen-buf-lint" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@bufbuild/buf-darwin-arm64": "1.67.0", + "@bufbuild/buf-darwin-x64": "1.67.0", + "@bufbuild/buf-linux-aarch64": "1.67.0", + "@bufbuild/buf-linux-armv7": "1.67.0", + "@bufbuild/buf-linux-x64": "1.67.0", + "@bufbuild/buf-win32-arm64": "1.67.0", + "@bufbuild/buf-win32-x64": "1.67.0" + } + }, + "node_modules/@bufbuild/buf-darwin-arm64": { + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.67.0.tgz", + "integrity": "sha512-9h/1E2FNCSIt9m4wriGiXt8gHrg8VBOOpmUPVr68axZxb17krPQrIZBPsx05yNpbyvSrPj26/jO2aoqpZsG1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-darwin-x64": { + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.67.0.tgz", + "integrity": "sha512-9kNu0JBR+TQvxCD6NBooy03g8sLNZGEd0umkWHzdO/05HuV/J6GecMGx1kJ2MYlZQHM4/MljfIuYQUblP1nP4A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-linux-aarch64": { + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.67.0.tgz", + "integrity": "sha512-hlA20Oot20nW/9CzPBMPPPMfUarKvzqni+Njgrw8T43IFoQWQv8iIRoWWOgOQTGCm4PmjYwiojzEHOEaaKrzTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-linux-armv7": { + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-armv7/-/buf-linux-armv7-1.67.0.tgz", + "integrity": "sha512-hO9FEEtloITNaxW89rzKUjAsgnX1+rth7IZbK0Z+ohatXdanYg7Kv66yWffytaYf2iHltTbY6W/H4C3x0Uimbg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-linux-x64": { + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.67.0.tgz", + "integrity": "sha512-KBOWZ0NbhJSfXLM3JEX2AEs32jyHvTKD7wkIYudqOTxPUqwM1MXUg7m2Xw5nP1pcKH4RKS5HFijPMeOW/XUQ8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-win32-arm64": { + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.67.0.tgz", + "integrity": "sha512-ARGPwOv0lkUp3FU7bUMpYzqoJInx2qkk1ECBEC9XZMnRKmhCbyzmBoBKChBBJhEyDFdzPivhjg//zk5AlQ3bFA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-win32-x64": { + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.67.0.tgz", + "integrity": "sha512-x9fkxEbjb2U4petBbESvNx+sfSQJONJxKOQzPfEKALksqRlvh7ktoHrYbygErnRZBSTNgrXzAqFI1GxMGEGSLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz", + "integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@bufbuild/protoc-gen-es": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protoc-gen-es/-/protoc-gen-es-2.11.0.tgz", + "integrity": "sha512-VzQuwEQDXipbZ1soWUuAWm1Z0C3B/IDWGeysnbX6ogJ6As91C2mdvAND/ekQ4YIWgen4d5nqLfIBOWLqCCjYUA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@bufbuild/protobuf": "2.11.0", + "@bufbuild/protoplugin": "2.11.0" + }, + "bin": { + "protoc-gen-es": "bin/protoc-gen-es" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@bufbuild/protobuf": "2.11.0" + }, + "peerDependenciesMeta": { + "@bufbuild/protobuf": { + "optional": true + } + } + }, + "node_modules/@bufbuild/protoplugin": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protoplugin/-/protoplugin-2.11.0.tgz", + "integrity": "sha512-lyZVNFUHArIOt4W0+dwYBe5GBwbKzbOy8ObaloEqsw9Mmiwv2O48TwddDoHN4itylC+BaEGqFdI1W8WQt2vWJQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@bufbuild/protobuf": "2.11.0", + "@typescript/vfs": "^1.6.2", + "typescript": "5.4.5" + } + }, + "node_modules/@bufbuild/protoplugin/node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", @@ -2543,6 +2747,19 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript/vfs": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.6.4.tgz", + "integrity": "sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3" + }, + "peerDependencies": { + "typescript": "*" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -3254,11 +3471,12 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -5369,9 +5587,10 @@ "license": "MIT" }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.8", @@ -7649,6 +7868,104 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "@bufbuild/buf": { + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.67.0.tgz", + "integrity": "sha512-BLfgGmNFiHM79PcaafFNiP/+xxbdyFp1neDDdJd6R0tu7McO+WgJHM6vyNYRm7vXOSgO1uUPE4X3YFdBgcWk2Q==", + "dev": true, + "requires": { + "@bufbuild/buf-darwin-arm64": "1.67.0", + "@bufbuild/buf-darwin-x64": "1.67.0", + "@bufbuild/buf-linux-aarch64": "1.67.0", + "@bufbuild/buf-linux-armv7": "1.67.0", + "@bufbuild/buf-linux-x64": "1.67.0", + "@bufbuild/buf-win32-arm64": "1.67.0", + "@bufbuild/buf-win32-x64": "1.67.0" + } + }, + "@bufbuild/buf-darwin-arm64": { + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.67.0.tgz", + "integrity": "sha512-9h/1E2FNCSIt9m4wriGiXt8gHrg8VBOOpmUPVr68axZxb17krPQrIZBPsx05yNpbyvSrPj26/jO2aoqpZsG1vw==", + "dev": true, + "optional": true + }, + "@bufbuild/buf-darwin-x64": { + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.67.0.tgz", + "integrity": "sha512-9kNu0JBR+TQvxCD6NBooy03g8sLNZGEd0umkWHzdO/05HuV/J6GecMGx1kJ2MYlZQHM4/MljfIuYQUblP1nP4A==", + "dev": true, + "optional": true + }, + "@bufbuild/buf-linux-aarch64": { + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.67.0.tgz", + "integrity": "sha512-hlA20Oot20nW/9CzPBMPPPMfUarKvzqni+Njgrw8T43IFoQWQv8iIRoWWOgOQTGCm4PmjYwiojzEHOEaaKrzTg==", + "dev": true, + "optional": true + }, + "@bufbuild/buf-linux-armv7": { + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-armv7/-/buf-linux-armv7-1.67.0.tgz", + "integrity": "sha512-hO9FEEtloITNaxW89rzKUjAsgnX1+rth7IZbK0Z+ohatXdanYg7Kv66yWffytaYf2iHltTbY6W/H4C3x0Uimbg==", + "dev": true, + "optional": true + }, + "@bufbuild/buf-linux-x64": { + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.67.0.tgz", + "integrity": "sha512-KBOWZ0NbhJSfXLM3JEX2AEs32jyHvTKD7wkIYudqOTxPUqwM1MXUg7m2Xw5nP1pcKH4RKS5HFijPMeOW/XUQ8Q==", + "dev": true, + "optional": true + }, + "@bufbuild/buf-win32-arm64": { + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.67.0.tgz", + "integrity": "sha512-ARGPwOv0lkUp3FU7bUMpYzqoJInx2qkk1ECBEC9XZMnRKmhCbyzmBoBKChBBJhEyDFdzPivhjg//zk5AlQ3bFA==", + "dev": true, + "optional": true + }, + "@bufbuild/buf-win32-x64": { + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.67.0.tgz", + "integrity": "sha512-x9fkxEbjb2U4petBbESvNx+sfSQJONJxKOQzPfEKALksqRlvh7ktoHrYbygErnRZBSTNgrXzAqFI1GxMGEGSLQ==", + "dev": true, + "optional": true + }, + "@bufbuild/protobuf": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz", + "integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==" + }, + "@bufbuild/protoc-gen-es": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protoc-gen-es/-/protoc-gen-es-2.11.0.tgz", + "integrity": "sha512-VzQuwEQDXipbZ1soWUuAWm1Z0C3B/IDWGeysnbX6ogJ6As91C2mdvAND/ekQ4YIWgen4d5nqLfIBOWLqCCjYUA==", + "dev": true, + "requires": { + "@bufbuild/protobuf": "2.11.0", + "@bufbuild/protoplugin": "2.11.0" + } + }, + "@bufbuild/protoplugin": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protoplugin/-/protoplugin-2.11.0.tgz", + "integrity": "sha512-lyZVNFUHArIOt4W0+dwYBe5GBwbKzbOy8ObaloEqsw9Mmiwv2O48TwddDoHN4itylC+BaEGqFdI1W8WQt2vWJQ==", + "dev": true, + "requires": { + "@bufbuild/protobuf": "2.11.0", + "@typescript/vfs": "^1.6.2", + "typescript": "5.4.5" + }, + "dependencies": { + "typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true + } + } + }, "@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", @@ -8911,6 +9228,15 @@ "eslint-visitor-keys": "^3.3.0" } }, + "@typescript/vfs": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.6.4.tgz", + "integrity": "sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ==", + "dev": true, + "requires": { + "debug": "^4.4.3" + } + }, "@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -9403,11 +9729,11 @@ } }, "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" } }, "decimal.js": { @@ -10927,9 +11253,9 @@ } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "nanoid": { "version": "3.3.8", diff --git a/webclient/package.json b/webclient/package.json index 9c24e60a4..527ebfa7c 100644 --- a/webclient/package.json +++ b/webclient/package.json @@ -14,9 +14,11 @@ "lint:fix": "eslint src/**/*.{ts,tsx} --fix", "golden": "npm run lint && npm run test", "prepare": "cd .. && husky install", - "translate": "node prebuild.js -i18nOnly" + "translate": "node prebuild.js -i18nOnly", + "proto:generate": "npx buf generate" }, "dependencies": { + "@bufbuild/protobuf": "^2.11.0", "@emotion/react": "^11.8.2", "@emotion/styled": "^11.8.1", "@mui/icons-material": "^5.5.1", @@ -32,7 +34,6 @@ "intl-messageformat": "^10.2.1", "lodash": "^4.17.21", "prop-types": "^15.8.1", - "protobufjs": "^7.2.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-final-form": "^6.5.8", @@ -48,6 +49,8 @@ "rxjs": "^7.5.4" }, "devDependencies": { + "@bufbuild/buf": "^1.67.0", + "@bufbuild/protoc-gen-es": "^2.11.0", "@mui/types": "^7.1.3", "@testing-library/jest-dom": "^6.4.0", "@testing-library/react": "^13.4.0", diff --git a/webclient/prebuild.js b/webclient/prebuild.js index 3b420f32c..7f0a585e1 100644 --- a/webclient/prebuild.js +++ b/webclient/prebuild.js @@ -6,13 +6,10 @@ const exec = util.promisify(require('child_process').exec); const ROOT_DIR = './src'; const PUBLIC_DIR = './public'; -const protoFilesDir = `${PUBLIC_DIR}/pb`; const i18nDefaultFile = `${ROOT_DIR}/i18n-default.json`; const serverPropsFile = `${ROOT_DIR}/server-props.json`; -const masterProtoFile = `${ROOT_DIR}/proto-files.json`; const sharedFiles = [ - ['../libcockatrice_protocol/libcockatrice/protocol/pb', protoFilesDir], ['../cockatrice/resources/countries', `${ROOT_DIR}/images/countries`], ]; @@ -26,10 +23,8 @@ const i18nOnly = process.argv.indexOf('-i18nOnly') > -1; return; } - // make sure these files finish copying before master file is created await copySharedFiles(); - await createMasterProtoFile(); await createServerProps(); await createI18NDefault(); })(); @@ -43,19 +38,6 @@ async function copySharedFiles() { } } -async function createMasterProtoFile() { - try { - fse.readdir(protoFilesDir, (err, files) => { - if (err) throw err; - - fse.outputFile(masterProtoFile, JSON.stringify(files.filter(file => /\.proto$/.test(file)))); - }); - } catch (e) { - console.error(e); - process.exitCode = 1; - } -} - async function createServerProps() { try { fse.outputFile(serverPropsFile, JSON.stringify({ diff --git a/webclient/src/api/AuthenticationService.spec.ts b/webclient/src/api/AuthenticationService.spec.ts index 460c8af0f..1ba4cd252 100644 --- a/webclient/src/api/AuthenticationService.spec.ts +++ b/webclient/src/api/AuthenticationService.spec.ts @@ -8,15 +8,9 @@ vi.mock('websocket', () => ({ }, })); -vi.mock('websocket/services/ProtoController', () => ({ - ProtoController: { - root: { - ServerInfo_User: { - UserLevelFlag: { - IsModerator: 4, - }, - }, - }, +vi.mock('generated/proto/serverinfo_user_pb', () => ({ + ServerInfo_User_UserLevelFlag: { + IsModerator: 4, }, })); diff --git a/webclient/src/api/AuthenticationService.tsx b/webclient/src/api/AuthenticationService.tsx index 7b3a46988..c5128fcbd 100644 --- a/webclient/src/api/AuthenticationService.tsx +++ b/webclient/src/api/AuthenticationService.tsx @@ -1,6 +1,6 @@ import { StatusEnum, User, WebSocketConnectReason, WebSocketConnectOptions } from 'types'; import { SessionCommands, webClient } from 'websocket'; -import { ProtoController } from 'websocket/services/ProtoController'; +import { ServerInfo_User_UserLevelFlag } from 'generated/proto/serverinfo_user_pb'; export class AuthenticationService { static login(options: WebSocketConnectOptions): void { @@ -40,7 +40,7 @@ export class AuthenticationService { } static isModerator(user: User): boolean { - const moderatorLevel = ProtoController.root.ServerInfo_User.UserLevelFlag.IsModerator; + const moderatorLevel = ServerInfo_User_UserLevelFlag.IsModerator; // @TODO tell cockatrice not to do this so shittily return (user.userLevel & moderatorLevel) === moderatorLevel; } diff --git a/webclient/src/setupTests.ts b/webclient/src/setupTests.ts index bfb001b2a..5b9a2b986 100644 --- a/webclient/src/setupTests.ts +++ b/webclient/src/setupTests.ts @@ -1,10 +1,2 @@ -import protobuf from 'protobufjs'; - // ensure jest-dom is always available during testing to cut down on boilerplate import '@testing-library/jest-dom/vitest'; - -class MockProtobufRoot { - load() {} -} - -(protobuf as any).Root = MockProtobufRoot; diff --git a/webclient/src/store/common/SortUtil.spec.ts b/webclient/src/store/common/SortUtil.spec.ts index 6581b640c..e3b5a4a6d 100644 --- a/webclient/src/store/common/SortUtil.spec.ts +++ b/webclient/src/store/common/SortUtil.spec.ts @@ -118,9 +118,9 @@ describe('sortByFields', () => { describe('sortUsersByField', () => { it('sorts by userLevel DESC first, then name ASC', () => { const users = [ - { name: 'Alice', userLevel: 1, accountageSecs: 0, privlevel: 0 }, - { name: 'Bob', userLevel: 8, accountageSecs: 0, privlevel: 0 }, - { name: 'Carol', userLevel: 1, accountageSecs: 0, privlevel: 0 }, + { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }, + { name: 'Bob', userLevel: 8, accountageSecs: 0n, privlevel: '' }, + { name: 'Carol', userLevel: 1, accountageSecs: 0n, privlevel: '' }, ]; SortUtil.sortUsersByField(users as any, { field: 'name', order: SortDirection.ASC }); expect(users[0].name).toBe('Bob'); @@ -136,8 +136,8 @@ describe('sortUsersByField', () => { it('returns 0 (stable) when two users tie on both userLevel and name', () => { const users = [ - { name: 'Alice', userLevel: 1, accountageSecs: 0, privlevel: 0 }, - { name: 'Alice', userLevel: 1, accountageSecs: 0, privlevel: 0 }, + { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }, + { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }, ]; expect(() => SortUtil.sortUsersByField(users as any, { field: 'name', order: SortDirection.ASC }) diff --git a/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts b/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts index 771fda745..27bc5a391 100644 --- a/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts +++ b/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts @@ -4,7 +4,6 @@ import { Room, SortDirection, User, - UserPrivLevel, UserSortField, } from 'types'; import { Message, RoomsState } from '../rooms.interfaces'; @@ -12,8 +11,8 @@ import { Message, RoomsState } from '../rooms.interfaces'; export function makeUser(overrides: Partial = {}): User { return { name: 'TestUser', - accountageSecs: 0, - privlevel: UserPrivLevel.NONE, + accountageSecs: 0n, + privlevel: '', userLevel: 0, ...overrides, }; diff --git a/webclient/src/store/server/__mocks__/server-fixtures.ts b/webclient/src/store/server/__mocks__/server-fixtures.ts index 9a7580365..fdaa997db 100644 --- a/webclient/src/store/server/__mocks__/server-fixtures.ts +++ b/webclient/src/store/server/__mocks__/server-fixtures.ts @@ -7,7 +7,6 @@ import { SortDirection, StatusEnum, User, - UserPrivLevel, UserSortField, WebSocketConnectOptions, WarnHistoryItem, @@ -18,8 +17,8 @@ import { ServerState } from '../server.interfaces'; export function makeUser(overrides: Partial = {}): User { return { name: 'TestUser', - accountageSecs: 0, - privlevel: UserPrivLevel.NONE, + accountageSecs: 0n, + privlevel: '', userLevel: 0, ...overrides, }; diff --git a/webclient/src/types/game.ts b/webclient/src/types/game.ts index 74ad18381..f15f62118 100644 --- a/webclient/src/types/game.ts +++ b/webclient/src/types/game.ts @@ -1,11 +1,77 @@ -export interface Game { - description: string; - gameId: number; +// ── Imports from generated proto files ─────────────────────────────────────── + +import type { GameEventContext } from 'generated/proto/game_event_context_pb'; +import type { CardToMove, Command_MoveCard } from 'generated/proto/command_move_card_pb'; +import type { Command_DrawCards } from 'generated/proto/command_draw_cards_pb'; +import type { Command_RollDie } from 'generated/proto/command_roll_die_pb'; +import type { Command_Shuffle } from 'generated/proto/command_shuffle_pb'; +import type { Command_FlipCard } from 'generated/proto/command_flip_card_pb'; +import type { Command_AttachCard } from 'generated/proto/command_attach_card_pb'; +import type { Command_CreateToken } from 'generated/proto/command_create_token_pb'; +import type { Command_SetCardAttr } from 'generated/proto/command_set_card_attr_pb'; +import type { Command_SetCardCounter } from 'generated/proto/command_set_card_counter_pb'; +import type { Command_IncCardCounter } from 'generated/proto/command_inc_card_counter_pb'; +import type { Command_RevealCards } from 'generated/proto/command_reveal_cards_pb'; +import type { Command_DumpZone } from 'generated/proto/command_dump_zone_pb'; +import type { Command_ChangeZoneProperties } from 'generated/proto/command_change_zone_properties_pb'; +import type { Command_CreateArrow } from 'generated/proto/command_create_arrow_pb'; +import type { Command_DeleteArrow } from 'generated/proto/command_delete_arrow_pb'; +import type { Command_CreateCounter } from 'generated/proto/command_create_counter_pb'; +import type { Command_SetCounter } from 'generated/proto/command_set_counter_pb'; +import type { Command_IncCounter } from 'generated/proto/command_inc_counter_pb'; +import type { Command_DelCounter } from 'generated/proto/command_del_counter_pb'; +import type { Command_KickFromGame } from 'generated/proto/command_kick_from_game_pb'; +import type { Command_ReadyStart } from 'generated/proto/command_ready_start_pb'; +import type { Command_Mulligan } from 'generated/proto/command_mulligan_pb'; +import type { Command_DeckSelect } from 'generated/proto/command_deck_select_pb'; +import type { MoveCard_ToZone } from 'generated/proto/move_card_to_zone_pb'; +import type { Command_SetSideboardPlan } from 'generated/proto/command_set_sideboard_plan_pb'; +import type { Command_SetSideboardLock } from 'generated/proto/command_set_sideboard_lock_pb'; +import type { Command_SetActivePhase } from 'generated/proto/command_set_active_phase_pb'; +import type { Command_GameSay } from 'generated/proto/command_game_say_pb'; +import type { Event_GameStateChanged } from 'generated/proto/event_game_state_changed_pb'; +import type { Event_GameSay } from 'generated/proto/event_game_say_pb'; +import type { Event_MoveCard } from 'generated/proto/event_move_card_pb'; +import type { Event_FlipCard } from 'generated/proto/event_flip_card_pb'; +import type { Event_DestroyCard } from 'generated/proto/event_destroy_card_pb'; +import type { Event_AttachCard } from 'generated/proto/event_attach_card_pb'; +import type { Event_CreateToken } from 'generated/proto/event_create_token_pb'; +import type { Event_SetCardAttr } from 'generated/proto/event_set_card_attr_pb'; +import type { Event_SetCardCounter } from 'generated/proto/event_set_card_counter_pb'; +import type { Event_CreateArrow } from 'generated/proto/event_create_arrow_pb'; +import type { Event_DeleteArrow } from 'generated/proto/event_delete_arrow_pb'; +import type { Event_CreateCounter } from 'generated/proto/event_create_counter_pb'; +import type { Event_SetCounter } from 'generated/proto/event_set_counter_pb'; +import type { Event_DelCounter } from 'generated/proto/event_del_counter_pb'; +import type { Event_DrawCards } from 'generated/proto/event_draw_cards_pb'; +import type { Event_RevealCards } from 'generated/proto/event_reveal_cards_pb'; +import type { Event_Shuffle } from 'generated/proto/event_shuffle_pb'; +import type { Event_RollDie } from 'generated/proto/event_roll_die_pb'; +import type { Event_DumpZone } from 'generated/proto/event_dump_zone_pb'; +import type { Event_ChangeZoneProperties } from 'generated/proto/event_change_zone_properties_pb'; +import type { Event_SetActivePlayer } from 'generated/proto/event_set_active_player_pb'; +import type { Event_SetActivePhase } from 'generated/proto/event_set_active_phase_pb'; +import type { Event_ReverseTurn } from 'generated/proto/event_reverse_turn_pb'; +import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; +import type { color } from 'generated/proto/color_pb'; +import type { ServerInfo_CardCounter } from 'generated/proto/serverinfo_cardcounter_pb'; +import type { ServerInfo_Card } from 'generated/proto/serverinfo_card_pb'; +import type { ServerInfo_Zone } from 'generated/proto/serverinfo_zone_pb'; +import type { ServerInfo_Counter } from 'generated/proto/serverinfo_counter_pb'; +import type { ServerInfo_Arrow } from 'generated/proto/serverinfo_arrow_pb'; +import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb'; +import type { ServerInfo_Player } from 'generated/proto/serverinfo_player_pb'; + +// ── Enum re-exports from generated proto files ──────────────────────────────── + +export { CardAttribute } from 'generated/proto/card_attributes_pb'; +export { ServerInfo_Zone_ZoneType as ZoneType } from 'generated/proto/serverinfo_zone_pb'; + +// ── UI types (not proto mirrors) ────────────────────────────────────────────── + +export type Game = ServerInfo_Game & { gameType: string; - gameTypes: string[]; - roomId: number; - started: boolean; -} +}; export enum GameSortField { START_TIME = 'startTime' @@ -14,7 +80,7 @@ export enum GameSortField { export interface GameConfig { description: string; password: string; - maxPlayer: number; + maxPlayers: number; onlyBuddies: boolean; onlyRegistered: boolean; spectatorsAllowed: boolean; @@ -24,6 +90,8 @@ export interface GameConfig { gameTypeIds: number[]; joinAsJudge: boolean; joinAsSpectator: boolean; + startingLifeTotal?: number; + shareDecklistsOnLoad?: boolean; } export interface JoinGameParams { @@ -41,283 +109,52 @@ export enum LeaveGameReason { USER_DISCONNECTED = 4 } -// ── Enums ──────────────────────────────────────────────────────────────────── +// ── Type aliases for generated state mirror types ───────────────────────────── -export enum ZoneType { - PrivateZone = 0, - PublicZone = 1, - HiddenZone = 2, -} +export type Color = color; +export type CardCounterInfo = ServerInfo_CardCounter; +export type CardInfo = ServerInfo_Card; +export type ZoneInfo = ServerInfo_Zone; +export type CounterInfo = ServerInfo_Counter; +export type ArrowInfo = ServerInfo_Arrow; +export type PlayerProperties = ServerInfo_PlayerProperties; +export type PlayerInfo = ServerInfo_Player; -/** Matches CardAttribute enum in card_attributes.proto */ -export enum CardAttribute { - AttrTapped = 1, - AttrAttacking = 2, - AttrFaceDown = 3, - AttrColor = 4, - AttrPT = 5, - AttrAnnotation = 6, - AttrDoesntUntap = 7, -} +// ── Type aliases for generated event data types ─────────────────────────────── -// ── Primitive data structures (mirrors ServerInfo_* protos) ────────────────── +export type GameStateChangedData = Event_GameStateChanged; +export type GameSayData = Event_GameSay; +export type MoveCardData = Event_MoveCard; +export type FlipCardData = Event_FlipCard; +export type DestroyCardData = Event_DestroyCard; +export type AttachCardData = Event_AttachCard; +export type CreateTokenData = Event_CreateToken; +export type SetCardAttrData = Event_SetCardAttr; +export type SetCardCounterData = Event_SetCardCounter; +export type CreateArrowData = Event_CreateArrow; +export type DeleteArrowData = Event_DeleteArrow; +export type CreateCounterData = Event_CreateCounter; +export type SetCounterData = Event_SetCounter; +export type DelCounterData = Event_DelCounter; +export type DrawCardsData = Event_DrawCards; +export type RevealCardsData = Event_RevealCards; +export type ShuffleData = Event_Shuffle; +export type RollDieData = Event_RollDie; +export type DumpZoneData = Event_DumpZone; +export type ChangeZonePropertiesData = Event_ChangeZoneProperties; +export type SetActivePlayerData = Event_SetActivePlayer; +export type SetActivePhaseData = Event_SetActivePhase; +export type ReverseTurnData = Event_ReverseTurn; -export interface Color { - r: number; - g: number; - b: number; - a: number; -} +// ── GameEventContext (re-export of generated type) ──────────────────────────── -/** Mirrors ServerInfo_CardCounter */ -export interface CardCounterInfo { - id: number; - value: number; -} - -/** Mirrors ServerInfo_Card */ -export interface CardInfo { - id: number; - name: string; - x: number; - y: number; - faceDown: boolean; - tapped: boolean; - attacking: boolean; - color: string; - pt: string; - annotation: string; - destroyOnZoneChange: boolean; - doesntUntap: boolean; - counterList: CardCounterInfo[]; - attachPlayerId: number; - attachZone: string; - attachCardId: number; - providerId: string; -} - -/** Mirrors ServerInfo_Zone */ -export interface ZoneInfo { - name: string; - type: ZoneType; - withCoords: boolean; - cardCount: number; - cardList: CardInfo[]; - alwaysRevealTopCard: boolean; - alwaysLookAtTopCard: boolean; -} - -/** Mirrors ServerInfo_Counter */ -export interface CounterInfo { - id: number; - name: string; - counterColor: Color; - radius: number; - count: number; -} - -/** Mirrors ServerInfo_Arrow */ -export interface ArrowInfo { - id: number; - startPlayerId: number; - startZone: string; - startCardId: number; - targetPlayerId: number; - targetZone: string; - targetCardId: number; - arrowColor: Color; -} - -/** Mirrors ServerInfo_PlayerProperties */ -export interface PlayerProperties { - playerId: number; - userInfo: any; - spectator: boolean; - conceded: boolean; - readyStart: boolean; - deckHash: string; - pingSeconds: number; - sideboardLocked: boolean; - judge: boolean; -} - -/** Mirrors ServerInfo_Player */ -export interface PlayerInfo { - properties: PlayerProperties; - deckList: string; - zoneList: ZoneInfo[]; - counterList: CounterInfo[]; - arrowList: ArrowInfo[]; -} - -// ── Game event payload interfaces (data arriving from server events) ────────── - -export interface GameStateChangedData { - playerList: PlayerInfo[]; - gameStarted: boolean; - activePlayerId: number; - activePhase: number; - secondsElapsed: number; -} - -export interface GameSayData { - message: string; -} - -export interface MoveCardData { - cardId: number; - cardName: string; - startPlayerId: number; - startZone: string; - position: number; - targetPlayerId: number; - targetZone: string; - x: number; - y: number; - newCardId: number; - faceDown: boolean; - newCardProviderId: string; -} - -export interface FlipCardData { - zoneName: string; - cardId: number; - cardName: string; - faceDown: boolean; - cardProviderId: string; -} - -export interface DestroyCardData { - zoneName: string; - cardId: number; -} - -export interface AttachCardData { - startZone: string; - cardId: number; - targetPlayerId: number; - targetZone: string; - targetCardId: number; -} - -export interface CreateTokenData { - zoneName: string; - cardId: number; - cardName: string; - color: string; - pt: string; - annotation: string; - destroyOnZoneChange: boolean; - x: number; - y: number; - cardProviderId: string; - faceDown: boolean; -} - -export interface SetCardAttrData { - zoneName: string; - cardId: number; - attribute: CardAttribute; - attrValue: string; -} - -export interface SetCardCounterData { - zoneName: string; - cardId: number; - counterId: number; - counterValue: number; -} - -export interface CreateArrowData { - arrowInfo: ArrowInfo; -} - -export interface DeleteArrowData { - arrowId: number; -} - -export interface CreateCounterData { - counterInfo: CounterInfo; -} - -export interface SetCounterData { - counterId: number; - value: number; -} - -export interface DelCounterData { - counterId: number; -} - -export interface DrawCardsData { - number: number; - cards: CardInfo[]; -} - -export interface RevealCardsData { - zoneName: string; - cardId: number[]; - otherPlayerId: number; - cards: CardInfo[]; - grantWriteAccess: boolean; - numberOfCards: number; -} - -export interface ShuffleData { - zoneName: string; - start: number; - end: number; -} - -export interface RollDieData { - sides: number; - value: number; - values: number[]; -} - -export interface DumpZoneData { - zoneOwnerId: number; - zoneName: string; - numberCards: number; - isReversed: boolean; -} - -export interface ChangeZonePropertiesData { - zoneName: string; - alwaysRevealTopCard: boolean; - alwaysLookAtTopCard: boolean; -} - -export interface SetActivePlayerData { - activePlayerId: number; -} - -export interface SetActivePhaseData { - phase: number; -} - -export interface ReverseTurnData { - reversed: boolean; -} +export type { GameEventContext }; /** * Passed to every game event handler alongside the event payload. * Contains per-container metadata from GameEventContainer. * Not stored in Redux — transient routing metadata only. */ -export interface GameEventContext { - '.Context_ReadyStart.ext'?: {}; - '.Context_Concede.ext'?: {}; - '.Context_DeckSelect.ext'?: {}; - '.Context_UndoDraw.ext'?: {}; - '.Context_MoveCard.ext'?: {}; - '.Context_Mulligan.ext'?: {}; - '.Context_PingChanged.ext'?: {}; - '.Context_ConnectionStateChanged.ext'?: {}; - '.Context_SetSideboardLock.ext'?: {}; - '.Context_Unconcede.ext'?: {}; -} - export interface GameEventMeta { gameId: number; playerId: number; @@ -328,186 +165,34 @@ export interface GameEventMeta { forcedByJudge: number; } -// ── Command parameter interfaces ───────────────────────────────────────────── +// ── Type aliases for generated command param types ──────────────────────────── -export interface CardToMove { - cardId: number; - faceDown?: boolean; - pt?: string; - tapped?: boolean; -} - -export interface MoveCardParams { - startPlayerId: number; - startZone: string; - cardsToMove: { card: CardToMove[] }; - targetPlayerId: number; - targetZone: string; - x?: number; - y?: number; - isReversed?: boolean; -} - -export interface DrawCardsParams { - number: number; -} - -export interface RollDieParams { - sides: number; - count?: number; -} - -export interface ShuffleParams { - zoneName: string; - start?: number; - end?: number; -} - -export interface FlipCardParams { - zone: string; - cardId: number; - faceDown: boolean; - pt?: string; -} - -export interface AttachCardParams { - startZone: string; - cardId: number; - targetPlayerId?: number; - targetZone?: string; - targetCardId?: number; -} - -export interface CreateTokenParams { - zone: string; - cardName: string; - color?: string; - pt?: string; - annotation?: string; - destroyOnZoneChange?: boolean; - x?: number; - y?: number; - targetZone?: string; - targetCardId?: number; - targetMode?: number; - cardProviderId?: string; - faceDown?: boolean; -} - -export interface SetCardAttrParams { - zone: string; - cardId: number; - attribute: CardAttribute; - attrValue: string; -} - -export interface SetCardCounterParams { - zone: string; - cardId: number; - counterId: number; - counterValue: number; -} - -export interface IncCardCounterParams { - zone: string; - cardId: number; - counterId: number; - counterDelta: number; -} - -export interface RevealCardsParams { - zoneName: string; - cardId?: number[]; - playerId?: number; - grantWriteAccess?: boolean; - topCards?: number; -} - -export interface DumpZoneParams { - playerId: number; - zoneName: string; - numberCards: number; - isReversed?: boolean; -} - -export interface ChangeZonePropertiesParams { - zoneName: string; - alwaysRevealTopCard?: boolean; - alwaysLookAtTopCard?: boolean; -} - -export interface CreateArrowParams { - startPlayerId: number; - startZone: string; - startCardId: number; - targetPlayerId: number; - targetZone?: string; - targetCardId?: number; - arrowColor: Color; - deleteInPhase?: number; -} - -export interface DeleteArrowParams { - arrowId: number; -} - -export interface CreateCounterParams { - counterName: string; - counterColor: Color; - radius: number; - value: number; -} - -export interface SetCounterParams { - counterId: number; - value: number; -} - -export interface IncCounterParams { - counterId: number; - delta: number; -} - -export interface DelCounterParams { - counterId: number; -} - -export interface KickFromGameParams { - playerId: number; -} - -export interface ReadyStartParams { - ready: boolean; - forceStart?: boolean; -} - -export interface MulliganParams { - number: number; -} - -export interface DeckSelectParams { - deck?: string; - deckId?: number; -} - -export interface MoveCardToZone { - cardName: string; - startZone: string; - targetZone: string; -} - -export interface SetSideboardPlanParams { - moveList: MoveCardToZone[]; -} - -export interface SetSideboardLockParams { - locked: boolean; -} - -export interface SetActivePhaseParams { - phase: number; -} - -export interface GameSayParams { - message: string; -} +export type { CardToMove }; +export type MoveCardParams = Command_MoveCard; +export type DrawCardsParams = Command_DrawCards; +export type RollDieParams = Command_RollDie; +export type ShuffleParams = Command_Shuffle; +export type FlipCardParams = Command_FlipCard; +export type AttachCardParams = Command_AttachCard; +export type CreateTokenParams = Command_CreateToken; +export type SetCardAttrParams = Command_SetCardAttr; +export type SetCardCounterParams = Command_SetCardCounter; +export type IncCardCounterParams = Command_IncCardCounter; +export type RevealCardsParams = Command_RevealCards; +export type DumpZoneParams = Command_DumpZone; +export type ChangeZonePropertiesParams = Command_ChangeZoneProperties; +export type CreateArrowParams = Command_CreateArrow; +export type DeleteArrowParams = Command_DeleteArrow; +export type CreateCounterParams = Command_CreateCounter; +export type SetCounterParams = Command_SetCounter; +export type IncCounterParams = Command_IncCounter; +export type DelCounterParams = Command_DelCounter; +export type KickFromGameParams = Command_KickFromGame; +export type ReadyStartParams = Command_ReadyStart; +export type MulliganParams = Command_Mulligan; +export type DeckSelectParams = Command_DeckSelect; +export type MoveCardToZone = MoveCard_ToZone; +export type SetSideboardPlanParams = Command_SetSideboardPlan; +export type SetSideboardLockParams = Command_SetSideboardLock; +export type SetActivePhaseParams = Command_SetActivePhase; +export type GameSayParams = Command_GameSay; diff --git a/webclient/src/types/message.ts b/webclient/src/types/message.ts index d7f256011..d4dc282a1 100644 --- a/webclient/src/types/message.ts +++ b/webclient/src/types/message.ts @@ -1,7 +1,5 @@ -export interface Message { - name: string; - message: string; - messageType: number; - timeOf: number; +import type { Event_RoomSay } from 'generated/proto/event_room_say_pb'; + +export type Message = Event_RoomSay & { timeReceived: number; -} +}; diff --git a/webclient/src/types/replay.ts b/webclient/src/types/replay.ts index dfa78538a..5f2e19217 100644 --- a/webclient/src/types/replay.ts +++ b/webclient/src/types/replay.ts @@ -1,16 +1,5 @@ -export interface Replay { - replayId: number; - replayName: string; - duration: number; -} +import type { ServerInfo_Replay } from 'generated/proto/serverinfo_replay_pb'; +import type { ServerInfo_ReplayMatch } from 'generated/proto/serverinfo_replay_match_pb'; -export interface ReplayMatch { - replayList: Replay[]; - gameId: number; - roomName: string; - timeStarted: number; - length: number; - gameName: string; - playerNames: string[]; - doNotHide: boolean; -} +export type Replay = ServerInfo_Replay; +export type ReplayMatch = ServerInfo_ReplayMatch; diff --git a/webclient/src/types/room.ts b/webclient/src/types/room.ts index e69b1a499..9f18d2d4c 100644 --- a/webclient/src/types/room.ts +++ b/webclient/src/types/room.ts @@ -1,23 +1,8 @@ -import { User } from './user'; - -export interface Room { - autoJoin: boolean - description: string; - gameCount: number; - gameList: any[]; - gametypeList: any[]; - gametypeMap: GametypeMap; - name: string; - permissionlevel: RoomAccessLevel; - playerCount: number; - privilegelevel: RoomAccessLevel; - roomId: number; - userList: User[]; - order: number; -} +import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; export interface GametypeMap { [index: number]: string } -export enum RoomAccessLevel { - 'none' -} +export type Room = ServerInfo_Room & { + gametypeMap: GametypeMap; + order: number; +}; diff --git a/webclient/src/types/session.ts b/webclient/src/types/session.ts index 1b49616c5..a7a615c4a 100644 --- a/webclient/src/types/session.ts +++ b/webclient/src/types/session.ts @@ -1,7 +1 @@ -export enum NotificationType { - UNKNOWN = 0, - PROMOTED = 1, - WARNING = 2, - IDLEWARNING = 3, - CUSTOM = 4, -}; +export { Event_NotifyUser_NotificationType as NotificationType } from 'generated/proto/event_notify_user_pb'; diff --git a/webclient/src/types/user.ts b/webclient/src/types/user.ts index 2d0383eb7..f3db25849 100644 --- a/webclient/src/types/user.ts +++ b/webclient/src/types/user.ts @@ -1,27 +1,7 @@ -export interface User { - accountageSecs: number; - name: string; - privlevel: UserPrivLevel; - userLevel: number; - realName?: string; - country?: string; - avatarBmp?: Uint8Array; -} +import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; -export enum UserLevelFlag { - IsNothing = 0, - IsUser = 1, - IsRegistered = 2, - IsModerator = 4, - IsAdmin = 8, - IsJudge = 16, -} - -export enum UserPrivLevel { - NONE = 0, - VIP = 1, - DONOR = 2 -} +export type User = ServerInfo_User; +export { ServerInfo_User_UserLevelFlag as UserLevelFlag } from 'generated/proto/serverinfo_user_pb'; export enum UserSortField { NAME = 'name' diff --git a/webclient/src/websocket/WebClient.spec.ts b/webclient/src/websocket/WebClient.spec.ts index 20cac3bcb..b279132ea 100644 --- a/webclient/src/websocket/WebClient.spec.ts +++ b/webclient/src/websocket/WebClient.spec.ts @@ -17,7 +17,7 @@ vi.mock('./services/ProtobufService', () => ({ vi.mock('./persistence', () => ({ RoomPersistence: { clearStore: vi.fn() }, - SessionPersistence: { clearStore: vi.fn() }, + SessionPersistence: { clearStore: vi.fn(), initialized: vi.fn() }, })); import { WebClient } from './WebClient'; @@ -60,6 +60,10 @@ describe('WebClient', () => { messageSubject.next(event); expect(client.protobuf.handleMessageEvent).toHaveBeenCalledWith(event); }); + + it('calls SessionPersistence.initialized', () => { + expect(SessionPersistence.initialized).toHaveBeenCalled(); + }); }); describe('connect', () => { diff --git a/webclient/src/websocket/WebClient.ts b/webclient/src/websocket/WebClient.ts index 378b5fd62..e1a0fad56 100644 --- a/webclient/src/websocket/WebClient.ts +++ b/webclient/src/websocket/WebClient.ts @@ -47,6 +47,8 @@ export class WebClient { this.protobuf.handleMessageEvent(message); }); + SessionPersistence.initialized(); + if (import.meta.env.MODE !== 'test') { console.log(this); } diff --git a/webclient/src/websocket/__mocks__/callbackHelpers.ts b/webclient/src/websocket/__mocks__/callbackHelpers.ts index 7f7d37ecc..0aaa61184 100644 --- a/webclient/src/websocket/__mocks__/callbackHelpers.ts +++ b/webclient/src/websocket/__mocks__/callbackHelpers.ts @@ -4,8 +4,8 @@ * @param mockFn - The vi.Mock for the BackendService send method * (e.g. BackendService.sendSessionCommand as vi.Mock). * @param optsArgIndex - Index of the options argument in the mock call. - * Defaults to 2 (commandName, params, options). - * Use 3 for sendRoomCommand (roomId, commandName, params, options). + * Defaults to 2 (ext, value, options). + * Use 3 for sendRoomCommand (roomId, ext, value, options). */ export function makeCallbackHelpers(mockFn: vi.Mock, optsArgIndex = 2) { function getLastSendOpts() { diff --git a/webclient/src/websocket/__mocks__/helpers.ts b/webclient/src/websocket/__mocks__/helpers.ts index dc1da0a73..c9d6d9973 100644 --- a/webclient/src/websocket/__mocks__/helpers.ts +++ b/webclient/src/websocket/__mocks__/helpers.ts @@ -1,54 +1,9 @@ /** * Shared mock factories for websocket layer unit tests. * Import the helpers you need in each spec file via: - * import { makeMockProtoRoot, makeMockWebSocket } from '../__mocks__/helpers'; + * import { makeMockWebSocket } from '../__mocks__/helpers'; */ -/** Builds a minimal mock of ProtoController.root */ -export function makeMockProtoRoot() { - const encode = { finish: vi.fn().mockReturnValue(new Uint8Array()) }; - return { - CommandContainer: { - create: vi.fn(args => ({ ...args })), - encode: vi.fn().mockReturnValue(encode), - }, - SessionCommand: { create: vi.fn(args => ({ ...args })) }, - RoomCommand: { create: vi.fn(args => ({ ...args })) }, - ModeratorCommand: { create: vi.fn(args => ({ ...args })) }, - AdminCommand: { create: vi.fn(args => ({ ...args })) }, - ServerMessage: { - decode: vi.fn(), - MessageType: { - RESPONSE: 'RESPONSE', - ROOM_EVENT: 'ROOM_EVENT', - SESSION_EVENT: 'SESSION_EVENT', - GAME_EVENT_CONTAINER: 'GAME_EVENT_CONTAINER', - }, - }, - Response: { - ResponseCode: { - RespOk: 0, - RespRegistrationRequired: 1, - }, - }, - Event_ServerIdentification: { - ServerOptions: { SupportsPasswordHash: 2 }, - }, - Event_ConnectionClosed: { - CloseReason: { - USER_LIMIT_REACHED: 1, - TOO_MANY_CONNECTIONS: 2, - BANNED: 3, - DEMOTED: 4, - SERVER_SHUTDOWN: 5, - USERNAMEINVALID: 6, - LOGGEDINELSEWERE: 7, - OTHER: 8, - }, - }, - }; -} - /** Builds a mock WebSocket instance */ export function makeMockWebSocketInstance() { return { diff --git a/webclient/src/websocket/__mocks__/sessionCommandMocks.ts b/webclient/src/websocket/__mocks__/sessionCommandMocks.ts index d5bea3a7f..cbab4833c 100644 --- a/webclient/src/websocket/__mocks__/sessionCommandMocks.ts +++ b/webclient/src/websocket/__mocks__/sessionCommandMocks.ts @@ -25,38 +25,6 @@ export function makeWebClientMock() { }; } -/** Superset ProtoController.root mock — includes all ResponseCode values and Event_ServerIdentification. */ -export function makeProtoControllerRootMock() { - return { - Response: { - ResponseCode: { - RespOk: 0, - RespClientUpdateRequired: 1, - RespWrongPassword: 2, - RespUsernameInvalid: 3, - RespWouldOverwriteOldSession: 4, - RespUserIsBanned: 5, - RespRegistrationRequired: 6, - RespClientIdRequired: 7, - RespContextError: 8, - RespAccountNotActivated: 9, - RespRegistrationAccepted: 10, - RespRegistrationAcceptedNeedsActivation: 11, - RespUserAlreadyExists: 12, - RespPasswordTooShort: 13, - RespEmailRequiredToRegister: 14, - RespEmailBlackListed: 15, - RespTooManyRequests: 16, - RespRegistrationDisabled: 17, - RespActivationAccepted: 18, - }, - }, - Event_ServerIdentification: { - ServerOptions: { SupportsPasswordHash: 2 }, - }, - }; -} - /** Utils mock with unified return values. */ export function makeUtilsMock() { return { diff --git a/webclient/src/websocket/commands/admin/adjustMod.ts b/webclient/src/websocket/commands/admin/adjustMod.ts index fd71fece1..7b61e322a 100644 --- a/webclient/src/websocket/commands/admin/adjustMod.ts +++ b/webclient/src/websocket/commands/admin/adjustMod.ts @@ -1,8 +1,10 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_AdjustMod_ext, Command_AdjustModSchema } from 'generated/proto/admin_commands_pb'; import { AdminPersistence } from '../../persistence'; export function adjustMod(userName: string, shouldBeMod?: boolean, shouldBeJudge?: boolean): void { - BackendService.sendAdminCommand('Command_AdjustMod', { userName, shouldBeMod, shouldBeJudge }, { + BackendService.sendAdminCommand(Command_AdjustMod_ext, create(Command_AdjustModSchema, { userName, shouldBeMod, shouldBeJudge }), { onSuccess: () => { AdminPersistence.adjustMod(userName, shouldBeMod, shouldBeJudge); }, diff --git a/webclient/src/websocket/commands/admin/adminCommands.spec.ts b/webclient/src/websocket/commands/admin/adminCommands.spec.ts index be0124736..098db2597 100644 --- a/webclient/src/websocket/commands/admin/adminCommands.spec.ts +++ b/webclient/src/websocket/commands/admin/adminCommands.spec.ts @@ -22,7 +22,8 @@ import { shutdownServer } from './shutdownServer'; import { updateServerMessage } from './updateServerMessage'; const { getLastSendOpts, invokeOnSuccess } = makeCallbackHelpers( - BackendService.sendAdminCommand as vi.Mock + BackendService.sendAdminCommand as vi.Mock, + 2 ); beforeEach(() => vi.clearAllMocks()); @@ -34,11 +35,7 @@ describe('adjustMod', () => { it('calls sendAdminCommand with Command_AdjustMod', () => { adjustMod('alice', true, false); - expect(BackendService.sendAdminCommand).toHaveBeenCalledWith( - 'Command_AdjustMod', - expect.objectContaining({ userName: 'alice', shouldBeMod: true, shouldBeJudge: false }), - expect.any(Object) - ); + expect(BackendService.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); }); it('onSuccess calls AdminPersistence.adjustMod', () => { @@ -55,7 +52,7 @@ describe('reloadConfig', () => { it('calls sendAdminCommand with Command_ReloadConfig', () => { reloadConfig(); - expect(BackendService.sendAdminCommand).toHaveBeenCalledWith('Command_ReloadConfig', {}, expect.any(Object)); + expect(BackendService.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); }); it('onSuccess calls AdminPersistence.reloadConfig', () => { @@ -72,11 +69,7 @@ describe('shutdownServer', () => { it('calls sendAdminCommand with Command_ShutdownServer', () => { shutdownServer('maintenance', 10); - expect(BackendService.sendAdminCommand).toHaveBeenCalledWith( - 'Command_ShutdownServer', - { reason: 'maintenance', minutes: 10 }, - expect.any(Object) - ); + expect(BackendService.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); }); it('onSuccess calls AdminPersistence.shutdownServer', () => { @@ -93,7 +86,7 @@ describe('updateServerMessage', () => { it('calls sendAdminCommand with Command_UpdateServerMessage', () => { updateServerMessage(); - expect(BackendService.sendAdminCommand).toHaveBeenCalledWith('Command_UpdateServerMessage', {}, expect.any(Object)); + expect(BackendService.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); }); it('onSuccess calls AdminPersistence.updateServerMessage', () => { diff --git a/webclient/src/websocket/commands/admin/reloadConfig.ts b/webclient/src/websocket/commands/admin/reloadConfig.ts index 979f3ec73..e72735c60 100644 --- a/webclient/src/websocket/commands/admin/reloadConfig.ts +++ b/webclient/src/websocket/commands/admin/reloadConfig.ts @@ -1,8 +1,10 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_ReloadConfig_ext, Command_ReloadConfigSchema } from 'generated/proto/admin_commands_pb'; import { AdminPersistence } from '../../persistence'; export function reloadConfig(): void { - BackendService.sendAdminCommand('Command_ReloadConfig', {}, { + BackendService.sendAdminCommand(Command_ReloadConfig_ext, create(Command_ReloadConfigSchema), { onSuccess: () => { AdminPersistence.reloadConfig(); }, diff --git a/webclient/src/websocket/commands/admin/shutdownServer.ts b/webclient/src/websocket/commands/admin/shutdownServer.ts index e65c900db..7fc32fb24 100644 --- a/webclient/src/websocket/commands/admin/shutdownServer.ts +++ b/webclient/src/websocket/commands/admin/shutdownServer.ts @@ -1,8 +1,10 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_ShutdownServer_ext, Command_ShutdownServerSchema } from 'generated/proto/admin_commands_pb'; import { AdminPersistence } from '../../persistence'; export function shutdownServer(reason: string, minutes: number): void { - BackendService.sendAdminCommand('Command_ShutdownServer', { reason, minutes }, { + BackendService.sendAdminCommand(Command_ShutdownServer_ext, create(Command_ShutdownServerSchema, { reason, minutes }), { onSuccess: () => { AdminPersistence.shutdownServer(); }, diff --git a/webclient/src/websocket/commands/admin/updateServerMessage.ts b/webclient/src/websocket/commands/admin/updateServerMessage.ts index e2b194514..d81b63028 100644 --- a/webclient/src/websocket/commands/admin/updateServerMessage.ts +++ b/webclient/src/websocket/commands/admin/updateServerMessage.ts @@ -1,8 +1,10 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_UpdateServerMessage_ext, Command_UpdateServerMessageSchema } from 'generated/proto/admin_commands_pb'; import { AdminPersistence } from '../../persistence'; export function updateServerMessage(): void { - BackendService.sendAdminCommand('Command_UpdateServerMessage', {}, { + BackendService.sendAdminCommand(Command_UpdateServerMessage_ext, create(Command_UpdateServerMessageSchema), { onSuccess: () => { AdminPersistence.updateServerMessage(); }, diff --git a/webclient/src/websocket/commands/game/attachCard.ts b/webclient/src/websocket/commands/game/attachCard.ts index bc9ebce75..08162f651 100644 --- a/webclient/src/websocket/commands/game/attachCard.ts +++ b/webclient/src/websocket/commands/game/attachCard.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_AttachCardSchema, Command_AttachCard_ext } from 'generated/proto/command_attach_card_pb'; import { AttachCardParams } from 'types'; export function attachCard(gameId: number, params: AttachCardParams): void { - BackendService.sendGameCommand(gameId, 'Command_AttachCard', params); + BackendService.sendGameCommand(gameId, Command_AttachCard_ext, create(Command_AttachCardSchema, params)); } diff --git a/webclient/src/websocket/commands/game/changeZoneProperties.ts b/webclient/src/websocket/commands/game/changeZoneProperties.ts index 77167cc72..7ee1feaca 100644 --- a/webclient/src/websocket/commands/game/changeZoneProperties.ts +++ b/webclient/src/websocket/commands/game/changeZoneProperties.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_ChangeZonePropertiesSchema, Command_ChangeZoneProperties_ext } from 'generated/proto/command_change_zone_properties_pb'; import { ChangeZonePropertiesParams } from 'types'; export function changeZoneProperties(gameId: number, params: ChangeZonePropertiesParams): void { - BackendService.sendGameCommand(gameId, 'Command_ChangeZoneProperties', params); + BackendService.sendGameCommand(gameId, Command_ChangeZoneProperties_ext, create(Command_ChangeZonePropertiesSchema, params)); } diff --git a/webclient/src/websocket/commands/game/concede.ts b/webclient/src/websocket/commands/game/concede.ts index fd587464b..50e9c6d29 100644 --- a/webclient/src/websocket/commands/game/concede.ts +++ b/webclient/src/websocket/commands/game/concede.ts @@ -1,5 +1,7 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_ConcedeSchema, Command_Concede_ext } from 'generated/proto/command_concede_pb'; export function concede(gameId: number): void { - BackendService.sendGameCommand(gameId, 'Command_Concede', {}); + BackendService.sendGameCommand(gameId, Command_Concede_ext, create(Command_ConcedeSchema)); } diff --git a/webclient/src/websocket/commands/game/createArrow.ts b/webclient/src/websocket/commands/game/createArrow.ts index 7b3a8a294..0a641585b 100644 --- a/webclient/src/websocket/commands/game/createArrow.ts +++ b/webclient/src/websocket/commands/game/createArrow.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_CreateArrowSchema, Command_CreateArrow_ext } from 'generated/proto/command_create_arrow_pb'; import { CreateArrowParams } from 'types'; export function createArrow(gameId: number, params: CreateArrowParams): void { - BackendService.sendGameCommand(gameId, 'Command_CreateArrow', params); + BackendService.sendGameCommand(gameId, Command_CreateArrow_ext, create(Command_CreateArrowSchema, params)); } diff --git a/webclient/src/websocket/commands/game/createCounter.ts b/webclient/src/websocket/commands/game/createCounter.ts index 562ac06b9..1bbf99478 100644 --- a/webclient/src/websocket/commands/game/createCounter.ts +++ b/webclient/src/websocket/commands/game/createCounter.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_CreateCounterSchema, Command_CreateCounter_ext } from 'generated/proto/command_create_counter_pb'; import { CreateCounterParams } from 'types'; export function createCounter(gameId: number, params: CreateCounterParams): void { - BackendService.sendGameCommand(gameId, 'Command_CreateCounter', params); + BackendService.sendGameCommand(gameId, Command_CreateCounter_ext, create(Command_CreateCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/createToken.ts b/webclient/src/websocket/commands/game/createToken.ts index 16e170e70..4b554e688 100644 --- a/webclient/src/websocket/commands/game/createToken.ts +++ b/webclient/src/websocket/commands/game/createToken.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_CreateTokenSchema, Command_CreateToken_ext } from 'generated/proto/command_create_token_pb'; import { CreateTokenParams } from 'types'; export function createToken(gameId: number, params: CreateTokenParams): void { - BackendService.sendGameCommand(gameId, 'Command_CreateToken', params); + BackendService.sendGameCommand(gameId, Command_CreateToken_ext, create(Command_CreateTokenSchema, params)); } diff --git a/webclient/src/websocket/commands/game/deckSelect.ts b/webclient/src/websocket/commands/game/deckSelect.ts index 077cee580..60a874150 100644 --- a/webclient/src/websocket/commands/game/deckSelect.ts +++ b/webclient/src/websocket/commands/game/deckSelect.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_DeckSelectSchema, Command_DeckSelect_ext } from 'generated/proto/command_deck_select_pb'; import { DeckSelectParams } from 'types'; export function deckSelect(gameId: number, params: DeckSelectParams): void { - BackendService.sendGameCommand(gameId, 'Command_DeckSelect', params); + BackendService.sendGameCommand(gameId, Command_DeckSelect_ext, create(Command_DeckSelectSchema, params)); } diff --git a/webclient/src/websocket/commands/game/delCounter.ts b/webclient/src/websocket/commands/game/delCounter.ts index ade08ef21..b84fb5a0f 100644 --- a/webclient/src/websocket/commands/game/delCounter.ts +++ b/webclient/src/websocket/commands/game/delCounter.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_DelCounterSchema, Command_DelCounter_ext } from 'generated/proto/command_del_counter_pb'; import { DelCounterParams } from 'types'; export function delCounter(gameId: number, params: DelCounterParams): void { - BackendService.sendGameCommand(gameId, 'Command_DelCounter', params); + BackendService.sendGameCommand(gameId, Command_DelCounter_ext, create(Command_DelCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/deleteArrow.ts b/webclient/src/websocket/commands/game/deleteArrow.ts index fceef8a95..67929e2a2 100644 --- a/webclient/src/websocket/commands/game/deleteArrow.ts +++ b/webclient/src/websocket/commands/game/deleteArrow.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_DeleteArrowSchema, Command_DeleteArrow_ext } from 'generated/proto/command_delete_arrow_pb'; import { DeleteArrowParams } from 'types'; export function deleteArrow(gameId: number, params: DeleteArrowParams): void { - BackendService.sendGameCommand(gameId, 'Command_DeleteArrow', params); + BackendService.sendGameCommand(gameId, Command_DeleteArrow_ext, create(Command_DeleteArrowSchema, params)); } diff --git a/webclient/src/websocket/commands/game/drawCards.ts b/webclient/src/websocket/commands/game/drawCards.ts index ae8e80744..ad5a08771 100644 --- a/webclient/src/websocket/commands/game/drawCards.ts +++ b/webclient/src/websocket/commands/game/drawCards.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_DrawCardsSchema, Command_DrawCards_ext } from 'generated/proto/command_draw_cards_pb'; import { DrawCardsParams } from 'types'; export function drawCards(gameId: number, params: DrawCardsParams): void { - BackendService.sendGameCommand(gameId, 'Command_DrawCards', params); + BackendService.sendGameCommand(gameId, Command_DrawCards_ext, create(Command_DrawCardsSchema, params)); } diff --git a/webclient/src/websocket/commands/game/dumpZone.ts b/webclient/src/websocket/commands/game/dumpZone.ts index 18aec44f6..9075b2dcf 100644 --- a/webclient/src/websocket/commands/game/dumpZone.ts +++ b/webclient/src/websocket/commands/game/dumpZone.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_DumpZoneSchema, Command_DumpZone_ext } from 'generated/proto/command_dump_zone_pb'; import { DumpZoneParams } from 'types'; export function dumpZone(gameId: number, params: DumpZoneParams): void { - BackendService.sendGameCommand(gameId, 'Command_DumpZone', params); + BackendService.sendGameCommand(gameId, Command_DumpZone_ext, create(Command_DumpZoneSchema, params)); } diff --git a/webclient/src/websocket/commands/game/flipCard.ts b/webclient/src/websocket/commands/game/flipCard.ts index 907f1e6a4..c12729d46 100644 --- a/webclient/src/websocket/commands/game/flipCard.ts +++ b/webclient/src/websocket/commands/game/flipCard.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_FlipCardSchema, Command_FlipCard_ext } from 'generated/proto/command_flip_card_pb'; import { FlipCardParams } from 'types'; export function flipCard(gameId: number, params: FlipCardParams): void { - BackendService.sendGameCommand(gameId, 'Command_FlipCard', params); + BackendService.sendGameCommand(gameId, Command_FlipCard_ext, create(Command_FlipCardSchema, params)); } diff --git a/webclient/src/websocket/commands/game/gameCommands.spec.ts b/webclient/src/websocket/commands/game/gameCommands.spec.ts index 023b5da75..5ac94fc9b 100644 --- a/webclient/src/websocket/commands/game/gameCommands.spec.ts +++ b/webclient/src/websocket/commands/game/gameCommands.spec.ts @@ -1,4 +1,37 @@ import { BackendService } from '../../services/BackendService'; +import { create, setExtension } from '@bufbuild/protobuf'; +import { GameCommandSchema, Command_Judge_ext } from 'generated/proto/game_commands_pb'; +import { Command_DrawCardsSchema, Command_DrawCards_ext } from 'generated/proto/command_draw_cards_pb'; +import { Command_AttachCard_ext } from 'generated/proto/command_attach_card_pb'; +import { Command_ChangeZoneProperties_ext } from 'generated/proto/command_change_zone_properties_pb'; +import { Command_Concede_ext, Command_Unconcede_ext } from 'generated/proto/command_concede_pb'; +import { Command_CreateArrow_ext } from 'generated/proto/command_create_arrow_pb'; +import { Command_CreateCounter_ext } from 'generated/proto/command_create_counter_pb'; +import { Command_CreateToken_ext } from 'generated/proto/command_create_token_pb'; +import { Command_DeckSelect_ext } from 'generated/proto/command_deck_select_pb'; +import { Command_DelCounter_ext } from 'generated/proto/command_del_counter_pb'; +import { Command_DeleteArrow_ext } from 'generated/proto/command_delete_arrow_pb'; +import { Command_DumpZone_ext } from 'generated/proto/command_dump_zone_pb'; +import { Command_FlipCard_ext } from 'generated/proto/command_flip_card_pb'; +import { Command_GameSay_ext } from 'generated/proto/command_game_say_pb'; +import { Command_IncCardCounter_ext } from 'generated/proto/command_inc_card_counter_pb'; +import { Command_IncCounter_ext } from 'generated/proto/command_inc_counter_pb'; +import { Command_KickFromGame_ext } from 'generated/proto/command_kick_from_game_pb'; +import { Command_LeaveGame_ext } from 'generated/proto/command_leave_game_pb'; +import { Command_MoveCard_ext } from 'generated/proto/command_move_card_pb'; +import { Command_Mulligan_ext } from 'generated/proto/command_mulligan_pb'; +import { Command_NextTurn_ext } from 'generated/proto/command_next_turn_pb'; +import { Command_ReadyStart_ext } from 'generated/proto/command_ready_start_pb'; +import { Command_RevealCards_ext } from 'generated/proto/command_reveal_cards_pb'; +import { Command_ReverseTurn_ext } from 'generated/proto/command_reverse_turn_pb'; +import { Command_SetActivePhase_ext } from 'generated/proto/command_set_active_phase_pb'; +import { Command_SetCardAttr_ext } from 'generated/proto/command_set_card_attr_pb'; +import { Command_SetCardCounter_ext } from 'generated/proto/command_set_card_counter_pb'; +import { Command_SetCounter_ext } from 'generated/proto/command_set_counter_pb'; +import { Command_SetSideboardLock_ext } from 'generated/proto/command_set_sideboard_lock_pb'; +import { Command_SetSideboardPlan_ext } from 'generated/proto/command_set_sideboard_plan_pb'; +import { Command_Shuffle_ext } from 'generated/proto/command_shuffle_pb'; +import { Command_UndoDraw_ext } from 'generated/proto/command_undo_draw_pb'; import { attachCard } from './attachCard'; import { changeZoneProperties } from './changeZoneProperties'; import { concede } from './concede'; @@ -38,7 +71,6 @@ vi.mock('../../services/BackendService', () => ({ })); const gameId = 1; -const params = {} as any; beforeEach(() => { (BackendService.sendGameCommand as vi.Mock).mockClear(); @@ -46,172 +78,208 @@ beforeEach(() => { describe('Game commands — delegate to BackendService.sendGameCommand', () => { it('attachCard sends Command_AttachCard', () => { - attachCard(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_AttachCard', params); + attachCard(gameId, { cardId: 10, startZone: 'hand' }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_AttachCard_ext, expect.objectContaining({ cardId: 10, startZone: 'hand' }) + ); }); it('changeZoneProperties sends Command_ChangeZoneProperties', () => { - changeZoneProperties(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_ChangeZoneProperties', params); + changeZoneProperties(gameId, { zoneName: 'side' }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_ChangeZoneProperties_ext, expect.objectContaining({ zoneName: 'side' }) + ); }); it('concede sends Command_Concede with empty object', () => { concede(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_Concede', {}); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Concede_ext, expect.any(Object)); }); it('createArrow sends Command_CreateArrow', () => { - createArrow(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_CreateArrow', params); + createArrow(gameId, { startPlayerId: 1, startZone: 'hand' }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_CreateArrow_ext, expect.objectContaining({ startPlayerId: 1, startZone: 'hand' }) + ); }); it('createCounter sends Command_CreateCounter', () => { - createCounter(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_CreateCounter', params); + createCounter(gameId, { counterName: 'life' }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_CreateCounter_ext, expect.objectContaining({ counterName: 'life' }) + ); }); it('createToken sends Command_CreateToken', () => { - createToken(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_CreateToken', params); + createToken(gameId, { cardName: 'Goblin', zone: 'play' }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_CreateToken_ext, expect.objectContaining({ cardName: 'Goblin', zone: 'play' }) + ); }); it('deckSelect sends Command_DeckSelect', () => { - deckSelect(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_DeckSelect', params); + deckSelect(gameId, { deckId: 5 }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DeckSelect_ext, expect.objectContaining({ deckId: 5 })); }); it('delCounter sends Command_DelCounter', () => { - delCounter(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_DelCounter', params); + delCounter(gameId, { counterId: 3 }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DelCounter_ext, expect.objectContaining({ counterId: 3 })); }); it('deleteArrow sends Command_DeleteArrow', () => { - deleteArrow(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_DeleteArrow', params); + deleteArrow(gameId, { arrowId: 2 }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DeleteArrow_ext, expect.objectContaining({ arrowId: 2 })); }); it('drawCards sends Command_DrawCards', () => { - drawCards(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_DrawCards', params); + drawCards(gameId, { number: 3 }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DrawCards_ext, expect.objectContaining({ number: 3 })); }); it('dumpZone sends Command_DumpZone', () => { - dumpZone(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_DumpZone', params); + dumpZone(gameId, { playerId: 2, zoneName: 'library' }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_DumpZone_ext, expect.objectContaining({ playerId: 2, zoneName: 'library' }) + ); }); it('flipCard sends Command_FlipCard', () => { - flipCard(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_FlipCard', params); + flipCard(gameId, { cardId: 7, faceDown: false }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_FlipCard_ext, expect.objectContaining({ cardId: 7, faceDown: false }) + ); }); it('gameSay sends Command_GameSay', () => { - gameSay(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_GameSay', params); + gameSay(gameId, { message: 'hello' }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_GameSay_ext, expect.objectContaining({ message: 'hello' })); }); it('incCardCounter sends Command_IncCardCounter', () => { - incCardCounter(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_IncCardCounter', params); + incCardCounter(gameId, { cardId: 5, counterId: 1 }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_IncCardCounter_ext, expect.objectContaining({ cardId: 5, counterId: 1 }) + ); }); it('incCounter sends Command_IncCounter', () => { - incCounter(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_IncCounter', params); + incCounter(gameId, { counterId: 1, delta: 5 }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_IncCounter_ext, expect.objectContaining({ counterId: 1, delta: 5 }) + ); }); it('kickFromGame sends Command_KickFromGame', () => { - kickFromGame(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_KickFromGame', params); + kickFromGame(gameId, { playerId: 2 }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_KickFromGame_ext, expect.objectContaining({ playerId: 2 })); }); it('leaveGame sends Command_LeaveGame with empty object', () => { leaveGame(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_LeaveGame', {}); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_LeaveGame_ext, expect.any(Object)); }); it('moveCard sends Command_MoveCard', () => { - moveCard(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_MoveCard', params); + moveCard(gameId, { startZone: 'hand', targetZone: 'graveyard' }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_MoveCard_ext, + expect.objectContaining({ startZone: 'hand', targetZone: 'graveyard' }) + ); }); it('mulligan sends Command_Mulligan', () => { - mulligan(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_Mulligan', params); + mulligan(gameId, { number: 7 }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Mulligan_ext, expect.objectContaining({ number: 7 })); }); it('nextTurn sends Command_NextTurn with empty object', () => { nextTurn(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_NextTurn', {}); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_NextTurn_ext, expect.any(Object)); }); it('readyStart sends Command_ReadyStart', () => { - readyStart(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_ReadyStart', params); + readyStart(gameId, { ready: true }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_ReadyStart_ext, expect.objectContaining({ ready: true })); }); it('revealCards sends Command_RevealCards', () => { - revealCards(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_RevealCards', params); + revealCards(gameId, { zoneName: 'hand', cardId: [1, 2] }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_RevealCards_ext, expect.objectContaining({ zoneName: 'hand', cardId: [1, 2] }) + ); }); it('reverseTurn sends Command_ReverseTurn with empty object', () => { reverseTurn(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_ReverseTurn', {}); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_ReverseTurn_ext, expect.any(Object)); }); it('setActivePhase sends Command_SetActivePhase', () => { - setActivePhase(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_SetActivePhase', params); + setActivePhase(gameId, { phase: 2 }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_SetActivePhase_ext, expect.objectContaining({ phase: 2 })); }); it('setCardAttr sends Command_SetCardAttr', () => { - setCardAttr(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_SetCardAttr', params); + setCardAttr(gameId, { zone: 'play', cardId: 5, attrValue: '2' }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_SetCardAttr_ext, + expect.objectContaining({ zone: 'play', cardId: 5, attrValue: '2' }) + ); }); it('setCardCounter sends Command_SetCardCounter', () => { - setCardCounter(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_SetCardCounter', params); + setCardCounter(gameId, { cardId: 5, counterId: 1 }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_SetCardCounter_ext, expect.objectContaining({ cardId: 5, counterId: 1 }) + ); }); it('setCounter sends Command_SetCounter', () => { - setCounter(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_SetCounter', params); + setCounter(gameId, { counterId: 1, value: 10 }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_SetCounter_ext, expect.objectContaining({ counterId: 1, value: 10 }) + ); }); it('setSideboardLock sends Command_SetSideboardLock', () => { - setSideboardLock(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_SetSideboardLock', params); + setSideboardLock(gameId, { locked: true }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_SetSideboardLock_ext, expect.objectContaining({ locked: true }) + ); }); it('setSideboardPlan sends Command_SetSideboardPlan', () => { - setSideboardPlan(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_SetSideboardPlan', params); + setSideboardPlan(gameId, { moveList: [] }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_SetSideboardPlan_ext, expect.objectContaining({ moveList: expect.any(Array) }) + ); }); it('shuffle sends Command_Shuffle', () => { - shuffle(gameId, params); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_Shuffle', params); + shuffle(gameId, { zoneName: 'hand' }); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Shuffle_ext, expect.objectContaining({ zoneName: 'hand' })); }); it('undoDraw sends Command_UndoDraw with empty object', () => { undoDraw(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_UndoDraw', {}); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_UndoDraw_ext, expect.any(Object)); }); it('unconcede sends Command_Unconcede with empty object', () => { unconcede(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_Unconcede', {}); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Unconcede_ext, expect.any(Object)); }); it('judge sends Command_Judge with targetId and wrapped gameCommand array', () => { const targetId = 3; - const innerGameCommand = { '.Command_DrawCards.ext': { numberOfCards: 2 } }; - judge(gameId, targetId, innerGameCommand); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, 'Command_Judge', { - targetId, - gameCommand: [innerGameCommand], - }); + const innerCmd = create(GameCommandSchema); + setExtension(innerCmd, Command_DrawCards_ext, create(Command_DrawCardsSchema, { number: 2 })); + judge(gameId, targetId, innerCmd); + expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + gameId, + Command_Judge_ext, + expect.objectContaining({ targetId: 3, gameCommand: expect.any(Array) }) + ); }); }); diff --git a/webclient/src/websocket/commands/game/gameSay.ts b/webclient/src/websocket/commands/game/gameSay.ts index 2f574b52e..a55085526 100644 --- a/webclient/src/websocket/commands/game/gameSay.ts +++ b/webclient/src/websocket/commands/game/gameSay.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_GameSaySchema, Command_GameSay_ext } from 'generated/proto/command_game_say_pb'; import { GameSayParams } from 'types'; export function gameSay(gameId: number, params: GameSayParams): void { - BackendService.sendGameCommand(gameId, 'Command_GameSay', params); + BackendService.sendGameCommand(gameId, Command_GameSay_ext, create(Command_GameSaySchema, params)); } diff --git a/webclient/src/websocket/commands/game/incCardCounter.ts b/webclient/src/websocket/commands/game/incCardCounter.ts index e2fda829a..dd678044f 100644 --- a/webclient/src/websocket/commands/game/incCardCounter.ts +++ b/webclient/src/websocket/commands/game/incCardCounter.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_IncCardCounterSchema, Command_IncCardCounter_ext } from 'generated/proto/command_inc_card_counter_pb'; import { IncCardCounterParams } from 'types'; export function incCardCounter(gameId: number, params: IncCardCounterParams): void { - BackendService.sendGameCommand(gameId, 'Command_IncCardCounter', params); + BackendService.sendGameCommand(gameId, Command_IncCardCounter_ext, create(Command_IncCardCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/incCounter.ts b/webclient/src/websocket/commands/game/incCounter.ts index a24173679..997fc1303 100644 --- a/webclient/src/websocket/commands/game/incCounter.ts +++ b/webclient/src/websocket/commands/game/incCounter.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_IncCounterSchema, Command_IncCounter_ext } from 'generated/proto/command_inc_counter_pb'; import { IncCounterParams } from 'types'; export function incCounter(gameId: number, params: IncCounterParams): void { - BackendService.sendGameCommand(gameId, 'Command_IncCounter', params); + BackendService.sendGameCommand(gameId, Command_IncCounter_ext, create(Command_IncCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/judge.ts b/webclient/src/websocket/commands/game/judge.ts index 1b9f22d14..57ad869eb 100644 --- a/webclient/src/websocket/commands/game/judge.ts +++ b/webclient/src/websocket/commands/game/judge.ts @@ -1,8 +1,12 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_JudgeSchema, Command_Judge_ext } from 'generated/proto/game_commands_pb'; +import type { GameCommand } from 'generated/proto/game_commands_pb'; -export function judge(gameId: number, targetId: number, innerGameCommand: any): void { - BackendService.sendGameCommand(gameId, 'Command_Judge', { +export function judge(gameId: number, targetId: number, innerGameCommand: GameCommand): void { + BackendService.sendGameCommand(gameId, Command_Judge_ext, create(Command_JudgeSchema, { targetId, gameCommand: [innerGameCommand], - }); + })); } + diff --git a/webclient/src/websocket/commands/game/kickFromGame.ts b/webclient/src/websocket/commands/game/kickFromGame.ts index c14aab980..e56574e41 100644 --- a/webclient/src/websocket/commands/game/kickFromGame.ts +++ b/webclient/src/websocket/commands/game/kickFromGame.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_KickFromGameSchema, Command_KickFromGame_ext } from 'generated/proto/command_kick_from_game_pb'; import { KickFromGameParams } from 'types'; export function kickFromGame(gameId: number, params: KickFromGameParams): void { - BackendService.sendGameCommand(gameId, 'Command_KickFromGame', params); + BackendService.sendGameCommand(gameId, Command_KickFromGame_ext, create(Command_KickFromGameSchema, params)); } diff --git a/webclient/src/websocket/commands/game/leaveGame.ts b/webclient/src/websocket/commands/game/leaveGame.ts index 48c8dcc2a..6bdcfc661 100644 --- a/webclient/src/websocket/commands/game/leaveGame.ts +++ b/webclient/src/websocket/commands/game/leaveGame.ts @@ -1,5 +1,7 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_LeaveGameSchema, Command_LeaveGame_ext } from 'generated/proto/command_leave_game_pb'; export function leaveGame(gameId: number): void { - BackendService.sendGameCommand(gameId, 'Command_LeaveGame', {}); + BackendService.sendGameCommand(gameId, Command_LeaveGame_ext, create(Command_LeaveGameSchema)); } diff --git a/webclient/src/websocket/commands/game/moveCard.ts b/webclient/src/websocket/commands/game/moveCard.ts index aebc97bbd..193d85b93 100644 --- a/webclient/src/websocket/commands/game/moveCard.ts +++ b/webclient/src/websocket/commands/game/moveCard.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_MoveCardSchema, Command_MoveCard_ext } from 'generated/proto/command_move_card_pb'; import { MoveCardParams } from 'types'; export function moveCard(gameId: number, params: MoveCardParams): void { - BackendService.sendGameCommand(gameId, 'Command_MoveCard', params); + BackendService.sendGameCommand(gameId, Command_MoveCard_ext, create(Command_MoveCardSchema, params)); } diff --git a/webclient/src/websocket/commands/game/mulligan.ts b/webclient/src/websocket/commands/game/mulligan.ts index 9871acf27..54c42635e 100644 --- a/webclient/src/websocket/commands/game/mulligan.ts +++ b/webclient/src/websocket/commands/game/mulligan.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_MulliganSchema, Command_Mulligan_ext } from 'generated/proto/command_mulligan_pb'; import { MulliganParams } from 'types'; export function mulligan(gameId: number, params: MulliganParams): void { - BackendService.sendGameCommand(gameId, 'Command_Mulligan', params); + BackendService.sendGameCommand(gameId, Command_Mulligan_ext, create(Command_MulliganSchema, params)); } diff --git a/webclient/src/websocket/commands/game/nextTurn.ts b/webclient/src/websocket/commands/game/nextTurn.ts index 696002256..756cca49a 100644 --- a/webclient/src/websocket/commands/game/nextTurn.ts +++ b/webclient/src/websocket/commands/game/nextTurn.ts @@ -1,5 +1,7 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_NextTurnSchema, Command_NextTurn_ext } from 'generated/proto/command_next_turn_pb'; export function nextTurn(gameId: number): void { - BackendService.sendGameCommand(gameId, 'Command_NextTurn', {}); + BackendService.sendGameCommand(gameId, Command_NextTurn_ext, create(Command_NextTurnSchema)); } diff --git a/webclient/src/websocket/commands/game/readyStart.ts b/webclient/src/websocket/commands/game/readyStart.ts index 50e818914..d6a608d9c 100644 --- a/webclient/src/websocket/commands/game/readyStart.ts +++ b/webclient/src/websocket/commands/game/readyStart.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_ReadyStartSchema, Command_ReadyStart_ext } from 'generated/proto/command_ready_start_pb'; import { ReadyStartParams } from 'types'; export function readyStart(gameId: number, params: ReadyStartParams): void { - BackendService.sendGameCommand(gameId, 'Command_ReadyStart', params); + BackendService.sendGameCommand(gameId, Command_ReadyStart_ext, create(Command_ReadyStartSchema, params)); } diff --git a/webclient/src/websocket/commands/game/revealCards.ts b/webclient/src/websocket/commands/game/revealCards.ts index 567970db5..18e054613 100644 --- a/webclient/src/websocket/commands/game/revealCards.ts +++ b/webclient/src/websocket/commands/game/revealCards.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_RevealCardsSchema, Command_RevealCards_ext } from 'generated/proto/command_reveal_cards_pb'; import { RevealCardsParams } from 'types'; export function revealCards(gameId: number, params: RevealCardsParams): void { - BackendService.sendGameCommand(gameId, 'Command_RevealCards', params); + BackendService.sendGameCommand(gameId, Command_RevealCards_ext, create(Command_RevealCardsSchema, params)); } diff --git a/webclient/src/websocket/commands/game/reverseTurn.ts b/webclient/src/websocket/commands/game/reverseTurn.ts index 27662ea7e..38d34600a 100644 --- a/webclient/src/websocket/commands/game/reverseTurn.ts +++ b/webclient/src/websocket/commands/game/reverseTurn.ts @@ -1,5 +1,7 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_ReverseTurnSchema, Command_ReverseTurn_ext } from 'generated/proto/command_reverse_turn_pb'; export function reverseTurn(gameId: number): void { - BackendService.sendGameCommand(gameId, 'Command_ReverseTurn', {}); + BackendService.sendGameCommand(gameId, Command_ReverseTurn_ext, create(Command_ReverseTurnSchema)); } diff --git a/webclient/src/websocket/commands/game/setActivePhase.ts b/webclient/src/websocket/commands/game/setActivePhase.ts index 664815f16..799a700f3 100644 --- a/webclient/src/websocket/commands/game/setActivePhase.ts +++ b/webclient/src/websocket/commands/game/setActivePhase.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_SetActivePhaseSchema, Command_SetActivePhase_ext } from 'generated/proto/command_set_active_phase_pb'; import { SetActivePhaseParams } from 'types'; export function setActivePhase(gameId: number, params: SetActivePhaseParams): void { - BackendService.sendGameCommand(gameId, 'Command_SetActivePhase', params); + BackendService.sendGameCommand(gameId, Command_SetActivePhase_ext, create(Command_SetActivePhaseSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setCardAttr.ts b/webclient/src/websocket/commands/game/setCardAttr.ts index 3d4418aa7..f1b2bbd56 100644 --- a/webclient/src/websocket/commands/game/setCardAttr.ts +++ b/webclient/src/websocket/commands/game/setCardAttr.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_SetCardAttrSchema, Command_SetCardAttr_ext } from 'generated/proto/command_set_card_attr_pb'; import { SetCardAttrParams } from 'types'; export function setCardAttr(gameId: number, params: SetCardAttrParams): void { - BackendService.sendGameCommand(gameId, 'Command_SetCardAttr', params); + BackendService.sendGameCommand(gameId, Command_SetCardAttr_ext, create(Command_SetCardAttrSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setCardCounter.ts b/webclient/src/websocket/commands/game/setCardCounter.ts index 87e1940d2..2aea270f8 100644 --- a/webclient/src/websocket/commands/game/setCardCounter.ts +++ b/webclient/src/websocket/commands/game/setCardCounter.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_SetCardCounterSchema, Command_SetCardCounter_ext } from 'generated/proto/command_set_card_counter_pb'; import { SetCardCounterParams } from 'types'; export function setCardCounter(gameId: number, params: SetCardCounterParams): void { - BackendService.sendGameCommand(gameId, 'Command_SetCardCounter', params); + BackendService.sendGameCommand(gameId, Command_SetCardCounter_ext, create(Command_SetCardCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setCounter.ts b/webclient/src/websocket/commands/game/setCounter.ts index a850d5111..fdbb90bf9 100644 --- a/webclient/src/websocket/commands/game/setCounter.ts +++ b/webclient/src/websocket/commands/game/setCounter.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_SetCounterSchema, Command_SetCounter_ext } from 'generated/proto/command_set_counter_pb'; import { SetCounterParams } from 'types'; export function setCounter(gameId: number, params: SetCounterParams): void { - BackendService.sendGameCommand(gameId, 'Command_SetCounter', params); + BackendService.sendGameCommand(gameId, Command_SetCounter_ext, create(Command_SetCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setSideboardLock.ts b/webclient/src/websocket/commands/game/setSideboardLock.ts index 00df8c763..a8b82dff9 100644 --- a/webclient/src/websocket/commands/game/setSideboardLock.ts +++ b/webclient/src/websocket/commands/game/setSideboardLock.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_SetSideboardLockSchema, Command_SetSideboardLock_ext } from 'generated/proto/command_set_sideboard_lock_pb'; import { SetSideboardLockParams } from 'types'; export function setSideboardLock(gameId: number, params: SetSideboardLockParams): void { - BackendService.sendGameCommand(gameId, 'Command_SetSideboardLock', params); + BackendService.sendGameCommand(gameId, Command_SetSideboardLock_ext, create(Command_SetSideboardLockSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setSideboardPlan.ts b/webclient/src/websocket/commands/game/setSideboardPlan.ts index ad7278738..65cac24e6 100644 --- a/webclient/src/websocket/commands/game/setSideboardPlan.ts +++ b/webclient/src/websocket/commands/game/setSideboardPlan.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_SetSideboardPlanSchema, Command_SetSideboardPlan_ext } from 'generated/proto/command_set_sideboard_plan_pb'; import { SetSideboardPlanParams } from 'types'; export function setSideboardPlan(gameId: number, params: SetSideboardPlanParams): void { - BackendService.sendGameCommand(gameId, 'Command_SetSideboardPlan', params); + BackendService.sendGameCommand(gameId, Command_SetSideboardPlan_ext, create(Command_SetSideboardPlanSchema, params)); } diff --git a/webclient/src/websocket/commands/game/shuffle.ts b/webclient/src/websocket/commands/game/shuffle.ts index 777d03fd6..750926d3b 100644 --- a/webclient/src/websocket/commands/game/shuffle.ts +++ b/webclient/src/websocket/commands/game/shuffle.ts @@ -1,6 +1,8 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_ShuffleSchema, Command_Shuffle_ext } from 'generated/proto/command_shuffle_pb'; import { ShuffleParams } from 'types'; export function shuffle(gameId: number, params: ShuffleParams): void { - BackendService.sendGameCommand(gameId, 'Command_Shuffle', params); + BackendService.sendGameCommand(gameId, Command_Shuffle_ext, create(Command_ShuffleSchema, params)); } diff --git a/webclient/src/websocket/commands/game/unconcede.ts b/webclient/src/websocket/commands/game/unconcede.ts index b724aee03..82cc0d63f 100644 --- a/webclient/src/websocket/commands/game/unconcede.ts +++ b/webclient/src/websocket/commands/game/unconcede.ts @@ -1,5 +1,7 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_UnconcedeSchema, Command_Unconcede_ext } from 'generated/proto/command_concede_pb'; export function unconcede(gameId: number): void { - BackendService.sendGameCommand(gameId, 'Command_Unconcede', {}); + BackendService.sendGameCommand(gameId, Command_Unconcede_ext, create(Command_UnconcedeSchema)); } diff --git a/webclient/src/websocket/commands/game/undoDraw.ts b/webclient/src/websocket/commands/game/undoDraw.ts index 28d47c093..9f70df3b8 100644 --- a/webclient/src/websocket/commands/game/undoDraw.ts +++ b/webclient/src/websocket/commands/game/undoDraw.ts @@ -1,5 +1,7 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_UndoDrawSchema, Command_UndoDraw_ext } from 'generated/proto/command_undo_draw_pb'; export function undoDraw(gameId: number): void { - BackendService.sendGameCommand(gameId, 'Command_UndoDraw', {}); + BackendService.sendGameCommand(gameId, Command_UndoDraw_ext, create(Command_UndoDrawSchema)); } diff --git a/webclient/src/websocket/commands/moderator/banFromServer.ts b/webclient/src/websocket/commands/moderator/banFromServer.ts index e45e34504..9fba4e72b 100644 --- a/webclient/src/websocket/commands/moderator/banFromServer.ts +++ b/webclient/src/websocket/commands/moderator/banFromServer.ts @@ -1,11 +1,13 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_BanFromServer_ext, Command_BanFromServerSchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; export function banFromServer(minutes: number, userName?: string, address?: string, reason?: string, visibleReason?: string, clientid?: string, removeMessages?: number): void { - BackendService.sendModeratorCommand('Command_BanFromServer', { + BackendService.sendModeratorCommand(Command_BanFromServer_ext, create(Command_BanFromServerSchema, { minutes, userName, address, reason, visibleReason, clientid, removeMessages - }, { + }), { onSuccess: () => { ModeratorPersistence.banFromServer(userName); }, diff --git a/webclient/src/websocket/commands/moderator/forceActivateUser.ts b/webclient/src/websocket/commands/moderator/forceActivateUser.ts index d4138a015..43dd1fc65 100644 --- a/webclient/src/websocket/commands/moderator/forceActivateUser.ts +++ b/webclient/src/websocket/commands/moderator/forceActivateUser.ts @@ -1,8 +1,13 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { + Command_ForceActivateUser_ext, Command_ForceActivateUserSchema, +} from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; export function forceActivateUser(usernameToActivate: string, moderatorName: string): void { - BackendService.sendModeratorCommand('Command_ForceActivateUser', { usernameToActivate, moderatorName }, { + const cmd = create(Command_ForceActivateUserSchema, { usernameToActivate, moderatorName }); + BackendService.sendModeratorCommand(Command_ForceActivateUser_ext, cmd, { onSuccess: () => { ModeratorPersistence.forceActivateUser(usernameToActivate, moderatorName); }, diff --git a/webclient/src/websocket/commands/moderator/getAdminNotes.ts b/webclient/src/websocket/commands/moderator/getAdminNotes.ts index d4f626aa2..48b374581 100644 --- a/webclient/src/websocket/commands/moderator/getAdminNotes.ts +++ b/webclient/src/websocket/commands/moderator/getAdminNotes.ts @@ -1,9 +1,12 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_GetAdminNotes_ext, Command_GetAdminNotesSchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; +import { Response_GetAdminNotes_ext } from 'generated/proto/response_get_admin_notes_pb'; export function getAdminNotes(userName: string): void { - BackendService.sendModeratorCommand('Command_GetAdminNotes', { userName }, { - responseName: 'Response_GetAdminNotes', + BackendService.sendModeratorCommand(Command_GetAdminNotes_ext, create(Command_GetAdminNotesSchema, { userName }), { + responseExt: Response_GetAdminNotes_ext, onSuccess: (response) => { ModeratorPersistence.getAdminNotes(userName, response.notes); }, diff --git a/webclient/src/websocket/commands/moderator/getBanHistory.ts b/webclient/src/websocket/commands/moderator/getBanHistory.ts index dd4e90eda..a6a084912 100644 --- a/webclient/src/websocket/commands/moderator/getBanHistory.ts +++ b/webclient/src/websocket/commands/moderator/getBanHistory.ts @@ -1,9 +1,12 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_GetBanHistory_ext, Command_GetBanHistorySchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; +import { Response_BanHistory_ext } from 'generated/proto/response_ban_history_pb'; export function getBanHistory(userName: string): void { - BackendService.sendModeratorCommand('Command_GetBanHistory', { userName }, { - responseName: 'Response_BanHistory', + BackendService.sendModeratorCommand(Command_GetBanHistory_ext, create(Command_GetBanHistorySchema, { userName }), { + responseExt: Response_BanHistory_ext, onSuccess: (response) => { ModeratorPersistence.banHistory(userName, response.banList); }, diff --git a/webclient/src/websocket/commands/moderator/getWarnHistory.ts b/webclient/src/websocket/commands/moderator/getWarnHistory.ts index c47e2c6e4..15b03595e 100644 --- a/webclient/src/websocket/commands/moderator/getWarnHistory.ts +++ b/webclient/src/websocket/commands/moderator/getWarnHistory.ts @@ -1,9 +1,12 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_GetWarnHistory_ext, Command_GetWarnHistorySchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; +import { Response_WarnHistory_ext } from 'generated/proto/response_warn_history_pb'; export function getWarnHistory(userName: string): void { - BackendService.sendModeratorCommand('Command_GetWarnHistory', { userName }, { - responseName: 'Response_WarnHistory', + BackendService.sendModeratorCommand(Command_GetWarnHistory_ext, create(Command_GetWarnHistorySchema, { userName }), { + responseExt: Response_WarnHistory_ext, onSuccess: (response) => { ModeratorPersistence.warnHistory(userName, response.warnList); }, diff --git a/webclient/src/websocket/commands/moderator/getWarnList.ts b/webclient/src/websocket/commands/moderator/getWarnList.ts index 412aee09e..3f7e178ed 100644 --- a/webclient/src/websocket/commands/moderator/getWarnList.ts +++ b/webclient/src/websocket/commands/moderator/getWarnList.ts @@ -1,9 +1,12 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_GetWarnList_ext, Command_GetWarnListSchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; +import { Response_WarnList_ext } from 'generated/proto/response_warn_list_pb'; export function getWarnList(modName: string, userName: string, userClientid: string): void { - BackendService.sendModeratorCommand('Command_GetWarnList', { modName, userName, userClientid }, { - responseName: 'Response_WarnList', + BackendService.sendModeratorCommand(Command_GetWarnList_ext, create(Command_GetWarnListSchema, { modName, userName, userClientid }), { + responseExt: Response_WarnList_ext, onSuccess: (response) => { ModeratorPersistence.warnListOptions(response.warning); }, diff --git a/webclient/src/websocket/commands/moderator/grantReplayAccess.ts b/webclient/src/websocket/commands/moderator/grantReplayAccess.ts index 74d64d17a..84adc725e 100644 --- a/webclient/src/websocket/commands/moderator/grantReplayAccess.ts +++ b/webclient/src/websocket/commands/moderator/grantReplayAccess.ts @@ -1,8 +1,12 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { + Command_GrantReplayAccess_ext, Command_GrantReplayAccessSchema, +} from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; export function grantReplayAccess(replayId: number, moderatorName: string): void { - BackendService.sendModeratorCommand('Command_GrantReplayAccess', { replayId, moderatorName }, { + BackendService.sendModeratorCommand(Command_GrantReplayAccess_ext, create(Command_GrantReplayAccessSchema, { replayId, moderatorName }), { onSuccess: () => { ModeratorPersistence.grantReplayAccess(replayId, moderatorName); }, diff --git a/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts b/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts index 47dedbfaa..576983942 100644 --- a/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts +++ b/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts @@ -22,6 +22,23 @@ vi.mock('../../persistence', () => ({ import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; import { BackendService } from '../../services/BackendService'; import { ModeratorPersistence } from '../../persistence'; +import { + Command_BanFromServer_ext, + Command_ForceActivateUser_ext, + Command_GetAdminNotes_ext, + Command_GetBanHistory_ext, + Command_GetWarnHistory_ext, + Command_GetWarnList_ext, + Command_GrantReplayAccess_ext, + Command_UpdateAdminNotes_ext, + Command_ViewLogHistory_ext, + Command_WarnUser_ext, +} from 'generated/proto/moderator_commands_pb'; +import { Response_GetAdminNotes_ext } from 'generated/proto/response_get_admin_notes_pb'; +import { Response_BanHistory_ext } from 'generated/proto/response_ban_history_pb'; +import { Response_WarnHistory_ext } from 'generated/proto/response_warn_history_pb'; +import { Response_WarnList_ext } from 'generated/proto/response_warn_list_pb'; +import { Response_ViewLogHistory_ext } from 'generated/proto/response_viewlog_history_pb'; import { banFromServer } from './banFromServer'; import { forceActivateUser } from './forceActivateUser'; import { getAdminNotes } from './getAdminNotes'; @@ -34,7 +51,8 @@ import { viewLogHistory } from './viewLogHistory'; import { warnUser } from './warnUser'; const { getLastSendOpts, invokeOnSuccess } = makeCallbackHelpers( - BackendService.sendModeratorCommand as vi.Mock + BackendService.sendModeratorCommand as vi.Mock, + 2 ); beforeEach(() => vi.clearAllMocks()); @@ -47,7 +65,7 @@ describe('banFromServer', () => { it('calls sendModeratorCommand with Command_BanFromServer', () => { banFromServer(30, 'alice', '1.2.3.4', 'reason', 'visible', 'cid', 1); expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( - 'Command_BanFromServer', + Command_BanFromServer_ext, expect.objectContaining({ minutes: 30, userName: 'alice' }), expect.any(Object) ); @@ -67,7 +85,7 @@ describe('forceActivateUser', () => { it('calls sendModeratorCommand with Command_ForceActivateUser', () => { forceActivateUser('alice', 'mod1'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith('Command_ForceActivateUser', expect.any(Object), expect.any(Object)); + expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith(Command_ForceActivateUser_ext, expect.any(Object), expect.any(Object)); }); it('onSuccess calls ModeratorPersistence.forceActivateUser', () => { @@ -85,16 +103,16 @@ describe('getAdminNotes', () => { it('calls sendModeratorCommand with Command_GetAdminNotes', () => { getAdminNotes('alice'); expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( - 'Command_GetAdminNotes', + Command_GetAdminNotes_ext, expect.any(Object), - expect.objectContaining({ responseName: 'Response_GetAdminNotes' }) + expect.objectContaining({ responseExt: Response_GetAdminNotes_ext }) ); }); it('onSuccess calls ModeratorPersistence.getAdminNotes with notes', () => { getAdminNotes('alice'); const resp = { notes: 'some notes' }; - invokeOnSuccess(resp, { responseCode: 0, '.Response_GetAdminNotes.ext': resp }); + invokeOnSuccess(resp, { responseCode: 0 }); expect(ModeratorPersistence.getAdminNotes).toHaveBeenCalledWith('alice', 'some notes'); }); }); @@ -107,16 +125,16 @@ describe('getBanHistory', () => { it('calls sendModeratorCommand with Command_GetBanHistory', () => { getBanHistory('alice'); expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( - 'Command_GetBanHistory', + Command_GetBanHistory_ext, expect.any(Object), - expect.objectContaining({ responseName: 'Response_BanHistory' }) + expect.objectContaining({ responseExt: Response_BanHistory_ext }) ); }); it('onSuccess calls ModeratorPersistence.banHistory with banList', () => { getBanHistory('alice'); const resp = { banList: [{ id: 1 }] }; - invokeOnSuccess(resp, { responseCode: 0, '.Response_BanHistory.ext': resp }); + invokeOnSuccess(resp, { responseCode: 0 }); expect(ModeratorPersistence.banHistory).toHaveBeenCalledWith('alice', [{ id: 1 }]); }); }); @@ -129,16 +147,16 @@ describe('getWarnHistory', () => { it('calls sendModeratorCommand with Command_GetWarnHistory', () => { getWarnHistory('alice'); expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( - 'Command_GetWarnHistory', + Command_GetWarnHistory_ext, expect.any(Object), - expect.objectContaining({ responseName: 'Response_WarnHistory' }) + expect.objectContaining({ responseExt: Response_WarnHistory_ext }) ); }); it('onSuccess calls ModeratorPersistence.warnHistory with warnList', () => { getWarnHistory('alice'); const resp = { warnList: [{ id: 2 }] }; - invokeOnSuccess(resp, { responseCode: 0, '.Response_WarnHistory.ext': resp }); + invokeOnSuccess(resp, { responseCode: 0 }); expect(ModeratorPersistence.warnHistory).toHaveBeenCalledWith('alice', [{ id: 2 }]); }); }); @@ -151,16 +169,16 @@ describe('getWarnList', () => { it('calls sendModeratorCommand with Command_GetWarnList', () => { getWarnList('mod1', 'alice', 'US'); expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( - 'Command_GetWarnList', + Command_GetWarnList_ext, expect.any(Object), - expect.objectContaining({ responseName: 'Response_WarnList' }) + expect.objectContaining({ responseExt: Response_WarnList_ext }) ); }); it('onSuccess calls ModeratorPersistence.warnListOptions with warning', () => { getWarnList('mod1', 'alice', 'US'); const resp = { warning: ['w1', 'w2'] }; - invokeOnSuccess(resp, { responseCode: 0, '.Response_WarnList.ext': resp }); + invokeOnSuccess(resp, { responseCode: 0 }); expect(ModeratorPersistence.warnListOptions).toHaveBeenCalledWith(['w1', 'w2']); }); }); @@ -172,7 +190,7 @@ describe('grantReplayAccess', () => { it('calls sendModeratorCommand with Command_GrantReplayAccess', () => { grantReplayAccess(10, 'mod1'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith('Command_GrantReplayAccess', expect.any(Object), expect.any(Object)); + expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith(Command_GrantReplayAccess_ext, expect.any(Object), expect.any(Object)); }); it('onSuccess calls ModeratorPersistence.grantReplayAccess', () => { @@ -189,7 +207,7 @@ describe('updateAdminNotes', () => { it('calls sendModeratorCommand with Command_UpdateAdminNotes', () => { updateAdminNotes('alice', 'new notes'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith('Command_UpdateAdminNotes', expect.any(Object), expect.any(Object)); + expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith(Command_UpdateAdminNotes_ext, expect.any(Object), expect.any(Object)); }); it('onSuccess calls ModeratorPersistence.updateAdminNotes', () => { @@ -205,18 +223,18 @@ describe('updateAdminNotes', () => { describe('viewLogHistory', () => { it('calls sendModeratorCommand with Command_ViewLogHistory', () => { - viewLogHistory({ filters: 'all' } as any); + viewLogHistory({ dateRange: 7 } as any); expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( - 'Command_ViewLogHistory', + Command_ViewLogHistory_ext, expect.any(Object), - expect.objectContaining({ responseName: 'Response_ViewLogHistory' }) + expect.objectContaining({ responseExt: Response_ViewLogHistory_ext }) ); }); it('onSuccess calls ModeratorPersistence.viewLogs with logMessage', () => { - viewLogHistory({ filters: 'all' } as any); + viewLogHistory({ dateRange: 7 } as any); const resp = { logMessage: ['log1'] }; - invokeOnSuccess(resp, { responseCode: 0, '.Response_ViewLogHistory.ext': resp }); + invokeOnSuccess(resp, { responseCode: 0 }); expect(ModeratorPersistence.viewLogs).toHaveBeenCalledWith(['log1']); }); }); @@ -228,7 +246,7 @@ describe('warnUser', () => { it('calls sendModeratorCommand with Command_WarnUser', () => { warnUser('alice', 'bad behavior', 'cid'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith('Command_WarnUser', expect.any(Object), expect.any(Object)); + expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith(Command_WarnUser_ext, expect.any(Object), expect.any(Object)); }); it('onSuccess calls ModeratorPersistence.warnUser', () => { diff --git a/webclient/src/websocket/commands/moderator/updateAdminNotes.ts b/webclient/src/websocket/commands/moderator/updateAdminNotes.ts index c7ac315c5..6f50e71aa 100644 --- a/webclient/src/websocket/commands/moderator/updateAdminNotes.ts +++ b/webclient/src/websocket/commands/moderator/updateAdminNotes.ts @@ -1,8 +1,12 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { + Command_UpdateAdminNotes_ext, Command_UpdateAdminNotesSchema, +} from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; export function updateAdminNotes(userName: string, notes: string): void { - BackendService.sendModeratorCommand('Command_UpdateAdminNotes', { userName, notes }, { + BackendService.sendModeratorCommand(Command_UpdateAdminNotes_ext, create(Command_UpdateAdminNotesSchema, { userName, notes }), { onSuccess: () => { ModeratorPersistence.updateAdminNotes(userName, notes); }, diff --git a/webclient/src/websocket/commands/moderator/viewLogHistory.ts b/webclient/src/websocket/commands/moderator/viewLogHistory.ts index 19a930608..82d101f99 100644 --- a/webclient/src/websocket/commands/moderator/viewLogHistory.ts +++ b/webclient/src/websocket/commands/moderator/viewLogHistory.ts @@ -1,10 +1,13 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_ViewLogHistory_ext, Command_ViewLogHistorySchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; +import { Response_ViewLogHistory_ext } from 'generated/proto/response_viewlog_history_pb'; import { LogFilters } from 'types'; export function viewLogHistory(filters: LogFilters): void { - BackendService.sendModeratorCommand('Command_ViewLogHistory', filters, { - responseName: 'Response_ViewLogHistory', + BackendService.sendModeratorCommand(Command_ViewLogHistory_ext, create(Command_ViewLogHistorySchema, filters), { + responseExt: Response_ViewLogHistory_ext, onSuccess: (response) => { ModeratorPersistence.viewLogs(response.logMessage); }, diff --git a/webclient/src/websocket/commands/moderator/warnUser.ts b/webclient/src/websocket/commands/moderator/warnUser.ts index 0e0271d4b..20cd7b565 100644 --- a/webclient/src/websocket/commands/moderator/warnUser.ts +++ b/webclient/src/websocket/commands/moderator/warnUser.ts @@ -1,8 +1,11 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_WarnUser_ext, Command_WarnUserSchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; export function warnUser(userName: string, reason: string, clientid?: string, removeMessages?: number): void { - BackendService.sendModeratorCommand('Command_WarnUser', { userName, reason, clientid, removeMessages }, { + const cmd = create(Command_WarnUserSchema, { userName, reason, clientid, removeMessages }); + BackendService.sendModeratorCommand(Command_WarnUser_ext, cmd, { onSuccess: () => { ModeratorPersistence.warnUser(userName); }, diff --git a/webclient/src/websocket/commands/room/createGame.ts b/webclient/src/websocket/commands/room/createGame.ts index 62565e0e6..c49691657 100644 --- a/webclient/src/websocket/commands/room/createGame.ts +++ b/webclient/src/websocket/commands/room/createGame.ts @@ -1,9 +1,11 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_CreateGame_ext, Command_CreateGameSchema } from 'generated/proto/room_commands_pb'; import { RoomPersistence } from '../../persistence'; import { GameConfig } from 'types'; export function createGame(roomId: number, gameConfig: GameConfig): void { - BackendService.sendRoomCommand(roomId, 'Command_CreateGame', gameConfig, { + BackendService.sendRoomCommand(roomId, Command_CreateGame_ext, create(Command_CreateGameSchema, gameConfig), { onSuccess: () => { RoomPersistence.gameCreated(roomId); }, diff --git a/webclient/src/websocket/commands/room/joinGame.ts b/webclient/src/websocket/commands/room/joinGame.ts index ef4b1fff2..3c5db7d98 100644 --- a/webclient/src/websocket/commands/room/joinGame.ts +++ b/webclient/src/websocket/commands/room/joinGame.ts @@ -1,9 +1,11 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_JoinGame_ext, Command_JoinGameSchema } from 'generated/proto/room_commands_pb'; import { RoomPersistence } from '../../persistence'; import { JoinGameParams } from 'types'; export function joinGame(roomId: number, joinGameParams: JoinGameParams): void { - BackendService.sendRoomCommand(roomId, 'Command_JoinGame', joinGameParams, { + BackendService.sendRoomCommand(roomId, Command_JoinGame_ext, create(Command_JoinGameSchema, joinGameParams), { onSuccess: () => { RoomPersistence.joinedGame(roomId, joinGameParams.gameId); }, diff --git a/webclient/src/websocket/commands/room/leaveRoom.ts b/webclient/src/websocket/commands/room/leaveRoom.ts index 7cd64a0e2..445f15ee3 100644 --- a/webclient/src/websocket/commands/room/leaveRoom.ts +++ b/webclient/src/websocket/commands/room/leaveRoom.ts @@ -1,8 +1,10 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_LeaveRoom_ext, Command_LeaveRoomSchema } from 'generated/proto/room_commands_pb'; import { RoomPersistence } from '../../persistence'; export function leaveRoom(roomId: number): void { - BackendService.sendRoomCommand(roomId, 'Command_LeaveRoom', {}, { + BackendService.sendRoomCommand(roomId, Command_LeaveRoom_ext, create(Command_LeaveRoomSchema), { onSuccess: () => { RoomPersistence.leaveRoom(roomId); }, diff --git a/webclient/src/websocket/commands/room/roomCommands.spec.ts b/webclient/src/websocket/commands/room/roomCommands.spec.ts index d700ced15..cf5693407 100644 --- a/webclient/src/websocket/commands/room/roomCommands.spec.ts +++ b/webclient/src/websocket/commands/room/roomCommands.spec.ts @@ -15,6 +15,7 @@ vi.mock('../../persistence', () => ({ import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; import { BackendService } from '../../services/BackendService'; import { RoomPersistence } from '../../persistence'; +import { Command_CreateGame_ext, Command_JoinGame_ext, Command_LeaveRoom_ext, Command_RoomSay_ext } from 'generated/proto/room_commands_pb'; import { createGame } from './createGame'; import { joinGame } from './joinGame'; import { leaveRoom } from './leaveRoom'; @@ -22,7 +23,7 @@ import { roomSay } from './roomSay'; const { getLastSendOpts, invokeOnSuccess } = makeCallbackHelpers( BackendService.sendRoomCommand as vi.Mock, - // sendRoomCommand(roomId, commandName, params, options) — options at index 3 + // sendRoomCommand(roomId, ext, value, options) — options at index 3 3 ); @@ -35,7 +36,9 @@ describe('createGame', () => { it('calls sendRoomCommand with Command_CreateGame', () => { createGame(5, { maxPlayers: 4 } as any); - expect(BackendService.sendRoomCommand).toHaveBeenCalledWith(5, 'Command_CreateGame', { maxPlayers: 4 }, expect.any(Object)); + expect(BackendService.sendRoomCommand).toHaveBeenCalledWith( + 5, Command_CreateGame_ext, expect.objectContaining({ maxPlayers: 4 }), expect.any(Object) + ); }); it('onSuccess calls RoomPersistence.gameCreated with roomId', () => { @@ -52,7 +55,9 @@ describe('joinGame', () => { it('calls sendRoomCommand with Command_JoinGame', () => { joinGame(7, { gameId: 42, password: '' } as any); - expect(BackendService.sendRoomCommand).toHaveBeenCalledWith(7, 'Command_JoinGame', { gameId: 42, password: '' }, expect.any(Object)); + expect(BackendService.sendRoomCommand).toHaveBeenCalledWith( + 7, Command_JoinGame_ext, expect.objectContaining({ gameId: 42, password: '' }), expect.any(Object) + ); }); it('onSuccess calls RoomPersistence.joinedGame with roomId and gameId', () => { @@ -69,7 +74,7 @@ describe('leaveRoom', () => { it('calls sendRoomCommand with Command_LeaveRoom', () => { leaveRoom(3); - expect(BackendService.sendRoomCommand).toHaveBeenCalledWith(3, 'Command_LeaveRoom', {}, expect.any(Object)); + expect(BackendService.sendRoomCommand).toHaveBeenCalledWith(3, Command_LeaveRoom_ext, expect.any(Object), expect.any(Object)); }); it('onSuccess calls RoomPersistence.leaveRoom with roomId', () => { @@ -86,7 +91,11 @@ describe('roomSay', () => { it('calls sendRoomCommand with trimmed message', () => { roomSay(2, ' hello '); - expect(BackendService.sendRoomCommand).toHaveBeenCalledWith(2, 'Command_RoomSay', { message: 'hello' }, expect.any(Object)); + expect(BackendService.sendRoomCommand).toHaveBeenCalledWith( + 2, + Command_RoomSay_ext, + expect.objectContaining({ message: 'hello' }) + ); }); it('does not call sendRoomCommand when message is blank', () => { diff --git a/webclient/src/websocket/commands/room/roomSay.ts b/webclient/src/websocket/commands/room/roomSay.ts index a429845be..0ebf62b00 100644 --- a/webclient/src/websocket/commands/room/roomSay.ts +++ b/webclient/src/websocket/commands/room/roomSay.ts @@ -1,4 +1,6 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_RoomSay_ext, Command_RoomSaySchema } from 'generated/proto/room_commands_pb'; export function roomSay(roomId: number, message: string): void { const trimmed = message.trim(); @@ -7,5 +9,5 @@ export function roomSay(roomId: number, message: string): void { return; } - BackendService.sendRoomCommand(roomId, 'Command_RoomSay', { message: trimmed }, {}); + BackendService.sendRoomCommand(roomId, Command_RoomSay_ext, create(Command_RoomSaySchema, { message: trimmed })); } diff --git a/webclient/src/websocket/commands/session/accountEdit.ts b/webclient/src/websocket/commands/session/accountEdit.ts index 31bf2d3f6..f36f48bf9 100644 --- a/webclient/src/websocket/commands/session/accountEdit.ts +++ b/webclient/src/websocket/commands/session/accountEdit.ts @@ -1,8 +1,11 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_AccountEdit_ext, Command_AccountEditSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; export function accountEdit(passwordCheck: string, realName?: string, email?: string, country?: string): void { - BackendService.sendSessionCommand('Command_AccountEdit', { passwordCheck, realName, email, country }, { + const cmd = create(Command_AccountEditSchema, { passwordCheck, realName, email, country }); + BackendService.sendSessionCommand(Command_AccountEdit_ext, cmd, { onSuccess: () => { SessionPersistence.accountEditChanged(realName, email, country); }, diff --git a/webclient/src/websocket/commands/session/accountImage.ts b/webclient/src/websocket/commands/session/accountImage.ts index cd0e24403..8a5eebe18 100644 --- a/webclient/src/websocket/commands/session/accountImage.ts +++ b/webclient/src/websocket/commands/session/accountImage.ts @@ -1,8 +1,10 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_AccountImage_ext, Command_AccountImageSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; export function accountImage(image: Uint8Array): void { - BackendService.sendSessionCommand('Command_AccountImage', { image }, { + BackendService.sendSessionCommand(Command_AccountImage_ext, create(Command_AccountImageSchema, { image }), { onSuccess: () => { SessionPersistence.accountImageChanged(image); }, diff --git a/webclient/src/websocket/commands/session/accountPassword.ts b/webclient/src/websocket/commands/session/accountPassword.ts index 81c7a993b..7a06ca6d7 100644 --- a/webclient/src/websocket/commands/session/accountPassword.ts +++ b/webclient/src/websocket/commands/session/accountPassword.ts @@ -1,8 +1,11 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_AccountPassword_ext, Command_AccountPasswordSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; export function accountPassword(oldPassword: string, newPassword: string, hashedNewPassword: string): void { - BackendService.sendSessionCommand('Command_AccountPassword', { oldPassword, newPassword, hashedNewPassword }, { + const cmd = create(Command_AccountPasswordSchema, { oldPassword, newPassword, hashedNewPassword }); + BackendService.sendSessionCommand(Command_AccountPassword_ext, cmd, { onSuccess: () => { SessionPersistence.accountPasswordChange(); }, diff --git a/webclient/src/websocket/commands/session/activate.ts b/webclient/src/websocket/commands/session/activate.ts index 6fd909ca8..25340d106 100644 --- a/webclient/src/websocket/commands/session/activate.ts +++ b/webclient/src/websocket/commands/session/activate.ts @@ -1,23 +1,25 @@ import { AccountActivationParams } from 'store'; import { StatusEnum, WebSocketConnectOptions } from 'types'; +import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; import { BackendService } from '../../services/BackendService'; -import { ProtoController } from '../../services/ProtoController'; +import { Command_Activate_ext, Command_ActivateSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; +import { Response_ResponseCode } from 'generated/proto/response_pb'; import { disconnect, login, updateStatus } from './'; export function activate(options: WebSocketConnectOptions, password?: string, passwordSalt?: string): void { const { userName, token } = options as unknown as AccountActivationParams; - BackendService.sendSessionCommand('Command_Activate', { + BackendService.sendSessionCommand(Command_Activate_ext, create(Command_ActivateSchema, { ...webClient.clientConfig, userName, token, - }, { + }), { onResponseCode: { - [ProtoController.root.Response.ResponseCode.RespActivationAccepted]: () => { + [Response_ResponseCode.RespActivationAccepted]: () => { SessionPersistence.accountActivationSuccess(); login(options, password, passwordSalt); }, diff --git a/webclient/src/websocket/commands/session/addToList.ts b/webclient/src/websocket/commands/session/addToList.ts index c5bc3c5f0..7dbee06e2 100644 --- a/webclient/src/websocket/commands/session/addToList.ts +++ b/webclient/src/websocket/commands/session/addToList.ts @@ -1,4 +1,6 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_AddToList_ext, Command_AddToListSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; export function addToBuddyList(userName: string): void { @@ -10,7 +12,7 @@ export function addToIgnoreList(userName: string): void { } export function addToList(list: string, userName: string): void { - BackendService.sendSessionCommand('Command_AddToList', { list, userName }, { + BackendService.sendSessionCommand(Command_AddToList_ext, create(Command_AddToListSchema, { list, userName }), { onSuccess: () => { SessionPersistence.addToList(list, userName); }, diff --git a/webclient/src/websocket/commands/session/deckDel.ts b/webclient/src/websocket/commands/session/deckDel.ts index 752ce78d5..04816ad46 100644 --- a/webclient/src/websocket/commands/session/deckDel.ts +++ b/webclient/src/websocket/commands/session/deckDel.ts @@ -1,8 +1,10 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_DeckDelSchema, Command_DeckDel_ext } from 'generated/proto/command_deck_del_pb'; import { SessionPersistence } from '../../persistence'; export function deckDel(deckId: number): void { - BackendService.sendSessionCommand('Command_DeckDel', { deckId }, { + BackendService.sendSessionCommand(Command_DeckDel_ext, create(Command_DeckDelSchema, { deckId }), { onSuccess: () => { SessionPersistence.deleteServerDeck(deckId); }, diff --git a/webclient/src/websocket/commands/session/deckDelDir.ts b/webclient/src/websocket/commands/session/deckDelDir.ts index df5bbc223..1d7782e2d 100644 --- a/webclient/src/websocket/commands/session/deckDelDir.ts +++ b/webclient/src/websocket/commands/session/deckDelDir.ts @@ -1,8 +1,10 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_DeckDelDirSchema, Command_DeckDelDir_ext } from 'generated/proto/command_deck_del_dir_pb'; import { SessionPersistence } from '../../persistence'; export function deckDelDir(path: string): void { - BackendService.sendSessionCommand('Command_DeckDelDir', { path }, { + BackendService.sendSessionCommand(Command_DeckDelDir_ext, create(Command_DeckDelDirSchema, { path }), { onSuccess: () => { SessionPersistence.deleteServerDeckDir(path); }, diff --git a/webclient/src/websocket/commands/session/deckList.ts b/webclient/src/websocket/commands/session/deckList.ts index 3d5a3499a..dc526d5be 100644 --- a/webclient/src/websocket/commands/session/deckList.ts +++ b/webclient/src/websocket/commands/session/deckList.ts @@ -1,9 +1,12 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_DeckListSchema, Command_DeckList_ext } from 'generated/proto/command_deck_list_pb'; import { SessionPersistence } from '../../persistence'; +import { Response_DeckList_ext } from 'generated/proto/response_deck_list_pb'; export function deckList(): void { - BackendService.sendSessionCommand('Command_DeckList', {}, { - responseName: 'Response_DeckList', + BackendService.sendSessionCommand(Command_DeckList_ext, create(Command_DeckListSchema), { + responseExt: Response_DeckList_ext, onSuccess: (response) => { SessionPersistence.updateServerDecks(response); }, diff --git a/webclient/src/websocket/commands/session/deckNewDir.ts b/webclient/src/websocket/commands/session/deckNewDir.ts index 85ab16afb..dc6f75481 100644 --- a/webclient/src/websocket/commands/session/deckNewDir.ts +++ b/webclient/src/websocket/commands/session/deckNewDir.ts @@ -1,8 +1,10 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_DeckNewDirSchema, Command_DeckNewDir_ext } from 'generated/proto/command_deck_new_dir_pb'; import { SessionPersistence } from '../../persistence'; export function deckNewDir(path: string, dirName: string): void { - BackendService.sendSessionCommand('Command_DeckNewDir', { path, dirName }, { + BackendService.sendSessionCommand(Command_DeckNewDir_ext, create(Command_DeckNewDirSchema, { path, dirName }), { onSuccess: () => { SessionPersistence.createServerDeckDir(path, dirName); }, diff --git a/webclient/src/websocket/commands/session/deckUpload.ts b/webclient/src/websocket/commands/session/deckUpload.ts index 2679c4e8e..fce574525 100644 --- a/webclient/src/websocket/commands/session/deckUpload.ts +++ b/webclient/src/websocket/commands/session/deckUpload.ts @@ -1,9 +1,12 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_DeckUploadSchema, Command_DeckUpload_ext } from 'generated/proto/command_deck_upload_pb'; import { SessionPersistence } from '../../persistence'; +import { Response_DeckUpload_ext } from 'generated/proto/response_deck_upload_pb'; export function deckUpload(path: string, deckId: number, deckList: string): void { - BackendService.sendSessionCommand('Command_DeckUpload', { path, deckId, deckList }, { - responseName: 'Response_DeckUpload', + BackendService.sendSessionCommand(Command_DeckUpload_ext, create(Command_DeckUploadSchema, { path, deckId, deckList }), { + responseExt: Response_DeckUpload_ext, onSuccess: (response) => { SessionPersistence.uploadServerDeck(path, response.newFile); }, diff --git a/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts b/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts index 05af1ccf9..71d105f8d 100644 --- a/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts +++ b/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts @@ -1,19 +1,23 @@ import { ForgotPasswordChallengeParams } from 'store'; import { StatusEnum, WebSocketConnectOptions } from 'types'; +import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; import { BackendService } from '../../services/BackendService'; +import { + Command_ForgotPasswordChallenge_ext, Command_ForgotPasswordChallengeSchema, +} from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; import { disconnect, updateStatus } from './'; export function forgotPasswordChallenge(options: WebSocketConnectOptions): void { const { userName, email } = options as unknown as ForgotPasswordChallengeParams; - BackendService.sendSessionCommand('Command_ForgotPasswordChallenge', { + BackendService.sendSessionCommand(Command_ForgotPasswordChallenge_ext, create(Command_ForgotPasswordChallengeSchema, { ...webClient.clientConfig, userName, email, - }, { + }), { onSuccess: () => { updateStatus(StatusEnum.DISCONNECTED, null); SessionPersistence.resetPassword(); diff --git a/webclient/src/websocket/commands/session/forgotPasswordRequest.ts b/webclient/src/websocket/commands/session/forgotPasswordRequest.ts index 23d301450..a14cd1ee5 100644 --- a/webclient/src/websocket/commands/session/forgotPasswordRequest.ts +++ b/webclient/src/websocket/commands/session/forgotPasswordRequest.ts @@ -1,20 +1,25 @@ import { ForgotPasswordParams } from 'store'; import { StatusEnum, WebSocketConnectOptions } from 'types'; +import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; import { BackendService } from '../../services/BackendService'; +import { + Command_ForgotPasswordRequest_ext, Command_ForgotPasswordRequestSchema, +} from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; +import { Response_ForgotPasswordRequest_ext } from 'generated/proto/response_forgotpasswordrequest_pb'; import { disconnect, updateStatus } from './'; export function forgotPasswordRequest(options: WebSocketConnectOptions): void { const { userName } = options as unknown as ForgotPasswordParams; - BackendService.sendSessionCommand('Command_ForgotPasswordRequest', { + BackendService.sendSessionCommand(Command_ForgotPasswordRequest_ext, create(Command_ForgotPasswordRequestSchema, { ...webClient.clientConfig, userName, - }, { - responseName: 'Response_ForgotPasswordRequest', + }), { + responseExt: Response_ForgotPasswordRequest_ext, onSuccess: (resp) => { if (resp?.challengeEmail) { updateStatus(StatusEnum.DISCONNECTED, null); diff --git a/webclient/src/websocket/commands/session/forgotPasswordReset.ts b/webclient/src/websocket/commands/session/forgotPasswordReset.ts index 21e5842f6..543467b18 100644 --- a/webclient/src/websocket/commands/session/forgotPasswordReset.ts +++ b/webclient/src/websocket/commands/session/forgotPasswordReset.ts @@ -1,8 +1,13 @@ import { ForgotPasswordResetParams } from 'store'; import { StatusEnum, WebSocketConnectOptions } from 'types'; +import { create } from '@bufbuild/protobuf'; +import type { MessageInitShape } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; import { BackendService } from '../../services/BackendService'; +import { + Command_ForgotPasswordReset_ext, Command_ForgotPasswordResetSchema, +} from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; import { hashPassword } from '../../utils'; @@ -11,19 +16,16 @@ import { disconnect, updateStatus } from '.'; export function forgotPasswordReset(options: WebSocketConnectOptions, newPassword?: string, passwordSalt?: string): void { const { userName, token } = options as unknown as ForgotPasswordResetParams; - const params: any = { + const params: MessageInitShape = { ...webClient.clientConfig, userName, token, + ...(passwordSalt + ? { hashedNewPassword: hashPassword(passwordSalt, newPassword) } + : { newPassword }), }; - if (passwordSalt) { - params.hashedNewPassword = hashPassword(passwordSalt, newPassword); - } else { - params.newPassword = newPassword; - } - - BackendService.sendSessionCommand('Command_ForgotPasswordReset', params, { + BackendService.sendSessionCommand(Command_ForgotPasswordReset_ext, create(Command_ForgotPasswordResetSchema, params), { onSuccess: () => { updateStatus(StatusEnum.DISCONNECTED, null); SessionPersistence.resetPasswordSuccess(); diff --git a/webclient/src/websocket/commands/session/getGamesOfUser.ts b/webclient/src/websocket/commands/session/getGamesOfUser.ts index 8fb8aeb5b..4f2cb2109 100644 --- a/webclient/src/websocket/commands/session/getGamesOfUser.ts +++ b/webclient/src/websocket/commands/session/getGamesOfUser.ts @@ -1,9 +1,12 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_GetGamesOfUser_ext, Command_GetGamesOfUserSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; +import { Response_GetGamesOfUser_ext } from 'generated/proto/response_get_games_of_user_pb'; export function getGamesOfUser(userName: string): void { - BackendService.sendSessionCommand('Command_GetGamesOfUser', { userName }, { - responseName: 'Response_GetGamesOfUser', + BackendService.sendSessionCommand(Command_GetGamesOfUser_ext, create(Command_GetGamesOfUserSchema, { userName }), { + responseExt: Response_GetGamesOfUser_ext, onSuccess: (response) => { SessionPersistence.getGamesOfUser(userName, response); }, diff --git a/webclient/src/websocket/commands/session/getUserInfo.ts b/webclient/src/websocket/commands/session/getUserInfo.ts index 5b0f178ae..2a93e81d1 100644 --- a/webclient/src/websocket/commands/session/getUserInfo.ts +++ b/webclient/src/websocket/commands/session/getUserInfo.ts @@ -1,9 +1,12 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_GetUserInfo_ext, Command_GetUserInfoSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; +import { Response_GetUserInfo_ext } from 'generated/proto/response_get_user_info_pb'; export function getUserInfo(userName: string): void { - BackendService.sendSessionCommand('Command_GetUserInfo', { userName }, { - responseName: 'Response_GetUserInfo', + BackendService.sendSessionCommand(Command_GetUserInfo_ext, create(Command_GetUserInfoSchema, { userName }), { + responseExt: Response_GetUserInfo_ext, onSuccess: (response) => { SessionPersistence.getUserInfo(response.userInfo); }, diff --git a/webclient/src/websocket/commands/session/joinRoom.ts b/webclient/src/websocket/commands/session/joinRoom.ts index be79976a0..657acd3c7 100644 --- a/webclient/src/websocket/commands/session/joinRoom.ts +++ b/webclient/src/websocket/commands/session/joinRoom.ts @@ -1,9 +1,12 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_JoinRoom_ext, Command_JoinRoomSchema } from 'generated/proto/session_commands_pb'; import { RoomPersistence } from '../../persistence'; +import { Response_JoinRoom_ext } from 'generated/proto/response_join_room_pb'; export function joinRoom(roomId: number): void { - BackendService.sendSessionCommand('Command_JoinRoom', { roomId }, { - responseName: 'Response_JoinRoom', + BackendService.sendSessionCommand(Command_JoinRoom_ext, create(Command_JoinRoomSchema, { roomId }), { + responseExt: Response_JoinRoom_ext, onSuccess: (response) => { RoomPersistence.joinRoom(response.roomInfo); }, diff --git a/webclient/src/websocket/commands/session/listRooms.ts b/webclient/src/websocket/commands/session/listRooms.ts index 367dada9b..9a4efde14 100644 --- a/webclient/src/websocket/commands/session/listRooms.ts +++ b/webclient/src/websocket/commands/session/listRooms.ts @@ -1,5 +1,7 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_ListRooms_ext, Command_ListRoomsSchema } from 'generated/proto/session_commands_pb'; export function listRooms(): void { - BackendService.sendSessionCommand('Command_ListRooms', {}, {}); + BackendService.sendSessionCommand(Command_ListRooms_ext, create(Command_ListRoomsSchema)); } diff --git a/webclient/src/websocket/commands/session/listUsers.ts b/webclient/src/websocket/commands/session/listUsers.ts index 9b95c1344..e10fe2ba5 100644 --- a/webclient/src/websocket/commands/session/listUsers.ts +++ b/webclient/src/websocket/commands/session/listUsers.ts @@ -1,9 +1,12 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_ListUsers_ext, Command_ListUsersSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; +import { Response_ListUsers_ext } from 'generated/proto/response_list_users_pb'; export function listUsers(): void { - BackendService.sendSessionCommand('Command_ListUsers', {}, { - responseName: 'Response_ListUsers', + BackendService.sendSessionCommand(Command_ListUsers_ext, create(Command_ListUsersSchema), { + responseExt: Response_ListUsers_ext, onSuccess: (response) => { SessionPersistence.updateUsers(response.userList); }, diff --git a/webclient/src/websocket/commands/session/login.ts b/webclient/src/websocket/commands/session/login.ts index adbd45b5d..68b0cd896 100644 --- a/webclient/src/websocket/commands/session/login.ts +++ b/webclient/src/websocket/commands/session/login.ts @@ -1,9 +1,13 @@ import { StatusEnum, WebSocketConnectOptions } from 'types'; +import { create } from '@bufbuild/protobuf'; +import type { MessageInitShape } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; import { BackendService } from '../../services/BackendService'; -import { ProtoController } from '../../services/ProtoController'; +import { Command_Login_ext, Command_LoginSchema } from 'generated/proto/session_commands_pb'; import { hashPassword } from '../../utils'; import { SessionPersistence } from '../../persistence'; +import { Response_Login_ext } from 'generated/proto/response_login_pb'; +import { Response_ResponseCode } from 'generated/proto/response_pb'; import { disconnect, @@ -15,20 +19,15 @@ import { export function login(options: WebSocketConnectOptions, password?: string, passwordSalt?: string): void { const { userName, hashedPassword } = options; - const loginConfig: any = { + const loginConfig: MessageInitShape = { ...webClient.clientConfig, clientid: 'webatrice', userName, + ...(passwordSalt + ? { hashedPassword: hashedPassword || hashPassword(passwordSalt, password) } + : { password }), }; - if (passwordSalt) { - loginConfig.hashedPassword = hashedPassword || hashPassword(passwordSalt, password); - } else { - loginConfig.password = password; - } - - const { ResponseCode } = ProtoController.root.Response; - const onLoginError = (message: string, extra?: () => void) => { updateStatus(StatusEnum.DISCONNECTED, message); extra?.(); @@ -36,8 +35,8 @@ export function login(options: WebSocketConnectOptions, password?: string, passw disconnect(); }; - BackendService.sendSessionCommand('Command_Login', loginConfig, { - responseName: 'Response_Login', + BackendService.sendSessionCommand(Command_Login_ext, create(Command_LoginSchema, loginConfig), { + responseExt: Response_Login_ext, onSuccess: (resp) => { const { buddyList, ignoreList, userInfo } = resp; @@ -53,23 +52,23 @@ export function login(options: WebSocketConnectOptions, password?: string, passw updateStatus(StatusEnum.LOGGED_IN, 'Logged in.'); }, onResponseCode: { - [ResponseCode.RespClientUpdateRequired]: () => + [Response_ResponseCode.RespClientUpdateRequired]: () => onLoginError('Login failed: missing features'), - [ResponseCode.RespWrongPassword]: () => + [Response_ResponseCode.RespWrongPassword]: () => onLoginError('Login failed: incorrect username or password'), - [ResponseCode.RespUsernameInvalid]: () => + [Response_ResponseCode.RespUsernameInvalid]: () => onLoginError('Login failed: incorrect username or password'), - [ResponseCode.RespWouldOverwriteOldSession]: () => + [Response_ResponseCode.RespWouldOverwriteOldSession]: () => onLoginError('Login failed: duplicated user session'), - [ResponseCode.RespUserIsBanned]: () => + [Response_ResponseCode.RespUserIsBanned]: () => onLoginError('Login failed: banned user'), - [ResponseCode.RespRegistrationRequired]: () => + [Response_ResponseCode.RespRegistrationRequired]: () => onLoginError('Login failed: registration required'), - [ResponseCode.RespClientIdRequired]: () => + [Response_ResponseCode.RespClientIdRequired]: () => onLoginError('Login failed: missing client ID'), - [ResponseCode.RespContextError]: () => + [Response_ResponseCode.RespContextError]: () => onLoginError('Login failed: server error'), - [ResponseCode.RespAccountNotActivated]: () => + [Response_ResponseCode.RespAccountNotActivated]: () => onLoginError('Login failed: account not activated', () => { const { password: _p, newPassword: _np, ...safeOptions } = options; diff --git a/webclient/src/websocket/commands/session/message.ts b/webclient/src/websocket/commands/session/message.ts index b6bde9cac..19946779e 100644 --- a/webclient/src/websocket/commands/session/message.ts +++ b/webclient/src/websocket/commands/session/message.ts @@ -1,5 +1,7 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_Message_ext, Command_MessageSchema } from 'generated/proto/session_commands_pb'; export function message(userName: string, message: string): void { - BackendService.sendSessionCommand('Command_Message', { userName, message }, {}); + BackendService.sendSessionCommand(Command_Message_ext, create(Command_MessageSchema, { userName, message })); } diff --git a/webclient/src/websocket/commands/session/ping.ts b/webclient/src/websocket/commands/session/ping.ts index fea2784a2..11a7ff4fc 100644 --- a/webclient/src/websocket/commands/session/ping.ts +++ b/webclient/src/websocket/commands/session/ping.ts @@ -1,7 +1,9 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_Ping_ext, Command_PingSchema } from 'generated/proto/session_commands_pb'; export function ping(pingReceived: Function): void { - BackendService.sendSessionCommand('Command_Ping', {}, { + BackendService.sendSessionCommand(Command_Ping_ext, create(Command_PingSchema), { onResponse: (raw) => pingReceived(raw), }); } diff --git a/webclient/src/websocket/commands/session/register.ts b/webclient/src/websocket/commands/session/register.ts index 31bd37a09..22d80d923 100644 --- a/webclient/src/websocket/commands/session/register.ts +++ b/webclient/src/websocket/commands/session/register.ts @@ -1,73 +1,71 @@ import { ServerRegisterParams } from 'store'; import { StatusEnum, WebSocketConnectOptions } from 'types'; +import { create } from '@bufbuild/protobuf'; +import type { MessageInitShape } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; import { BackendService } from '../../services/BackendService'; -import { ProtoController } from '../../services/ProtoController'; +import { Command_Register_ext, Command_RegisterSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; import { hashPassword } from '../../utils'; +import { Response_ResponseCode } from 'generated/proto/response_pb'; import { login, disconnect, updateStatus } from './'; export function register(options: WebSocketConnectOptions, password?: string, passwordSalt?: string): void { const { userName, email, country, realName } = options as ServerRegisterParams; - const params: any = { + const params: MessageInitShape = { ...webClient.clientConfig, userName, email, country, realName, + ...(passwordSalt + ? { hashedPassword: hashPassword(passwordSalt, password) } + : { password }), }; - if (passwordSalt) { - params.hashedPassword = hashPassword(passwordSalt, password); - } else { - params.password = password; - } - - const { ResponseCode } = ProtoController.root.Response; - const onRegistrationError = (action: () => void) => { action(); updateStatus(StatusEnum.DISCONNECTED, 'Registration failed'); disconnect(); }; - BackendService.sendSessionCommand('Command_Register', params, { + BackendService.sendSessionCommand(Command_Register_ext, create(Command_RegisterSchema, params), { onResponseCode: { - [ResponseCode.RespRegistrationAccepted]: () => { + [Response_ResponseCode.RespRegistrationAccepted]: () => { login(options, password, passwordSalt); SessionPersistence.registrationSuccess(); }, - [ResponseCode.RespRegistrationAcceptedNeedsActivation]: () => { + [Response_ResponseCode.RespRegistrationAcceptedNeedsActivation]: () => { updateStatus(StatusEnum.DISCONNECTED, 'Registration accepted, awaiting activation'); const { password: _p, newPassword: _np, ...safeOptions } = options; SessionPersistence.accountAwaitingActivation(safeOptions); disconnect(); }, - [ResponseCode.RespUserAlreadyExists]: () => onRegistrationError( + [Response_ResponseCode.RespUserAlreadyExists]: () => onRegistrationError( () => SessionPersistence.registrationUserNameError('Username is taken') ), - [ResponseCode.RespUsernameInvalid]: () => onRegistrationError( + [Response_ResponseCode.RespUsernameInvalid]: () => onRegistrationError( () => SessionPersistence.registrationUserNameError('Invalid username') ), - [ResponseCode.RespPasswordTooShort]: () => onRegistrationError( + [Response_ResponseCode.RespPasswordTooShort]: () => onRegistrationError( () => SessionPersistence.registrationPasswordError('Your password was too short') ), - [ResponseCode.RespEmailRequiredToRegister]: () => onRegistrationError( + [Response_ResponseCode.RespEmailRequiredToRegister]: () => onRegistrationError( () => SessionPersistence.registrationRequiresEmail() ), - [ResponseCode.RespEmailBlackListed]: () => onRegistrationError( + [Response_ResponseCode.RespEmailBlackListed]: () => onRegistrationError( () => SessionPersistence.registrationEmailError('This email provider has been blocked') ), - [ResponseCode.RespTooManyRequests]: () => onRegistrationError( + [Response_ResponseCode.RespTooManyRequests]: () => onRegistrationError( () => SessionPersistence.registrationEmailError('Max accounts reached for this email') ), - [ResponseCode.RespRegistrationDisabled]: () => onRegistrationError( + [Response_ResponseCode.RespRegistrationDisabled]: () => onRegistrationError( () => SessionPersistence.registrationFailed('Registration is currently disabled') ), - [ResponseCode.RespUserIsBanned]: (raw) => onRegistrationError( + [Response_ResponseCode.RespUserIsBanned]: (raw) => onRegistrationError( () => SessionPersistence.registrationFailed(raw.reasonStr, raw.endTime) ), }, diff --git a/webclient/src/websocket/commands/session/removeFromList.ts b/webclient/src/websocket/commands/session/removeFromList.ts index aede49c49..ff1254e27 100644 --- a/webclient/src/websocket/commands/session/removeFromList.ts +++ b/webclient/src/websocket/commands/session/removeFromList.ts @@ -1,4 +1,6 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_RemoveFromList_ext, Command_RemoveFromListSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; export function removeFromBuddyList(userName: string): void { @@ -10,7 +12,7 @@ export function removeFromIgnoreList(userName: string): void { } export function removeFromList(list: string, userName: string): void { - BackendService.sendSessionCommand('Command_RemoveFromList', { list, userName }, { + BackendService.sendSessionCommand(Command_RemoveFromList_ext, create(Command_RemoveFromListSchema, { list, userName }), { onSuccess: () => { SessionPersistence.removeFromList(list, userName); }, diff --git a/webclient/src/websocket/commands/session/replayDeleteMatch.ts b/webclient/src/websocket/commands/session/replayDeleteMatch.ts index 24ac48f1c..7d243e1b2 100644 --- a/webclient/src/websocket/commands/session/replayDeleteMatch.ts +++ b/webclient/src/websocket/commands/session/replayDeleteMatch.ts @@ -1,8 +1,10 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_ReplayDeleteMatchSchema, Command_ReplayDeleteMatch_ext } from 'generated/proto/command_replay_delete_match_pb'; import { SessionPersistence } from '../../persistence'; export function replayDeleteMatch(gameId: number): void { - BackendService.sendSessionCommand('Command_ReplayDeleteMatch', { gameId }, { + BackendService.sendSessionCommand(Command_ReplayDeleteMatch_ext, create(Command_ReplayDeleteMatchSchema, { gameId }), { onSuccess: () => { SessionPersistence.replayDeleteMatch(gameId); }, diff --git a/webclient/src/websocket/commands/session/replayGetCode.ts b/webclient/src/websocket/commands/session/replayGetCode.ts index 1e0557d1c..77f72c330 100644 --- a/webclient/src/websocket/commands/session/replayGetCode.ts +++ b/webclient/src/websocket/commands/session/replayGetCode.ts @@ -1,8 +1,11 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_ReplayGetCodeSchema, Command_ReplayGetCode_ext } from 'generated/proto/command_replay_get_code_pb'; +import { Response_ReplayGetCode_ext } from 'generated/proto/response_replay_get_code_pb'; export function replayGetCode(gameId: number, onCodeReceived: (code: string) => void): void { - BackendService.sendSessionCommand('Command_ReplayGetCode', { gameId }, { - responseName: 'Response_ReplayGetCode', + BackendService.sendSessionCommand(Command_ReplayGetCode_ext, create(Command_ReplayGetCodeSchema, { gameId }), { + responseExt: Response_ReplayGetCode_ext, onSuccess: (response) => { onCodeReceived(response.replayCode); }, diff --git a/webclient/src/websocket/commands/session/replayList.ts b/webclient/src/websocket/commands/session/replayList.ts index f39eb279f..1d6dc8aa5 100644 --- a/webclient/src/websocket/commands/session/replayList.ts +++ b/webclient/src/websocket/commands/session/replayList.ts @@ -1,9 +1,12 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_ReplayListSchema, Command_ReplayList_ext } from 'generated/proto/command_replay_list_pb'; import { SessionPersistence } from '../../persistence'; +import { Response_ReplayList_ext } from 'generated/proto/response_replay_list_pb'; export function replayList(): void { - BackendService.sendSessionCommand('Command_ReplayList', {}, { - responseName: 'Response_ReplayList', + BackendService.sendSessionCommand(Command_ReplayList_ext, create(Command_ReplayListSchema), { + responseExt: Response_ReplayList_ext, onSuccess: (response) => { SessionPersistence.replayList(response.matchList); }, diff --git a/webclient/src/websocket/commands/session/replayModifyMatch.ts b/webclient/src/websocket/commands/session/replayModifyMatch.ts index 9825047f3..fa2af33c5 100644 --- a/webclient/src/websocket/commands/session/replayModifyMatch.ts +++ b/webclient/src/websocket/commands/session/replayModifyMatch.ts @@ -1,8 +1,10 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_ReplayModifyMatchSchema, Command_ReplayModifyMatch_ext } from 'generated/proto/command_replay_modify_match_pb'; import { SessionPersistence } from '../../persistence'; export function replayModifyMatch(gameId: number, doNotHide: boolean): void { - BackendService.sendSessionCommand('Command_ReplayModifyMatch', { gameId, doNotHide }, { + BackendService.sendSessionCommand(Command_ReplayModifyMatch_ext, create(Command_ReplayModifyMatchSchema, { gameId, doNotHide }), { onSuccess: () => { SessionPersistence.replayModifyMatch(gameId, doNotHide); }, diff --git a/webclient/src/websocket/commands/session/replaySubmitCode.ts b/webclient/src/websocket/commands/session/replaySubmitCode.ts index ad1896e57..1ae371e8d 100644 --- a/webclient/src/websocket/commands/session/replaySubmitCode.ts +++ b/webclient/src/websocket/commands/session/replaySubmitCode.ts @@ -1,11 +1,13 @@ +import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; +import { Command_ReplaySubmitCodeSchema, Command_ReplaySubmitCode_ext } from 'generated/proto/command_replay_submit_code_pb'; export function replaySubmitCode( replayCode: string, onSuccess?: () => void, onError?: (responseCode: number) => void, ): void { - BackendService.sendSessionCommand('Command_ReplaySubmitCode', { replayCode }, { + BackendService.sendSessionCommand(Command_ReplaySubmitCode_ext, create(Command_ReplaySubmitCodeSchema, { replayCode }), { onSuccess, onError, }); diff --git a/webclient/src/websocket/commands/session/requestPasswordSalt.ts b/webclient/src/websocket/commands/session/requestPasswordSalt.ts index e3635da70..5ba515719 100644 --- a/webclient/src/websocket/commands/session/requestPasswordSalt.ts +++ b/webclient/src/websocket/commands/session/requestPasswordSalt.ts @@ -1,10 +1,15 @@ import { RequestPasswordSaltParams } from 'store'; import { StatusEnum, WebSocketConnectOptions, WebSocketConnectReason } from 'types'; +import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; import { BackendService } from '../../services/BackendService'; -import { ProtoController } from '../../services/ProtoController'; +import { + Command_RequestPasswordSalt_ext, Command_RequestPasswordSaltSchema, +} from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; +import { Response_PasswordSalt_ext } from 'generated/proto/response_password_salt_pb'; +import { Response_ResponseCode } from 'generated/proto/response_pb'; import { activate, @@ -31,11 +36,11 @@ export function requestPasswordSalt(options: WebSocketConnectOptions, password?: disconnect(); }; - BackendService.sendSessionCommand('Command_RequestPasswordSalt', { + BackendService.sendSessionCommand(Command_RequestPasswordSalt_ext, create(Command_RequestPasswordSaltSchema, { ...webClient.clientConfig, userName, - }, { - responseName: 'Response_PasswordSalt', + }), { + responseExt: Response_PasswordSalt_ext, onSuccess: (resp) => { const passwordSalt = resp?.passwordSalt; @@ -51,7 +56,7 @@ export function requestPasswordSalt(options: WebSocketConnectOptions, password?: } }, onResponseCode: { - [ProtoController.root.Response.ResponseCode.RespRegistrationRequired]: () => { + [Response_ResponseCode.RespRegistrationRequired]: () => { updateStatus(StatusEnum.DISCONNECTED, 'Login failed: registration required'); onFailure(); }, diff --git a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts index 8e8e2d02a..fe4227c4a 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts @@ -20,11 +20,6 @@ vi.mock('../../WebClient', async () => { return { __esModule: true, default: makeWebClientMock() }; }); -vi.mock('../../services/ProtoController', async () => { - const { makeProtoControllerRootMock } = await import('../../__mocks__/sessionCommandMocks'); - return { ProtoController: { root: makeProtoControllerRootMock() } }; -}); - vi.mock('../../utils', async () => { const { makeUtilsMock } = await import('../../__mocks__/sessionCommandMocks'); return makeUtilsMock(); @@ -43,6 +38,19 @@ import webClient from '../../WebClient'; import * as SessionIndexMocks from './'; import { StatusEnum, WebSocketConnectReason } from 'types'; import { hashPassword, generateSalt, passwordSaltSupported } from '../../utils'; +import { Response_ResponseCode } from 'generated/proto/response_pb'; +import { + Command_Activate_ext, + Command_ForgotPasswordChallenge_ext, + Command_ForgotPasswordRequest_ext, + Command_ForgotPasswordReset_ext, + Command_Login_ext, + Command_Register_ext, + Command_RequestPasswordSalt_ext, +} from 'generated/proto/session_commands_pb'; +import { Response_ForgotPasswordRequest_ext } from 'generated/proto/response_forgotpasswordrequest_pb'; +import { Response_Login_ext } from 'generated/proto/response_login_pb'; +import { Response_PasswordSalt_ext } from 'generated/proto/response_password_salt_pb'; import { connect } from './connect'; import { updateStatus } from './updateStatus'; import { login } from './login'; @@ -54,7 +62,8 @@ import { forgotPasswordReset } from './forgotPasswordReset'; import { requestPasswordSalt } from './requestPasswordSalt'; const { getLastSendOpts, invokeOnSuccess, invokeResponseCode, invokeOnError } = makeCallbackHelpers( - BackendService.sendSessionCommand as vi.Mock + BackendService.sendSessionCommand as vi.Mock, + 2 ); beforeEach(() => { @@ -132,34 +141,34 @@ describe('login', () => { it('sends Command_Login with plain password when no salt', () => { login({ userName: 'alice' } as any, 'pw'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_Login', - expect.objectContaining({ userName: 'alice', password: 'pw' }), - expect.any(Object) + Command_Login_ext, + expect.objectContaining({ password: 'pw' }), + expect.objectContaining({ responseExt: Response_Login_ext }) ); }); it('sends Command_Login with hashedPassword when salt is given', () => { login({ userName: 'alice' } as any, 'pw', 'salt'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_Login', + Command_Login_ext, expect.objectContaining({ hashedPassword: 'hashed_pw' }), - expect.any(Object) + expect.objectContaining({ responseExt: Response_Login_ext }) ); }); it('uses options.hashedPassword if provided', () => { login({ userName: 'alice', hashedPassword: 'pre_hashed' } as any, 'pw', 'salt'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_Login', + Command_Login_ext, expect.objectContaining({ hashedPassword: 'pre_hashed' }), - expect.any(Object) + expect.objectContaining({ responseExt: Response_Login_ext }) ); }); it('onSuccess dispatches buddy/ignore/user and calls listUsers/listRooms', () => { login({ userName: 'alice' } as any, 'pw'); const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } }; - invokeOnSuccess(loginResp, { responseCode: 0, '.Response_Login.ext': loginResp }); + invokeOnSuccess(loginResp, { responseCode: 0 }); expect(SessionPersistence.updateBuddyList).toHaveBeenCalledWith([]); expect(SessionPersistence.updateIgnoreList).toHaveBeenCalledWith([]); expect(SessionPersistence.updateUser).toHaveBeenCalledWith({ name: 'alice' }); @@ -172,7 +181,7 @@ describe('login', () => { it('onSuccess does NOT pass plaintext password to loginSuccessful', () => { login({ userName: 'alice' } as any, 'secret'); const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } }; - invokeOnSuccess(loginResp, { responseCode: 0, '.Response_Login.ext': loginResp }); + invokeOnSuccess(loginResp, { responseCode: 0 }); const calledWith = (SessionPersistence.loginSuccessful as vi.Mock).mock.calls[0][0]; expect(calledWith).not.toHaveProperty('password'); }); @@ -180,63 +189,63 @@ describe('login', () => { it('onSuccess passes hashedPassword to loginSuccessful when salt is used', () => { login({ userName: 'alice' } as any, 'pw', 'salt'); const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } }; - invokeOnSuccess(loginResp, { responseCode: 0, '.Response_Login.ext': loginResp }); + invokeOnSuccess(loginResp, { responseCode: 0 }); const calledWith = (SessionPersistence.loginSuccessful as vi.Mock).mock.calls[0][0]; expect(calledWith).toHaveProperty('hashedPassword', 'hashed_pw'); }); it('onResponseCode RespClientUpdateRequired calls onLoginError', () => { login({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(1); + invokeResponseCode(Response_ResponseCode.RespClientUpdateRequired); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); }); it('onResponseCode RespWrongPassword', () => { login({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(2); + invokeResponseCode(Response_ResponseCode.RespWrongPassword); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespUsernameInvalid', () => { login({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(3); + invokeResponseCode(Response_ResponseCode.RespUsernameInvalid); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespWouldOverwriteOldSession', () => { login({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(4); + invokeResponseCode(Response_ResponseCode.RespWouldOverwriteOldSession); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespUserIsBanned', () => { login({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(5); + invokeResponseCode(Response_ResponseCode.RespUserIsBanned); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespRegistrationRequired', () => { login({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(6); + invokeResponseCode(Response_ResponseCode.RespRegistrationRequired); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespClientIdRequired', () => { login({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(7); + invokeResponseCode(Response_ResponseCode.RespClientIdRequired); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespContextError', () => { login({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(8); + invokeResponseCode(Response_ResponseCode.RespContextError); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespAccountNotActivated calls accountAwaitingActivation without password in options', () => { login({ userName: 'alice', password: 'leaked' } as any, 'pw'); - invokeResponseCode(9); + invokeResponseCode(Response_ResponseCode.RespAccountNotActivated); expect(SessionPersistence.accountAwaitingActivation).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }) ); @@ -258,8 +267,8 @@ describe('register', () => { it('sends Command_Register with plain password when no salt', () => { register({ userName: 'alice', email: 'a@b.com', country: 'US', realName: 'Al' } as any, 'pw'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_Register', - expect.objectContaining({ userName: 'alice', password: 'pw' }), + Command_Register_ext, + expect.objectContaining({ password: 'pw' }), expect.any(Object) ); }); @@ -267,7 +276,7 @@ describe('register', () => { it('uses hashedPassword when salt is provided', () => { register({ userName: 'alice' } as any, 'pw', 'salt'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_Register', + Command_Register_ext, expect.objectContaining({ hashedPassword: 'hashed_pw' }), expect.any(Object) ); @@ -275,21 +284,21 @@ describe('register', () => { it('RespRegistrationAccepted calls login without salt and registrationSuccess', () => { register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(10); + invokeResponseCode(Response_ResponseCode.RespRegistrationAccepted); expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'pw', undefined); expect(SessionPersistence.registrationSuccess).toHaveBeenCalled(); }); it('RespRegistrationAccepted forwards salt to login', () => { register({ userName: 'alice' } as any, 'pw', 'mySalt'); - invokeResponseCode(10); + invokeResponseCode(Response_ResponseCode.RespRegistrationAccepted); expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'pw', 'mySalt'); expect(SessionPersistence.registrationSuccess).toHaveBeenCalled(); }); it('RespRegistrationAcceptedNeedsActivation calls accountAwaitingActivation without password in options', () => { register({ userName: 'alice', password: 'leaked' } as any, 'pw'); - invokeResponseCode(11); + invokeResponseCode(Response_ResponseCode.RespRegistrationAcceptedNeedsActivation); expect(SessionPersistence.accountAwaitingActivation).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }) ); @@ -298,49 +307,49 @@ describe('register', () => { it('RespUserAlreadyExists calls registrationUserNameError', () => { register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(12); + invokeResponseCode(Response_ResponseCode.RespUserAlreadyExists); expect(SessionPersistence.registrationUserNameError).toHaveBeenCalled(); }); it('RespUsernameInvalid calls registrationUserNameError', () => { register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(3); + invokeResponseCode(Response_ResponseCode.RespUsernameInvalid); expect(SessionPersistence.registrationUserNameError).toHaveBeenCalled(); }); it('RespPasswordTooShort calls registrationPasswordError', () => { register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(13); + invokeResponseCode(Response_ResponseCode.RespPasswordTooShort); expect(SessionPersistence.registrationPasswordError).toHaveBeenCalled(); }); it('RespEmailRequiredToRegister calls registrationRequiresEmail', () => { register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(14); + invokeResponseCode(Response_ResponseCode.RespEmailRequiredToRegister); expect(SessionPersistence.registrationRequiresEmail).toHaveBeenCalled(); }); it('RespEmailBlackListed calls registrationEmailError', () => { register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(15); + invokeResponseCode(Response_ResponseCode.RespEmailBlackListed); expect(SessionPersistence.registrationEmailError).toHaveBeenCalled(); }); it('RespTooManyRequests calls registrationEmailError', () => { register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(16); + invokeResponseCode(Response_ResponseCode.RespTooManyRequests); expect(SessionPersistence.registrationEmailError).toHaveBeenCalled(); }); it('RespRegistrationDisabled calls registrationFailed', () => { register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(17); + invokeResponseCode(Response_ResponseCode.RespRegistrationDisabled); expect(SessionPersistence.registrationFailed).toHaveBeenCalled(); }); it('RespUserIsBanned calls registrationFailed with raw.reasonStr and raw.endTime', () => { register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(5, { reasonStr: 'bad user', endTime: 9999 }); + invokeResponseCode(Response_ResponseCode.RespUserIsBanned, { reasonStr: 'bad user', endTime: 9999 }); expect(SessionPersistence.registrationFailed).toHaveBeenCalledWith('bad user', 9999); }); @@ -359,12 +368,12 @@ describe('activate', () => { it('sends Command_Activate with userName and token, not password', () => { activate({ userName: 'alice', token: 'tok' } as any, 'pw'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_Activate', + Command_Activate_ext, expect.objectContaining({ userName: 'alice', token: 'tok' }), expect.any(Object) ); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_Activate', + Command_Activate_ext, expect.not.objectContaining({ password: expect.anything() }), expect.any(Object) ); @@ -372,7 +381,7 @@ describe('activate', () => { it('RespActivationAccepted calls accountActivationSuccess and forwards password+salt to login', () => { activate({ userName: 'alice', token: 'tok' } as any, 'pw', 'salt'); - invokeResponseCode(18); + invokeResponseCode(Response_ResponseCode.RespActivationAccepted); expect(SessionPersistence.accountActivationSuccess).toHaveBeenCalled(); expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'pw', 'salt'); }); @@ -393,7 +402,7 @@ describe('forgotPasswordChallenge', () => { it('sends Command_ForgotPasswordChallenge', () => { forgotPasswordChallenge({ userName: 'alice', email: 'a@b.com' } as any); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_ForgotPasswordChallenge', expect.any(Object), expect.any(Object) + Command_ForgotPasswordChallenge_ext, expect.any(Object), expect.any(Object) ); }); @@ -419,13 +428,17 @@ describe('forgotPasswordRequest', () => { it('sends Command_ForgotPasswordRequest', () => { forgotPasswordRequest({ userName: 'alice' } as any); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ForgotPasswordRequest', expect.any(Object), expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + Command_ForgotPasswordRequest_ext, + expect.any(Object), + expect.objectContaining({ responseExt: Response_ForgotPasswordRequest_ext }) + ); }); it('onSuccess with challengeEmail calls resetPasswordChallenge', () => { forgotPasswordRequest({ userName: 'alice' } as any); const resp = { challengeEmail: true }; - invokeOnSuccess(resp, { responseCode: 0, '.Response_ForgotPasswordRequest.ext': resp }); + invokeOnSuccess(resp, { responseCode: 0 }); expect(SessionPersistence.resetPasswordChallenge).toHaveBeenCalled(); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); }); @@ -433,7 +446,7 @@ describe('forgotPasswordRequest', () => { it('onSuccess without challengeEmail calls resetPassword', () => { forgotPasswordRequest({ userName: 'alice' } as any); const resp = { challengeEmail: false }; - invokeOnSuccess(resp, { responseCode: 0, '.Response_ForgotPasswordRequest.ext': resp }); + invokeOnSuccess(resp, { responseCode: 0 }); expect(SessionPersistence.resetPassword).toHaveBeenCalled(); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); }); @@ -454,7 +467,7 @@ describe('forgotPasswordReset', () => { it('sends Command_ForgotPasswordReset with plain newPassword when no salt', () => { forgotPasswordReset({ userName: 'alice', token: 'tok' } as any, 'newpw'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_ForgotPasswordReset', + Command_ForgotPasswordReset_ext, expect.objectContaining({ newPassword: 'newpw' }), expect.any(Object) ); @@ -463,7 +476,7 @@ describe('forgotPasswordReset', () => { it('sends hashed new password when salt provided', () => { forgotPasswordReset({ userName: 'alice', token: 'tok' } as any, 'newpw', 'salt'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_ForgotPasswordReset', + Command_ForgotPasswordReset_ext, expect.objectContaining({ hashedNewPassword: 'hashed_pw' }), expect.any(Object) ); @@ -491,40 +504,44 @@ describe('requestPasswordSalt', () => { it('sends Command_RequestPasswordSalt', () => { requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any, 'pw'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_RequestPasswordSalt', expect.any(Object), expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + Command_RequestPasswordSalt_ext, + expect.any(Object), + expect.objectContaining({ responseExt: Response_PasswordSalt_ext }) + ); }); it('onSuccess with LOGIN reason forwards password+salt to login', () => { requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any, 'pw'); const resp = { passwordSalt: 'salt123' }; - invokeOnSuccess(resp, { responseCode: 0, '.Response_PasswordSalt.ext': resp }); + invokeOnSuccess(resp, { responseCode: 0 }); expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'pw', 'salt123'); }); it('onSuccess with ACTIVATE_ACCOUNT reason forwards password+salt to activate', () => { requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.ACTIVATE_ACCOUNT } as any, 'pw'); const resp = { passwordSalt: 'salt123' }; - invokeOnSuccess(resp, { responseCode: 0, '.Response_PasswordSalt.ext': resp }); + invokeOnSuccess(resp, { responseCode: 0 }); expect(SessionIndexMocks.activate).toHaveBeenCalledWith(expect.any(Object), 'pw', 'salt123'); }); it('onSuccess with PASSWORD_RESET reason forwards newPassword+salt to forgotPasswordReset', () => { requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.PASSWORD_RESET } as any, undefined, 'newpw'); const resp = { passwordSalt: 'salt123' }; - invokeOnSuccess(resp, { responseCode: 0, '.Response_PasswordSalt.ext': resp }); + invokeOnSuccess(resp, { responseCode: 0 }); expect(SessionIndexMocks.forgotPasswordReset).toHaveBeenCalledWith(expect.any(Object), 'newpw', 'salt123'); }); it('onResponseCode RespRegistrationRequired calls updateStatus and disconnect', () => { requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any, 'pw'); - invokeResponseCode(6); + invokeResponseCode(Response_ResponseCode.RespRegistrationRequired); expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, expect.any(String)); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); }); it('onResponseCode RespRegistrationRequired with ACTIVATE_ACCOUNT calls accountActivationFailed', () => { requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.ACTIVATE_ACCOUNT } as any, 'pw'); - invokeResponseCode(6); + invokeResponseCode(Response_ResponseCode.RespRegistrationRequired); expect(SessionPersistence.accountActivationFailed).toHaveBeenCalled(); }); diff --git a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts index c6daa0491..4cd8bdce4 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts @@ -19,11 +19,6 @@ vi.mock('../../WebClient', async () => { return { __esModule: true, default: makeWebClientMock() }; }); -vi.mock('../../services/ProtoController', async () => { - const { makeProtoControllerRootMock } = await import('../../__mocks__/sessionCommandMocks'); - return { ProtoController: { root: makeProtoControllerRootMock() } }; -}); - vi.mock('../../utils', async () => { const { makeUtilsMock } = await import('../../__mocks__/sessionCommandMocks'); return makeUtilsMock(); @@ -43,6 +38,38 @@ import { RoomPersistence } from '../../persistence'; import webClient from '../../WebClient'; import * as SessionCommands from './'; import { hashPassword, generateSalt, passwordSaltSupported } from '../../utils'; +import { + Command_AccountEdit_ext, + Command_AccountImage_ext, + Command_AccountPassword_ext, + Command_AddToList_ext, + Command_GetGamesOfUser_ext, + Command_GetUserInfo_ext, + Command_JoinRoom_ext, + Command_ListRooms_ext, + Command_ListUsers_ext, + Command_Message_ext, + Command_Ping_ext, + Command_RemoveFromList_ext, +} from 'generated/proto/session_commands_pb'; +import { Command_DeckDel_ext } from 'generated/proto/command_deck_del_pb'; +import { Command_DeckDelDir_ext } from 'generated/proto/command_deck_del_dir_pb'; +import { Command_DeckList_ext } from 'generated/proto/command_deck_list_pb'; +import { Command_DeckNewDir_ext } from 'generated/proto/command_deck_new_dir_pb'; +import { Command_DeckUpload_ext } from 'generated/proto/command_deck_upload_pb'; +import { Command_ReplayDeleteMatch_ext } from 'generated/proto/command_replay_delete_match_pb'; +import { Command_ReplayGetCode_ext } from 'generated/proto/command_replay_get_code_pb'; +import { Command_ReplayList_ext } from 'generated/proto/command_replay_list_pb'; +import { Command_ReplayModifyMatch_ext } from 'generated/proto/command_replay_modify_match_pb'; +import { Command_ReplaySubmitCode_ext } from 'generated/proto/command_replay_submit_code_pb'; +import { Response_DeckList_ext } from 'generated/proto/response_deck_list_pb'; +import { Response_DeckUpload_ext } from 'generated/proto/response_deck_upload_pb'; +import { Response_GetGamesOfUser_ext } from 'generated/proto/response_get_games_of_user_pb'; +import { Response_GetUserInfo_ext } from 'generated/proto/response_get_user_info_pb'; +import { Response_JoinRoom_ext } from 'generated/proto/response_join_room_pb'; +import { Response_ListUsers_ext } from 'generated/proto/response_list_users_pb'; +import { Response_ReplayGetCode_ext } from 'generated/proto/response_replay_get_code_pb'; +import { Response_ReplayList_ext } from 'generated/proto/response_replay_list_pb'; import { accountEdit } from './accountEdit'; import { accountImage } from './accountImage'; import { accountPassword } from './accountPassword'; @@ -68,7 +95,8 @@ import { replayGetCode } from './replayGetCode'; import { replaySubmitCode } from './replaySubmitCode'; const { invokeOnSuccess, invokeCallback } = makeCallbackHelpers( - BackendService.sendSessionCommand as vi.Mock + BackendService.sendSessionCommand as vi.Mock, + 2 ); beforeEach(() => { @@ -86,8 +114,8 @@ describe('accountEdit', () => { it('sends Command_AccountEdit with correct params', () => { accountEdit('pw', 'Alice', 'a@b.com', 'US'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_AccountEdit', - { passwordCheck: 'pw', realName: 'Alice', email: 'a@b.com', country: 'US' }, + Command_AccountEdit_ext, + expect.objectContaining({ passwordCheck: 'pw', realName: 'Alice', email: 'a@b.com', country: 'US' }), expect.any(Object) ); }); @@ -105,7 +133,9 @@ describe('accountImage', () => { it('sends Command_AccountImage', () => { const img = new Uint8Array([1, 2]); accountImage(img); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_AccountImage', { image: img }, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + Command_AccountImage_ext, expect.objectContaining({ image: img }), expect.any(Object) + ); }); it('calls SessionPersistence.accountImageChanged on success', () => { @@ -122,8 +152,8 @@ describe('accountPassword', () => { it('sends Command_AccountPassword', () => { accountPassword('old', 'new', 'hashed'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_AccountPassword', - { oldPassword: 'old', newPassword: 'new', hashedNewPassword: 'hashed' }, + Command_AccountPassword_ext, + expect.objectContaining({ oldPassword: 'old', newPassword: 'new', hashedNewPassword: 'hashed' }), expect.any(Object) ); }); @@ -140,7 +170,11 @@ describe('deckDel', () => { it('sends Command_DeckDel', () => { deckDel(42); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_DeckDel', { deckId: 42 }, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + Command_DeckDel_ext, + expect.objectContaining({ deckId: 42 }), + expect.any(Object) + ); }); it('calls deleteServerDeck on success', () => { @@ -155,7 +189,9 @@ describe('deckDelDir', () => { it('sends Command_DeckDelDir', () => { deckDelDir('/path'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_DeckDelDir', { path: '/path' }, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + Command_DeckDelDir_ext, expect.objectContaining({ path: '/path' }), expect.any(Object) + ); }); it('calls deleteServerDeckDir on success', () => { @@ -170,13 +206,17 @@ describe('deckList', () => { it('sends Command_DeckList', () => { deckList(); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_DeckList', {}, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + Command_DeckList_ext, + expect.any(Object), + expect.objectContaining({ responseExt: Response_DeckList_ext }) + ); }); it('calls updateServerDecks on success', () => { deckList(); const resp = { folders: [] }; - invokeOnSuccess(resp, { responseCode: 0, '.Response_DeckList.ext': resp }); + invokeOnSuccess(resp, { responseCode: 0 }); expect(SessionPersistence.updateServerDecks).toHaveBeenCalledWith(resp); }); }); @@ -187,7 +227,7 @@ describe('deckNewDir', () => { it('sends Command_DeckNewDir', () => { deckNewDir('/path', 'dir'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_DeckNewDir', { path: '/path', dirName: 'dir' }, expect.any(Object) + Command_DeckNewDir_ext, expect.objectContaining({ path: '/path', dirName: 'dir' }), expect.any(Object) ); }); @@ -204,16 +244,16 @@ describe('deckUpload', () => { it('sends Command_DeckUpload', () => { deckUpload('/path', 1, 'content'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_DeckUpload', - { path: '/path', deckId: 1, deckList: 'content' }, - expect.any(Object) + Command_DeckUpload_ext, + expect.objectContaining({ path: '/path', deckId: 1, deckList: 'content' }), + expect.objectContaining({ responseExt: Response_DeckUpload_ext }) ); }); it('calls uploadServerDeck on success', () => { deckUpload('/path', 1, 'content'); const resp = { newFile: { id: 1 } }; - invokeOnSuccess(resp, { responseCode: 0, '.Response_DeckUpload.ext': resp }); + invokeOnSuccess(resp, { responseCode: 0 }); expect(SessionPersistence.uploadServerDeck).toHaveBeenCalledWith('/path', resp.newFile); }); }); @@ -232,13 +272,17 @@ describe('getGamesOfUser', () => { it('sends Command_GetGamesOfUser', () => { getGamesOfUser('alice'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_GetGamesOfUser', { userName: 'alice' }, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + Command_GetGamesOfUser_ext, + expect.any(Object), + expect.objectContaining({ responseExt: Response_GetGamesOfUser_ext }) + ); }); it('calls getGamesOfUser on success', () => { getGamesOfUser('alice'); const resp = { gameList: [] }; - invokeOnSuccess(resp, { responseCode: 0, '.Response_GetGamesOfUser.ext': resp }); + invokeOnSuccess(resp, { responseCode: 0 }); expect(SessionPersistence.getGamesOfUser).toHaveBeenCalledWith('alice', resp); }); }); @@ -248,13 +292,17 @@ describe('getUserInfo', () => { it('sends Command_GetUserInfo', () => { getUserInfo('alice'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_GetUserInfo', { userName: 'alice' }, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + Command_GetUserInfo_ext, + expect.any(Object), + expect.objectContaining({ responseExt: Response_GetUserInfo_ext }) + ); }); it('calls getUserInfo on success', () => { getUserInfo('alice'); const resp = { userInfo: { name: 'alice' } }; - invokeOnSuccess(resp, { responseCode: 0, '.Response_GetUserInfo.ext': resp }); + invokeOnSuccess(resp, { responseCode: 0 }); expect(SessionPersistence.getUserInfo).toHaveBeenCalledWith(resp.userInfo); }); }); @@ -264,13 +312,17 @@ describe('joinRoom', () => { it('sends Command_JoinRoom', () => { joinRoom(5); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_JoinRoom', { roomId: 5 }, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + Command_JoinRoom_ext, + expect.any(Object), + expect.objectContaining({ responseExt: Response_JoinRoom_ext }) + ); }); it('calls RoomPersistence.joinRoom on success', () => { joinRoom(5); const resp = { roomInfo: { roomId: 5 } }; - invokeOnSuccess(resp, { responseCode: 0, '.Response_JoinRoom.ext': resp }); + invokeOnSuccess(resp, { responseCode: 0 }); expect(RoomPersistence.joinRoom).toHaveBeenCalledWith(resp.roomInfo); }); }); @@ -280,7 +332,7 @@ describe('listRooms (command)', () => { it('sends Command_ListRooms', () => { listRooms(); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ListRooms', {}, {}); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(Command_ListRooms_ext, expect.any(Object)); }); }); @@ -289,13 +341,17 @@ describe('listUsers', () => { it('sends Command_ListUsers', () => { listUsers(); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ListUsers', {}, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + Command_ListUsers_ext, + expect.any(Object), + expect.objectContaining({ responseExt: Response_ListUsers_ext }) + ); }); it('calls SessionPersistence.updateUsers with the user list on success', () => { listUsers(); const resp = { userList: [{ name: 'Alice' }] }; - invokeOnSuccess(resp, { responseCode: 0, '.Response_ListUsers.ext': resp }); + invokeOnSuccess(resp, { responseCode: 0 }); expect(SessionPersistence.updateUsers).toHaveBeenCalledWith([{ name: 'Alice' }]); }); }); @@ -306,7 +362,7 @@ describe('message', () => { it('sends Command_Message', () => { message('bob', 'hi'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_Message', { userName: 'bob', message: 'hi' }, expect.any(Object) + Command_Message_ext, expect.objectContaining({ userName: 'bob', message: 'hi' }) ); }); @@ -318,7 +374,7 @@ describe('ping', () => { it('sends Command_Ping', () => { const pingReceived = vi.fn(); ping(pingReceived); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_Ping', {}, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(Command_Ping_ext, expect.any(Object), expect.any(Object)); }); it('calls pingReceived via onResponse', () => { @@ -335,7 +391,11 @@ describe('replayDeleteMatch', () => { it('sends Command_ReplayDeleteMatch', () => { replayDeleteMatch(7); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ReplayDeleteMatch', { gameId: 7 }, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + Command_ReplayDeleteMatch_ext, + expect.objectContaining({ gameId: 7 }), + expect.any(Object) + ); }); it('calls replayDeleteMatch on success', () => { @@ -350,13 +410,17 @@ describe('replayList', () => { it('sends Command_ReplayList', () => { replayList(); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ReplayList', {}, expect.any(Object)); + expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + Command_ReplayList_ext, + expect.any(Object), + expect.objectContaining({ responseExt: Response_ReplayList_ext }) + ); }); it('calls replayList on success', () => { replayList(); const resp = { matchList: [] }; - invokeOnSuccess(resp, { responseCode: 0, '.Response_ReplayList.ext': resp }); + invokeOnSuccess(resp, { responseCode: 0 }); expect(SessionPersistence.replayList).toHaveBeenCalledWith([]); }); }); @@ -367,7 +431,7 @@ describe('replayModifyMatch', () => { it('sends Command_ReplayModifyMatch', () => { replayModifyMatch(7, true); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_ReplayModifyMatch', { gameId: 7, doNotHide: true }, expect.any(Object) + Command_ReplayModifyMatch_ext, expect.objectContaining({ gameId: 7, doNotHide: true }), expect.any(Object) ); }); @@ -384,14 +448,18 @@ describe('addToList / addToBuddyList / addToIgnoreList', () => { it('addToBuddyList sends Command_AddToList with list=buddy', () => { addToBuddyList('alice'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_AddToList', { list: 'buddy', userName: 'alice' }, expect.any(Object) + Command_AddToList_ext, + expect.objectContaining({ list: 'buddy' }), + expect.any(Object) ); }); it('addToIgnoreList sends Command_AddToList with list=ignore', () => { addToIgnoreList('bob'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_AddToList', { list: 'ignore', userName: 'bob' }, expect.any(Object) + Command_AddToList_ext, + expect.objectContaining({ list: 'ignore' }), + expect.any(Object) ); }); @@ -408,14 +476,18 @@ describe('removeFromList / removeFromBuddyList / removeFromIgnoreList', () => { it('removeFromBuddyList sends Command_RemoveFromList with list=buddy', () => { removeFromBuddyList('alice'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_RemoveFromList', { list: 'buddy', userName: 'alice' }, expect.any(Object) + Command_RemoveFromList_ext, + expect.objectContaining({ list: 'buddy' }), + expect.any(Object) ); }); it('removeFromIgnoreList sends Command_RemoveFromList with list=ignore', () => { removeFromIgnoreList('bob'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_RemoveFromList', { list: 'ignore', userName: 'bob' }, expect.any(Object) + Command_RemoveFromList_ext, + expect.objectContaining({ list: 'ignore' }), + expect.any(Object) ); }); @@ -429,12 +501,12 @@ describe('removeFromList / removeFromBuddyList / removeFromIgnoreList', () => { describe('replayGetCode', () => { beforeEach(() => vi.clearAllMocks()); - it('sends Command_ReplayGetCode with gameId and responseName', () => { + it('sends Command_ReplayGetCode with gameId and responseExt', () => { replayGetCode(42, vi.fn()); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_ReplayGetCode', - { gameId: 42 }, - expect.objectContaining({ responseName: 'Response_ReplayGetCode' }) + Command_ReplayGetCode_ext, + expect.any(Object), + expect.objectContaining({ responseExt: Response_ReplayGetCode_ext }) ); }); @@ -452,9 +524,7 @@ describe('replaySubmitCode', () => { it('sends Command_ReplaySubmitCode with replayCode', () => { replaySubmitCode('42-abc123'); expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( - 'Command_ReplaySubmitCode', - { replayCode: '42-abc123' }, - expect.any(Object) + Command_ReplaySubmitCode_ext, expect.objectContaining({ replayCode: '42-abc123' }), expect.any(Object) ); }); diff --git a/webclient/src/websocket/events/common/commonEvents.spec.ts b/webclient/src/websocket/events/common/commonEvents.spec.ts index 7dd74c37d..cdfaa86dc 100644 --- a/webclient/src/websocket/events/common/commonEvents.spec.ts +++ b/webclient/src/websocket/events/common/commonEvents.spec.ts @@ -2,6 +2,6 @@ import { CommonEvents } from './index'; describe('CommonEvents', () => { it('is an empty event map (all common events were moved to game/session events)', () => { - expect(CommonEvents).toEqual({}); + expect(CommonEvents).toEqual([]); }); }); diff --git a/webclient/src/websocket/events/common/index.ts b/webclient/src/websocket/events/common/index.ts index 305171fbc..93d40e0e6 100644 --- a/webclient/src/websocket/events/common/index.ts +++ b/webclient/src/websocket/events/common/index.ts @@ -1,3 +1,3 @@ -import { ProtobufEvents } from '../../services/ProtobufService'; +import { ExtensionRegistry } from '../../services/ProtobufService'; -export const CommonEvents: ProtobufEvents = {}; +export const CommonEvents: ExtensionRegistry = []; diff --git a/webclient/src/websocket/events/game/index.ts b/webclient/src/websocket/events/game/index.ts index aa4be6391..64c26b153 100644 --- a/webclient/src/websocket/events/game/index.ts +++ b/webclient/src/websocket/events/game/index.ts @@ -1,4 +1,4 @@ -import { ProtobufEvents } from '../../services/ProtobufService'; +import { ExtensionRegistry } from '../../services/ProtobufService'; import { attachCard } from './attachCard'; import { changeZoneProperties } from './changeZoneProperties'; import { createArrow } from './createArrow'; @@ -29,34 +29,65 @@ import { setCardCounter } from './setCardCounter'; import { setCounter } from './setCounter'; import { shuffle } from './shuffle'; -export const GameEvents: ProtobufEvents = { - '.Event_Join.ext': joinGame, - '.Event_Leave.ext': leaveGame, - '.Event_GameClosed.ext': gameClosed, - '.Event_GameHostChanged.ext': gameHostChanged, - '.Event_Kicked.ext': kicked, - '.Event_GameStateChanged.ext': gameStateChanged, - '.Event_PlayerPropertiesChanged.ext': playerPropertiesChanged, - '.Event_GameSay.ext': gameSay, - '.Event_CreateArrow.ext': createArrow, - '.Event_DeleteArrow.ext': deleteArrow, - '.Event_CreateCounter.ext': createCounter, - '.Event_SetCounter.ext': setCounter, - '.Event_DelCounter.ext': delCounter, - '.Event_DrawCards.ext': drawCards, - '.Event_RevealCards.ext': revealCards, - '.Event_Shuffle.ext': shuffle, - '.Event_RollDie.ext': rollDie, - '.Event_MoveCard.ext': moveCard, - '.Event_FlipCard.ext': flipCard, - '.Event_DestroyCard.ext': destroyCard, - '.Event_AttachCard.ext': attachCard, - '.Event_CreateToken.ext': createToken, - '.Event_SetCardAttr.ext': setCardAttr, - '.Event_SetCardCounter.ext': setCardCounter, - '.Event_SetActivePlayer.ext': setActivePlayer, - '.Event_SetActivePhase.ext': setActivePhase, - '.Event_DumpZone.ext': dumpZone, - '.Event_ChangeZoneProperties.ext': changeZoneProperties, - '.Event_ReverseTurn.ext': reverseTurn, -}; +import { Event_Join_ext } from 'generated/proto/event_join_pb'; +import { Event_Leave_ext } from 'generated/proto/event_leave_pb'; +import { Event_GameClosed_ext } from 'generated/proto/event_game_closed_pb'; +import { Event_GameHostChanged_ext } from 'generated/proto/event_game_host_changed_pb'; +import { Event_Kicked_ext } from 'generated/proto/event_kicked_pb'; +import { Event_GameStateChanged_ext } from 'generated/proto/event_game_state_changed_pb'; +import { Event_PlayerPropertiesChanged_ext } from 'generated/proto/event_player_properties_changed_pb'; +import { Event_GameSay_ext } from 'generated/proto/event_game_say_pb'; +import { Event_CreateArrow_ext } from 'generated/proto/event_create_arrow_pb'; +import { Event_DeleteArrow_ext } from 'generated/proto/event_delete_arrow_pb'; +import { Event_CreateCounter_ext } from 'generated/proto/event_create_counter_pb'; +import { Event_SetCounter_ext } from 'generated/proto/event_set_counter_pb'; +import { Event_DelCounter_ext } from 'generated/proto/event_del_counter_pb'; +import { Event_DrawCards_ext } from 'generated/proto/event_draw_cards_pb'; +import { Event_RevealCards_ext } from 'generated/proto/event_reveal_cards_pb'; +import { Event_Shuffle_ext } from 'generated/proto/event_shuffle_pb'; +import { Event_RollDie_ext } from 'generated/proto/event_roll_die_pb'; +import { Event_MoveCard_ext } from 'generated/proto/event_move_card_pb'; +import { Event_FlipCard_ext } from 'generated/proto/event_flip_card_pb'; +import { Event_DestroyCard_ext } from 'generated/proto/event_destroy_card_pb'; +import { Event_AttachCard_ext } from 'generated/proto/event_attach_card_pb'; +import { Event_CreateToken_ext } from 'generated/proto/event_create_token_pb'; +import { Event_SetCardAttr_ext } from 'generated/proto/event_set_card_attr_pb'; +import { Event_SetCardCounter_ext } from 'generated/proto/event_set_card_counter_pb'; +import { Event_SetActivePlayer_ext } from 'generated/proto/event_set_active_player_pb'; +import { Event_SetActivePhase_ext } from 'generated/proto/event_set_active_phase_pb'; +import { Event_DumpZone_ext } from 'generated/proto/event_dump_zone_pb'; +import { Event_ChangeZoneProperties_ext } from 'generated/proto/event_change_zone_properties_pb'; +import { Event_ReverseTurn_ext } from 'generated/proto/event_reverse_turn_pb'; + +export const GameEvents: ExtensionRegistry = [ + [Event_Join_ext, joinGame], + [Event_Leave_ext, leaveGame], + [Event_GameClosed_ext, gameClosed], + [Event_GameHostChanged_ext, gameHostChanged], + [Event_Kicked_ext, kicked], + [Event_GameStateChanged_ext, gameStateChanged], + [Event_PlayerPropertiesChanged_ext, playerPropertiesChanged], + [Event_GameSay_ext, gameSay], + [Event_CreateArrow_ext, createArrow], + [Event_DeleteArrow_ext, deleteArrow], + [Event_CreateCounter_ext, createCounter], + [Event_SetCounter_ext, setCounter], + [Event_DelCounter_ext, delCounter], + [Event_DrawCards_ext, drawCards], + [Event_RevealCards_ext, revealCards], + [Event_Shuffle_ext, shuffle], + [Event_RollDie_ext, rollDie], + [Event_MoveCard_ext, moveCard], + [Event_FlipCard_ext, flipCard], + [Event_DestroyCard_ext, destroyCard], + [Event_AttachCard_ext, attachCard], + [Event_CreateToken_ext, createToken], + [Event_SetCardAttr_ext, setCardAttr], + [Event_SetCardCounter_ext, setCardCounter], + [Event_SetActivePlayer_ext, setActivePlayer], + [Event_SetActivePhase_ext, setActivePhase], + [Event_DumpZone_ext, dumpZone], + [Event_ChangeZoneProperties_ext, changeZoneProperties], + [Event_ReverseTurn_ext, reverseTurn], +]; + diff --git a/webclient/src/websocket/events/room/index.ts b/webclient/src/websocket/events/room/index.ts index 5b571d388..366d68476 100644 --- a/webclient/src/websocket/events/room/index.ts +++ b/webclient/src/websocket/events/room/index.ts @@ -1,4 +1,4 @@ -import { ProtobufEvents } from '../../services/ProtobufService'; +import { ExtensionRegistry } from '../../services/ProtobufService'; import { joinRoom } from './joinRoom'; import { leaveRoom } from './leaveRoom'; @@ -6,10 +6,17 @@ import { listGames } from './listGames'; import { roomSay } from './roomSay'; import { removeMessages } from './removeMessages'; -export const RoomEvents: ProtobufEvents = { - '.Event_JoinRoom.ext': joinRoom, - '.Event_LeaveRoom.ext': leaveRoom, - '.Event_ListGames.ext': listGames, - '.Event_RemoveMessages.ext': removeMessages, - '.Event_RoomSay.ext': roomSay, -}; +import { Event_JoinRoom_ext } from 'generated/proto/event_join_room_pb'; +import { Event_LeaveRoom_ext } from 'generated/proto/event_leave_room_pb'; +import { Event_ListGames_ext } from 'generated/proto/event_list_games_pb'; +import { Event_RemoveMessages_ext } from 'generated/proto/event_remove_messages_pb'; +import { Event_RoomSay_ext } from 'generated/proto/event_room_say_pb'; + +export const RoomEvents: ExtensionRegistry = [ + [Event_JoinRoom_ext, joinRoom], + [Event_LeaveRoom_ext, leaveRoom], + [Event_ListGames_ext, listGames], + [Event_RemoveMessages_ext, removeMessages], + [Event_RoomSay_ext, roomSay], +]; + diff --git a/webclient/src/websocket/events/room/interfaces.ts b/webclient/src/websocket/events/room/interfaces.ts index b3f922141..cb7086555 100644 --- a/webclient/src/websocket/events/room/interfaces.ts +++ b/webclient/src/websocket/events/room/interfaces.ts @@ -1,24 +1,11 @@ -import { Game, User } from 'types'; +import type { Event_JoinRoom } from 'generated/proto/event_join_room_pb'; +import type { Event_LeaveRoom } from 'generated/proto/event_leave_room_pb'; +import type { Event_ListGames } from 'generated/proto/event_list_games_pb'; +import type { Event_RemoveMessages } from 'generated/proto/event_remove_messages_pb'; +import type { RoomEvent as GeneratedRoomEvent } from 'generated/proto/room_event_pb'; -export interface JoinRoomData { - userInfo: User; -} - -export interface LeaveRoomData { - name: string; -} - -export interface ListGamesData { - gameList: Game[]; -} - -export interface RemoveMessagesData { - name: string; - amount: number; -} - -export interface RoomEvent { - roomEvent: { - roomId: number; - } -} +export type JoinRoomData = Event_JoinRoom; +export type LeaveRoomData = Event_LeaveRoom; +export type ListGamesData = Event_ListGames; +export type RemoveMessagesData = Event_RemoveMessages; +export type RoomEvent = GeneratedRoomEvent; diff --git a/webclient/src/websocket/events/room/joinRoom.ts b/webclient/src/websocket/events/room/joinRoom.ts index b1a5f6606..f45ff18ab 100644 --- a/webclient/src/websocket/events/room/joinRoom.ts +++ b/webclient/src/websocket/events/room/joinRoom.ts @@ -1,6 +1,6 @@ import { RoomPersistence } from '../../persistence'; import { JoinRoomData, RoomEvent } from './interfaces'; -export function joinRoom({ userInfo }: JoinRoomData, { roomEvent: { roomId } }: RoomEvent): void { +export function joinRoom({ userInfo }: JoinRoomData, { roomId }: RoomEvent): void { RoomPersistence.userJoined(roomId, userInfo); } diff --git a/webclient/src/websocket/events/room/leaveRoom.ts b/webclient/src/websocket/events/room/leaveRoom.ts index 6d45197fc..c7564d458 100644 --- a/webclient/src/websocket/events/room/leaveRoom.ts +++ b/webclient/src/websocket/events/room/leaveRoom.ts @@ -1,6 +1,6 @@ import { RoomPersistence } from '../../persistence'; import { LeaveRoomData, RoomEvent } from './interfaces'; -export function leaveRoom({ name }: LeaveRoomData, { roomEvent: { roomId } }: RoomEvent): void { +export function leaveRoom({ name }: LeaveRoomData, { roomId }: RoomEvent): void { RoomPersistence.userLeft(roomId, name); } diff --git a/webclient/src/websocket/events/room/listGames.ts b/webclient/src/websocket/events/room/listGames.ts index d460a5336..0f1f20438 100644 --- a/webclient/src/websocket/events/room/listGames.ts +++ b/webclient/src/websocket/events/room/listGames.ts @@ -1,6 +1,6 @@ import { RoomPersistence } from '../../persistence'; import { ListGamesData, RoomEvent } from './interfaces'; -export function listGames({ gameList }: ListGamesData, { roomEvent: { roomId } }: RoomEvent): void { +export function listGames({ gameList }: ListGamesData, { roomId }: RoomEvent): void { RoomPersistence.updateGames(roomId, gameList); } diff --git a/webclient/src/websocket/events/room/removeMessages.ts b/webclient/src/websocket/events/room/removeMessages.ts index 4fe01cb6f..859470c81 100644 --- a/webclient/src/websocket/events/room/removeMessages.ts +++ b/webclient/src/websocket/events/room/removeMessages.ts @@ -1,6 +1,6 @@ import { RoomPersistence } from '../../persistence'; import { RemoveMessagesData, RoomEvent } from './interfaces'; -export function removeMessages({ name, amount }: RemoveMessagesData, { roomEvent: { roomId } }: RoomEvent): void { +export function removeMessages({ name, amount }: RemoveMessagesData, { roomId }: RoomEvent): void { RoomPersistence.removeMessages(roomId, name, amount); } diff --git a/webclient/src/websocket/events/room/roomEvents.spec.ts b/webclient/src/websocket/events/room/roomEvents.spec.ts index c554128cc..f2e4eee8f 100644 --- a/webclient/src/websocket/events/room/roomEvents.spec.ts +++ b/webclient/src/websocket/events/room/roomEvents.spec.ts @@ -15,7 +15,7 @@ import { listGames } from './listGames'; import { removeMessages } from './removeMessages'; import { roomSay } from './roomSay'; -const makeRoomEvent = (roomId: number) => ({ roomEvent: { roomId } }); +const makeRoomEvent = (roomId: number) => ({ roomId }) as any; beforeEach(() => vi.clearAllMocks()); diff --git a/webclient/src/websocket/events/room/roomSay.ts b/webclient/src/websocket/events/room/roomSay.ts index 5a96198ea..9522248da 100644 --- a/webclient/src/websocket/events/room/roomSay.ts +++ b/webclient/src/websocket/events/room/roomSay.ts @@ -3,6 +3,6 @@ import { Message } from 'types'; import { RoomPersistence } from '../../persistence'; import { RoomEvent } from './interfaces'; -export function roomSay(message: Message, { roomEvent: { roomId } }: RoomEvent): void { +export function roomSay(message: Message, { roomId }: RoomEvent): void { RoomPersistence.addMessage(roomId, message); } diff --git a/webclient/src/websocket/events/session/connectionClosed.ts b/webclient/src/websocket/events/session/connectionClosed.ts index 796f3ab80..c98080f5f 100644 --- a/webclient/src/websocket/events/session/connectionClosed.ts +++ b/webclient/src/websocket/events/session/connectionClosed.ts @@ -1,5 +1,5 @@ import { StatusEnum } from 'types'; -import { ProtoController } from '../../services/ProtoController'; +import { Event_ConnectionClosed_CloseReason } from 'generated/proto/event_connection_closed_pb'; import { updateStatus } from '../../commands/session'; import { ConnectionClosedData } from './interfaces'; @@ -10,32 +10,31 @@ export function connectionClosed({ reason, reasonStr, endTime }: ConnectionClose if (reasonStr) { message = reasonStr; } else { - const { CloseReason } = ProtoController.root.Event_ConnectionClosed; switch (reason) { - case CloseReason.USER_LIMIT_REACHED: + case Event_ConnectionClosed_CloseReason.USER_LIMIT_REACHED: message = 'The server has reached its maximum user capacity'; break; - case CloseReason.TOO_MANY_CONNECTIONS: + case Event_ConnectionClosed_CloseReason.TOO_MANY_CONNECTIONS: message = 'There are too many concurrent connections from your address'; break; - case CloseReason.BANNED: + case Event_ConnectionClosed_CloseReason.BANNED: message = typeof endTime === 'number' && endTime > 0 && Number.isFinite(endTime) ? `You are banned until ${new Date(endTime * 1000).toLocaleString()}` : 'You are banned'; break; - case CloseReason.DEMOTED: + case Event_ConnectionClosed_CloseReason.DEMOTED: message = 'You were demoted'; break; - case CloseReason.SERVER_SHUTDOWN: + case Event_ConnectionClosed_CloseReason.SERVER_SHUTDOWN: message = 'Scheduled server shutdown'; break; - case CloseReason.USERNAMEINVALID: + case Event_ConnectionClosed_CloseReason.USERNAMEINVALID: message = 'Invalid username'; break; - case CloseReason.LOGGEDINELSEWERE: + case Event_ConnectionClosed_CloseReason.LOGGEDINELSEWERE: message = 'You have been logged out due to logging in at another location'; break; - case CloseReason.OTHER: + case Event_ConnectionClosed_CloseReason.OTHER: default: message = 'Unknown reason'; break; diff --git a/webclient/src/websocket/events/session/index.ts b/webclient/src/websocket/events/session/index.ts index 5b3ab198e..6c34f0dfd 100644 --- a/webclient/src/websocket/events/session/index.ts +++ b/webclient/src/websocket/events/session/index.ts @@ -1,4 +1,4 @@ -import { ProtobufEvents } from '../../services/ProtobufService'; +import { ExtensionRegistry } from '../../services/ProtobufService'; import { addToList } from './addToList'; import { connectionClosed } from './connectionClosed'; import { listRooms } from './listRooms'; @@ -14,19 +14,35 @@ import { userLeft } from './userLeft'; import { userMessage } from './userMessage'; import { gameJoined } from './gameJoined'; -export const SessionEvents: ProtobufEvents = { - '.Event_AddToList.ext': addToList, - '.Event_ConnectionClosed.ext': connectionClosed, - '.Event_GameJoined.ext': gameJoined, - '.Event_ListRooms.ext': listRooms, - '.Event_NotifyUser.ext': notifyUser, - '.Event_RemoveFromList.ext': removeFromList, - '.Event_ReplayAdded.ext': replayAdded, - '.Event_ServerCompleteList.ext': serverCompleteList, - '.Event_ServerIdentification.ext': serverIdentification, - '.Event_ServerMessage.ext': serverMessage, - '.Event_ServerShutdown.ext': serverShutdown, - '.Event_UserJoined.ext': userJoined, - '.Event_UserLeft.ext': userLeft, - '.Event_UserMessage.ext': userMessage, -} +import { Event_AddToList_ext } from 'generated/proto/event_add_to_list_pb'; +import { Event_ConnectionClosed_ext } from 'generated/proto/event_connection_closed_pb'; +import { Event_GameJoined_ext } from 'generated/proto/event_game_joined_pb'; +import { Event_ListRooms_ext } from 'generated/proto/event_list_rooms_pb'; +import { Event_NotifyUser_ext } from 'generated/proto/event_notify_user_pb'; +import { Event_RemoveFromList_ext } from 'generated/proto/event_remove_from_list_pb'; +import { Event_ReplayAdded_ext } from 'generated/proto/event_replay_added_pb'; +import { Event_ServerCompleteList_ext } from 'generated/proto/event_server_complete_list_pb'; +import { Event_ServerIdentification_ext } from 'generated/proto/event_server_identification_pb'; +import { Event_ServerMessage_ext } from 'generated/proto/event_server_message_pb'; +import { Event_ServerShutdown_ext } from 'generated/proto/event_server_shutdown_pb'; +import { Event_UserJoined_ext } from 'generated/proto/event_user_joined_pb'; +import { Event_UserLeft_ext } from 'generated/proto/event_user_left_pb'; +import { Event_UserMessage_ext } from 'generated/proto/event_user_message_pb'; + +export const SessionEvents: ExtensionRegistry = [ + [Event_AddToList_ext, addToList], + [Event_ConnectionClosed_ext, connectionClosed], + [Event_GameJoined_ext, gameJoined], + [Event_ListRooms_ext, listRooms], + [Event_NotifyUser_ext, notifyUser], + [Event_RemoveFromList_ext, removeFromList], + [Event_ReplayAdded_ext, replayAdded], + [Event_ServerCompleteList_ext, serverCompleteList], + [Event_ServerIdentification_ext, serverIdentification], + [Event_ServerMessage_ext, serverMessage], + [Event_ServerShutdown_ext, serverShutdown], + [Event_UserJoined_ext, userJoined], + [Event_UserLeft_ext, userLeft], + [Event_UserMessage_ext, userMessage], +]; + diff --git a/webclient/src/websocket/events/session/interfaces.ts b/webclient/src/websocket/events/session/interfaces.ts index ddc10d103..fcd1fd1b7 100644 --- a/webclient/src/websocket/events/session/interfaces.ts +++ b/webclient/src/websocket/events/session/interfaces.ts @@ -1,90 +1,31 @@ -import { Game, NotificationType, ReplayMatch, Room, User } from 'types'; +import type { Event_AddToList } from 'generated/proto/event_add_to_list_pb'; +import type { Event_ConnectionClosed } from 'generated/proto/event_connection_closed_pb'; +import type { Event_GameJoined } from 'generated/proto/event_game_joined_pb'; +import type { Event_ListRooms } from 'generated/proto/event_list_rooms_pb'; +import type { Event_NotifyUser } from 'generated/proto/event_notify_user_pb'; +import type { Event_RemoveFromList } from 'generated/proto/event_remove_from_list_pb'; +import type { Event_ReplayAdded } from 'generated/proto/event_replay_added_pb'; +import type { Event_ServerCompleteList } from 'generated/proto/event_server_complete_list_pb'; +import type { Event_ServerIdentification } from 'generated/proto/event_server_identification_pb'; +import type { Event_ServerMessage } from 'generated/proto/event_server_message_pb'; +import type { Event_ServerShutdown } from 'generated/proto/event_server_shutdown_pb'; +import type { Event_UserJoined } from 'generated/proto/event_user_joined_pb'; +import type { Event_UserLeft } from 'generated/proto/event_user_left_pb'; +import type { Event_UserMessage } from 'generated/proto/event_user_message_pb'; +import type { Event_PlayerPropertiesChanged } from 'generated/proto/event_player_properties_changed_pb'; -export interface AddToListData { - listName: string; - userInfo: User; -} - -export interface ConnectionClosedData { - endTime: number; - reason: number; - reasonStr: string; -} - -export interface GameJoinedData { - gameInfo: Game; - gameTypes: any[]; - hostId: number; - playerId: number; - spectator: boolean; - resuming: boolean; - judge: boolean; -} - -export interface ListRoomsData { - roomList: Room[]; -} - -export interface NotifyUserData { - type: NotificationType; - warningReason: string; - customTitle: string; - customContent: string; -} - -export interface PlayerGamePropertiesData { - playerId: number; - userInfo: User; - spectator: boolean; - conceded: boolean; - readyStart: boolean; - deckHash: string; - pingSeconds: number; - sideboardLocked: boolean; - judge: boolean; -} - -export interface RemoveFromListData { - listName: string; - userName: string; -} - -export interface ServerIdentificationData { - protocolVersion: number; - serverName: string; - serverVersion: string; - serverOptions: number; -} - -export interface ServerMessageData { - message: string; -} - -export interface ServerShutdownData { - reason: string; - minutes: number; -} - -export interface UserJoinedData { - userInfo: User; -} - -export interface UserLeftData { - name: string; -} - -export interface UserMessageData { - senderName: string; - receiverName: string; - message: string; -} - -export interface ReplayAddedData { - matchInfo: ReplayMatch; -} - -export interface ServerCompleteListData { - serverId: number; - userList: User[]; - roomList: Room[]; -} +export type AddToListData = Event_AddToList; +export type ConnectionClosedData = Event_ConnectionClosed; +export type GameJoinedData = Event_GameJoined; +export type ListRoomsData = Event_ListRooms; +export type NotifyUserData = Event_NotifyUser; +export type RemoveFromListData = Event_RemoveFromList; +export type ReplayAddedData = Event_ReplayAdded; +export type ServerCompleteListData = Event_ServerCompleteList; +export type ServerIdentificationData = Event_ServerIdentification; +export type ServerMessageData = Event_ServerMessage; +export type ServerShutdownData = Event_ServerShutdown; +export type UserJoinedData = Event_UserJoined; +export type UserLeftData = Event_UserLeft; +export type UserMessageData = Event_UserMessage; +export type PlayerGamePropertiesData = Event_PlayerPropertiesChanged; diff --git a/webclient/src/websocket/events/session/sessionEvents.spec.ts b/webclient/src/websocket/events/session/sessionEvents.spec.ts index 612f4b497..fae54fc4b 100644 --- a/webclient/src/websocket/events/session/sessionEvents.spec.ts +++ b/webclient/src/websocket/events/session/sessionEvents.spec.ts @@ -51,26 +51,8 @@ vi.mock('../../utils', () => ({ passwordSaltSupported: vi.fn().mockReturnValue(0), })); -vi.mock('../../services/ProtoController', () => ({ - ProtoController: { - root: { - Event_ConnectionClosed: { - CloseReason: { - USER_LIMIT_REACHED: 0, - TOO_MANY_CONNECTIONS: 1, - BANNED: 2, - DEMOTED: 3, - SERVER_SHUTDOWN: 4, - USERNAMEINVALID: 5, - LOGGEDINELSEWERE: 6, - OTHER: 7, - }, - }, - }, - }, -})); - import { WebSocketConnectReason } from 'types'; +import { Event_ConnectionClosed_CloseReason } from 'generated/proto/event_connection_closed_pb'; import { SessionPersistence, RoomPersistence } from '../../persistence'; import webClient from '../../WebClient'; @@ -282,7 +264,7 @@ describe('connectionClosed', () => { }); it('USER_LIMIT_REACHED → specific message', () => { - connectionClosed({ reason: 0 } as any); + connectionClosed({ reason: Event_ConnectionClosed_CloseReason.USER_LIMIT_REACHED } as any); expect(SessionCmds.updateStatus).toHaveBeenCalledWith( expect.anything(), expect.stringContaining('maximum user capacity') @@ -290,42 +272,42 @@ describe('connectionClosed', () => { }); it('TOO_MANY_CONNECTIONS → specific message', () => { - connectionClosed({ reason: 1 } as any); + connectionClosed({ reason: Event_ConnectionClosed_CloseReason.TOO_MANY_CONNECTIONS } as any); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('too many concurrent')); }); it('BANNED → specific message', () => { - connectionClosed({ reason: 2 } as any); - expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('banned')); + connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED } as any); + expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); }); it('DEMOTED → specific message', () => { - connectionClosed({ reason: 3 } as any); + connectionClosed({ reason: Event_ConnectionClosed_CloseReason.DEMOTED } as any); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('demoted')); }); it('SERVER_SHUTDOWN → specific message', () => { - connectionClosed({ reason: 4 } as any); + connectionClosed({ reason: Event_ConnectionClosed_CloseReason.SERVER_SHUTDOWN } as any); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('shutdown')); }); it('USERNAMEINVALID → specific message', () => { - connectionClosed({ reason: 5 } as any); + connectionClosed({ reason: Event_ConnectionClosed_CloseReason.USERNAMEINVALID } as any); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('username')); }); it('LOGGEDINELSEWERE → specific message', () => { - connectionClosed({ reason: 6 } as any); + connectionClosed({ reason: Event_ConnectionClosed_CloseReason.LOGGEDINELSEWERE } as any); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('logged out')); }); it('OTHER → "Unknown reason"', () => { - connectionClosed({ reason: 7 } as any); + connectionClosed({ reason: Event_ConnectionClosed_CloseReason.OTHER } as any); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'Unknown reason'); }); it('BANNED with valid positive endTime → shows formatted date', () => { - connectionClosed({ reason: 2, endTime: 1700000000 } as any); + connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 1700000000 } as any); expect(SessionCmds.updateStatus).toHaveBeenCalledWith( expect.anything(), expect.stringContaining('You are banned until') @@ -333,27 +315,27 @@ describe('connectionClosed', () => { }); it('BANNED with endTime = 0 → shows generic banned message', () => { - connectionClosed({ reason: 2, endTime: 0 } as any); + connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 0 } as any); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); }); it('BANNED with endTime = -1 → shows generic banned message', () => { - connectionClosed({ reason: 2, endTime: -1 } as any); + connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: -1 } as any); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); }); it('BANNED with endTime = NaN → shows generic banned message', () => { - connectionClosed({ reason: 2, endTime: NaN } as any); + connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: NaN } as any); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); }); it('BANNED with endTime = Infinity → shows generic banned message', () => { - connectionClosed({ reason: 2, endTime: Infinity } as any); + connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: Infinity } as any); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); }); it('BANNED with reasonStr → uses reasonStr regardless of endTime', () => { - connectionClosed({ reason: 2, endTime: 0, reasonStr: 'custom ban reason' } as any); + connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 0, reasonStr: 'custom ban reason' } as any); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'custom ban reason'); }); }); diff --git a/webclient/src/websocket/persistence/SessionPersistence.ts b/webclient/src/websocket/persistence/SessionPersistence.ts index 10fcc0539..6e96005ac 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.ts @@ -11,6 +11,10 @@ import { UserMessageData } from '../events/session/interfaces'; import NormalizeService from '../utils/NormalizeService'; +import type { Response_GetGamesOfUser } from 'generated/proto/response_get_games_of_user_pb'; +import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; +import type { ServerInfo_GameType } from 'generated/proto/serverinfo_gametype_pb'; +import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; export class SessionPersistence { static initialized() { @@ -171,14 +175,14 @@ export class SessionPersistence { ServerDispatch.getUserInfo(userInfo); } - static getGamesOfUser(userName: string, response: any): void { + static getGamesOfUser(userName: string, response: Response_GetGamesOfUser): void { const gametypeMap: Record = {}; - (response.roomList || []).forEach((room: any) => { - (room.gametypeList || []).forEach((gt: any) => { + (response.roomList || []).forEach((room: ServerInfo_Room) => { + (room.gametypeList || []).forEach((gt: ServerInfo_GameType) => { gametypeMap[gt.gameTypeId] = gt.description; }); }); - const games = (response.gameList || []).map((game: any) => { + const games = (response.gameList || []).map((game: ServerInfo_Game) => { NormalizeService.normalizeGameObject(game, gametypeMap); return game; }); diff --git a/webclient/src/websocket/services/BackendService.spec.ts b/webclient/src/websocket/services/BackendService.spec.ts index 85545f3cf..98d072338 100644 --- a/webclient/src/websocket/services/BackendService.spec.ts +++ b/webclient/src/websocket/services/BackendService.spec.ts @@ -1,7 +1,11 @@ -import { makeMockProtoRoot } from '../__mocks__/helpers'; +vi.mock('@bufbuild/protobuf', () => ({ + create: vi.fn().mockReturnValue({}), + setExtension: vi.fn(), + getExtension: vi.fn(), +})); -vi.mock('./ProtoController', () => ({ - ProtoController: { root: null }, +vi.mock('generated/proto/response_pb', () => ({ + Response_ResponseCode: { RespOk: 1 }, })); vi.mock('../WebClient', () => { @@ -15,35 +19,29 @@ vi.mock('../WebClient', () => { return { __esModule: true, default: { protobuf: mockProtobuf } }; }); +import { getExtension } from '@bufbuild/protobuf'; import { BackendService } from './BackendService'; -import { ProtoController } from './ProtoController'; import webClient from '../WebClient'; beforeEach(() => { vi.clearAllMocks(); - ProtoController.root = makeMockProtoRoot(); - ProtoController.root.GameCommand = { create: vi.fn(args => ({ ...args })) }; - ProtoController.root['Command_Game'] = { create: vi.fn(p => ({ ...p })) }; - ProtoController.root['Command_Test'] = { create: vi.fn(p => ({ ...p })) }; - ProtoController.root['Command_Room'] = { create: vi.fn(p => ({ ...p })) }; - ProtoController.root['Command_Mod'] = { create: vi.fn(p => ({ ...p })) }; - ProtoController.root['Command_Admin'] = { create: vi.fn(p => ({ ...p })) }; - ProtoController.root['Response_Test'] = {}; }); -function captureCallback(sendFn: vi.Mock) { - return sendFn.mock.calls[0][sendFn === (webClient.protobuf as any).sendRoomCommand ? 2 : 1]; +function captureCallback(sendFn: ReturnType) { + const protobuf = webClient.protobuf as any; + const usesIndex2 = sendFn === protobuf.sendRoomCommand || sendFn === protobuf.sendGameCommand; + return sendFn.mock.calls[0][usesIndex2 ? 2 : 1]; } describe('BackendService', () => { describe('send commands', () => { it.each([ - ['sendGameCommand', () => BackendService.sendGameCommand(7, 'Command_Game', { g: 1 })], - ['sendSessionCommand', () => BackendService.sendSessionCommand('Command_Test', { x: 1 }, {})], - ['sendRoomCommand', () => BackendService.sendRoomCommand(5, 'Command_Room', { y: 2 }, {})], - ['sendModeratorCommand', () => BackendService.sendModeratorCommand('Command_Mod', { z: 3 }, {})], - ['sendAdminCommand', () => BackendService.sendAdminCommand('Command_Admin', {}, {})], - ])('%s creates the command and delegates to protobuf', (methodName, invoke) => { + ['sendGameCommand', () => BackendService.sendGameCommand(7, {} as any, {} as any)], + ['sendSessionCommand', () => BackendService.sendSessionCommand({} as any, {} as any)], + ['sendRoomCommand', () => BackendService.sendRoomCommand(5, {} as any, {} as any)], + ['sendModeratorCommand', () => BackendService.sendModeratorCommand({} as any, {} as any)], + ['sendAdminCommand', () => BackendService.sendAdminCommand({} as any, {} as any)], + ])('%s delegates to protobuf', (methodName, invoke) => { invoke(); expect((webClient.protobuf as any)[methodName]).toHaveBeenCalled(); }); @@ -52,37 +50,37 @@ describe('BackendService', () => { describe('handleResponse via non-session command callbacks', () => { it('sendGameCommand callback invokes handleResponse', () => { const onSuccess = vi.fn(); - BackendService.sendGameCommand(7, 'Command_Game', {}, { onSuccess }); + BackendService.sendGameCommand(7, {} as any, {} as any, { onSuccess }); const cb = (webClient.protobuf as any).sendGameCommand.mock.calls[0][2]; - cb({ responseCode: 0 }); + cb({ responseCode: 1 }); expect(onSuccess).toHaveBeenCalled(); }); it('sendRoomCommand callback invokes handleResponse', () => { const onSuccess = vi.fn(); - BackendService.sendRoomCommand(5, 'Command_Room', {}, { onSuccess }); - captureCallback((webClient.protobuf as any).sendRoomCommand)({ responseCode: 0 }); + BackendService.sendRoomCommand(5, {} as any, {} as any, { onSuccess }); + captureCallback((webClient.protobuf as any).sendRoomCommand)({ responseCode: 1 }); expect(onSuccess).toHaveBeenCalled(); }); it('sendModeratorCommand callback invokes handleResponse', () => { const onSuccess = vi.fn(); - BackendService.sendModeratorCommand('Command_Mod', {}, { onSuccess }); - captureCallback((webClient.protobuf as any).sendModeratorCommand)({ responseCode: 0 }); + BackendService.sendModeratorCommand({} as any, {} as any, { onSuccess }); + captureCallback((webClient.protobuf as any).sendModeratorCommand)({ responseCode: 1 }); expect(onSuccess).toHaveBeenCalled(); }); it('sendAdminCommand callback invokes handleResponse', () => { const onSuccess = vi.fn(); - BackendService.sendAdminCommand('Command_Admin', {}, { onSuccess }); - captureCallback((webClient.protobuf as any).sendAdminCommand)({ responseCode: 0 }); + BackendService.sendAdminCommand({} as any, {} as any, { onSuccess }); + captureCallback((webClient.protobuf as any).sendAdminCommand)({ responseCode: 1 }); expect(onSuccess).toHaveBeenCalled(); }); }); describe('handleResponse (via sendSessionCommand callback)', () => { function invokeCallback(options: any, raw: any) { - BackendService.sendSessionCommand('Command_Test', {}, options); + BackendService.sendSessionCommand({} as any, {} as any, options); const cb = (webClient.protobuf as any).sendSessionCommand.mock.calls[0][1]; cb(raw); } @@ -95,17 +93,19 @@ describe('BackendService', () => { expect(onSuccess).not.toHaveBeenCalled(); }); - it('calls onSuccess with raw when responseCode is RespOk and no responseName', () => { + it('calls onSuccess with raw when responseCode is RespOk and no responseExt', () => { const onSuccess = vi.fn(); - const raw = { responseCode: 0 }; + const raw = { responseCode: 1 }; invokeCallback({ onSuccess }, raw); expect(onSuccess).toHaveBeenCalledWith(raw, raw); }); - it('calls onSuccess with nested response when responseName is set', () => { + it('calls onSuccess with nested response when responseExt is set', () => { + vi.mocked(getExtension).mockReturnValue({ nested: true }); const onSuccess = vi.fn(); - const raw = { responseCode: 0, '.Response_Test.ext': { nested: true } }; - invokeCallback({ onSuccess, responseName: 'Response_Test' }, raw); + const fakeExt = {} as any; + const raw = { responseCode: 1 }; + invokeCallback({ onSuccess, responseExt: fakeExt }, raw); expect(onSuccess).toHaveBeenCalledWith({ nested: true }, raw); }); diff --git a/webclient/src/websocket/services/BackendService.ts b/webclient/src/websocket/services/BackendService.ts index 94319c47e..c9b65eb44 100644 --- a/webclient/src/websocket/services/BackendService.ts +++ b/webclient/src/websocket/services/BackendService.ts @@ -1,66 +1,64 @@ -import webClient from '../WebClient'; -import { ProtoController } from './ProtoController'; +import { create, getExtension, setExtension } from '@bufbuild/protobuf'; +import type { GenExtension } from '@bufbuild/protobuf'; -export interface CommandOptions { - responseName?: string; - onSuccess?: (response: any, raw: any) => void; - onError?: (responseCode: number, raw: any) => void; - onResponseCode?: { [code: number]: (raw: any) => void }; - onResponse?: (raw: any) => void; +import webClient from '../WebClient'; +import { Response_ResponseCode, type Response } from 'generated/proto/response_pb'; +import { SessionCommandSchema, type SessionCommand } from 'generated/proto/session_commands_pb'; +import { GameCommandSchema, type GameCommand } from 'generated/proto/game_commands_pb'; +import { RoomCommandSchema, type RoomCommand } from 'generated/proto/room_commands_pb'; +import { ModeratorCommandSchema, type ModeratorCommand } from 'generated/proto/moderator_commands_pb'; +import { AdminCommandSchema, type AdminCommand } from 'generated/proto/admin_commands_pb'; + +export interface CommandOptions { + responseExt?: GenExtension; + onSuccess?: (response: R, raw: Response) => void; + onError?: (responseCode: number, raw: Response) => void; + onResponseCode?: { [code: number]: (raw: Response) => void }; + onResponse?: (raw: Response) => void; } export class BackendService { - static sendGameCommand(gameId: number, commandName: string, params: any, options: CommandOptions = {}): void { - const command = ProtoController.root[commandName].create(params || {}); - const gc = ProtoController.root.GameCommand.create({ - [`.${commandName}.ext`]: command, - }); - webClient.protobuf.sendGameCommand(gameId, gc, (raw: any) => { - BackendService.handleResponse(commandName, raw, options); + static sendGameCommand(gameId: number, ext: GenExtension, value: V, options: CommandOptions = {}): void { + const cmd = create(GameCommandSchema); + setExtension(cmd, ext, value); + webClient.protobuf.sendGameCommand(gameId, cmd, (raw: Response) => { + BackendService.handleResponse(ext, raw, options); }); } - static sendSessionCommand(commandName: string, params: any, options: CommandOptions): void { - const command = ProtoController.root[commandName].create(params || {}); - const sc = ProtoController.root.SessionCommand.create({ - [`.${commandName}.ext`]: command, - }); - webClient.protobuf.sendSessionCommand(sc, raw => { - BackendService.handleResponse(commandName, raw, options); + static sendSessionCommand(ext: GenExtension, value: V, options: CommandOptions = {}): void { + const cmd = create(SessionCommandSchema); + setExtension(cmd, ext, value); + webClient.protobuf.sendSessionCommand(cmd, raw => { + BackendService.handleResponse(ext, raw, options); }); } - static sendRoomCommand(roomId: number, commandName: string, params: any, options: CommandOptions): void { - const command = ProtoController.root[commandName].create(params || {}); - const rc = ProtoController.root.RoomCommand.create({ - [`.${commandName}.ext`]: command, - }); - webClient.protobuf.sendRoomCommand(roomId, rc, raw => { - BackendService.handleResponse(commandName, raw, options); + static sendRoomCommand(roomId: number, ext: GenExtension, value: V, options: CommandOptions = {}): void { + const cmd = create(RoomCommandSchema); + setExtension(cmd, ext, value); + webClient.protobuf.sendRoomCommand(roomId, cmd, raw => { + BackendService.handleResponse(ext, raw, options); }); } - static sendModeratorCommand(commandName: string, params: any, options: CommandOptions): void { - const command = ProtoController.root[commandName].create(params || {}); - const mc = ProtoController.root.ModeratorCommand.create({ - [`.${commandName}.ext`]: command, - }); - webClient.protobuf.sendModeratorCommand(mc, raw => { - BackendService.handleResponse(commandName, raw, options); + static sendModeratorCommand(ext: GenExtension, value: V, options: CommandOptions = {}): void { + const cmd = create(ModeratorCommandSchema); + setExtension(cmd, ext, value); + webClient.protobuf.sendModeratorCommand(cmd, raw => { + BackendService.handleResponse(ext, raw, options); }); } - static sendAdminCommand(commandName: string, params: any, options: CommandOptions): void { - const command = ProtoController.root[commandName].create(params || {}); - const ac = ProtoController.root.AdminCommand.create({ - [`.${commandName}.ext`]: command, - }); - webClient.protobuf.sendAdminCommand(ac, raw => { - BackendService.handleResponse(commandName, raw, options); + static sendAdminCommand(ext: GenExtension, value: V, options: CommandOptions = {}): void { + const cmd = create(AdminCommandSchema); + setExtension(cmd, ext, value); + webClient.protobuf.sendAdminCommand(cmd, raw => { + BackendService.handleResponse(ext, raw, options); }); } - private static handleResponse(commandName: string, raw: any, options: CommandOptions): void { + private static handleResponse(ext: GenExtension, raw: Response, options: CommandOptions): void { if (options.onResponse) { options.onResponse(raw); return; @@ -68,11 +66,11 @@ export class BackendService { const { responseCode } = raw; - if (responseCode === ProtoController.root.Response.ResponseCode.RespOk) { + if (responseCode === Response_ResponseCode.RespOk) { if (options.onSuccess) { - const response = options.responseName - ? raw[`.${options.responseName}.ext`] - : raw; + const response = options.responseExt + ? getExtension(raw, options.responseExt) + : raw as unknown as R; options.onSuccess(response, raw); } return; @@ -86,7 +84,8 @@ export class BackendService { if (options.onError) { options.onError(responseCode, raw); } else { - console.error(`${commandName} failed with response code: ${responseCode}`); + console.error(`${ext.typeName} failed with response code: ${responseCode}`); } } } + diff --git a/webclient/src/websocket/services/ProtoController.spec.ts b/webclient/src/websocket/services/ProtoController.spec.ts deleted file mode 100644 index b16b3bedb..000000000 --- a/webclient/src/websocket/services/ProtoController.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -vi.mock('../persistence', () => ({ - SessionPersistence: { initialized: vi.fn() }, -})); - -vi.mock('../../proto-files.json', () => ({ default: ['test.proto'] })); - -import { ProtoController } from './ProtoController'; -import { SessionPersistence } from '../persistence'; -import protobuf from 'protobufjs'; - -beforeEach(() => { - vi.clearAllMocks(); - ProtoController.root = null; -}); - -describe('ProtoController', () => { - describe('load', () => { - it('creates a new protobuf.Root', () => { - ProtoController.load(); - expect(ProtoController.root).toBeDefined(); - }); - - it('calls initialized when callback succeeds', () => { - const loadSpy = vi.spyOn(protobuf.Root.prototype, 'load').mockImplementation( - ((_files: any, _opts: any, cb: any) => cb(null)) as any - ); - ProtoController.load(); - expect(SessionPersistence.initialized).toHaveBeenCalled(); - loadSpy.mockRestore(); - }); - - it('throws when callback receives an error', () => { - const loadSpy = vi.spyOn(protobuf.Root.prototype, 'load').mockImplementation( - ((_files: any, _opts: any, cb: any) => cb(new Error('load failed'))) as any - ); - expect(() => ProtoController.load()).toThrow('load failed'); - loadSpy.mockRestore(); - }); - }); -}); diff --git a/webclient/src/websocket/services/ProtoController.ts b/webclient/src/websocket/services/ProtoController.ts deleted file mode 100644 index 4f6b37a19..000000000 --- a/webclient/src/websocket/services/ProtoController.ts +++ /dev/null @@ -1,24 +0,0 @@ -import protobuf from 'protobufjs'; - -import { SessionPersistence } from '../persistence'; -import ProtoFiles from '../../proto-files.json'; - -const PB_FILE_DIR = `${import.meta.env.BASE_URL}pb`; - -// Leaf module — no imports from the websocket layer other than persistence. -// Both BackendService and ProtobufService import this; neither should import -// the other for controller access, avoiding circular dependency cycles. -export const ProtoController = { - root: null as any, - - load(): void { - const files = ProtoFiles.map(file => `${PB_FILE_DIR}/${file}`); - ProtoController.root = new protobuf.Root(); - ProtoController.root.load(files, { keepCase: false }, (err: Error) => { - if (err) { - throw err; - } - SessionPersistence.initialized(); - }); - }, -}; diff --git a/webclient/src/websocket/services/ProtobufService.spec.ts b/webclient/src/websocket/services/ProtobufService.spec.ts index 5e933f118..2e1913e59 100644 --- a/webclient/src/websocket/services/ProtobufService.spec.ts +++ b/webclient/src/websocket/services/ProtobufService.spec.ts @@ -1,7 +1,23 @@ -import { makeMockProtoRoot } from '../__mocks__/helpers'; +vi.mock('@bufbuild/protobuf', () => ({ + create: vi.fn((_schema: any, fields?: any) => ({ ...(fields ?? {}) })), + fromBinary: vi.fn(), + toBinary: vi.fn().mockReturnValue(new Uint8Array()), + hasExtension: vi.fn().mockReturnValue(false), + getExtension: vi.fn(), +})); -vi.mock('./ProtoController', () => ({ - ProtoController: { root: null, load: vi.fn() }, +vi.mock('generated/proto/commands_pb', () => ({ + CommandContainerSchema: {}, +})); + +vi.mock('generated/proto/server_message_pb', () => ({ + ServerMessageSchema: {}, + ServerMessage_MessageType: { + RESPONSE: 1, + ROOM_EVENT: 2, + SESSION_EVENT: 3, + GAME_EVENT_CONTAINER: 4, + }, })); vi.mock('../commands/session', () => ({ @@ -10,9 +26,9 @@ vi.mock('../commands/session', () => ({ })); vi.mock('../events', () => ({ - GameEvents: { '.Event_Game.ext': vi.fn() }, - RoomEvents: { '.Event_Room.ext': vi.fn() }, - SessionEvents: { '.Event_Session.ext': vi.fn() }, + GameEvents: [], + RoomEvents: [], + SessionEvents: [], })); vi.mock('../WebClient', () => ({ @@ -20,42 +36,28 @@ vi.mock('../WebClient', () => ({ default: {}, })); +import { fromBinary, toBinary, hasExtension, getExtension } from '@bufbuild/protobuf'; +import { ServerMessage_MessageType } from 'generated/proto/server_message_pb'; import { ProtobufService } from './ProtobufService'; -import { ProtoController } from './ProtoController'; import { ping as sessionPing } from '../commands/session'; import { GameEvents } from '../events'; let mockSocket: any; -let mockWebClient: any; beforeEach(() => { vi.clearAllMocks(); - ProtoController.root = makeMockProtoRoot(); - const encodeResult = { finish: vi.fn().mockReturnValue(new Uint8Array([1, 2])) }; - ProtoController.root.CommandContainer.encode = vi.fn().mockReturnValue(encodeResult); - mockSocket = { checkReadyState: vi.fn().mockReturnValue(true), send: vi.fn(), }; - - mockWebClient = { - socket: mockSocket, - }; }); describe('ProtobufService', () => { - it('calls ProtoController.load on construction', () => { - new ProtobufService(mockWebClient); - expect(ProtoController.load).toHaveBeenCalled(); - }); - describe('resetCommands', () => { it('resets cmdId and pendingCommands', () => { - const service = new ProtobufService(mockWebClient); - // add a pending command - service.sendSessionCommand({}, vi.fn()); + const service = new ProtobufService({ socket: mockSocket } as any); + service.sendSessionCommand({} as any, vi.fn()); expect((service as any).cmdId).toBe(1); service.resetCommands(); expect((service as any).cmdId).toBe(0); @@ -65,42 +67,41 @@ describe('ProtobufService', () => { describe('sendCommand', () => { it('increments cmdId and stores callback', () => { - const service = new ProtobufService(mockWebClient); + const service = new ProtobufService({ socket: mockSocket } as any); const cb = vi.fn(); - service.sendCommand({}, cb); + service.sendCommand({} as any, cb); expect((service as any).cmdId).toBe(1); expect((service as any).pendingCommands[1]).toBe(cb); }); it('sends encoded data when socket is OPEN', () => { - const service = new ProtobufService(mockWebClient); + const service = new ProtobufService({ socket: mockSocket } as any); mockSocket.checkReadyState.mockReturnValue(true); - service.sendCommand({}, vi.fn()); + service.sendCommand({} as any, vi.fn()); expect(mockSocket.send).toHaveBeenCalled(); }); it('does not send when socket is not OPEN', () => { - const service = new ProtobufService(mockWebClient); + const service = new ProtobufService({ socket: mockSocket } as any); mockSocket.checkReadyState.mockReturnValue(false); - service.sendCommand({}, vi.fn()); + service.sendCommand({} as any, vi.fn()); expect(mockSocket.send).not.toHaveBeenCalled(); }); }); describe('sendSessionCommand', () => { - it('creates a CommandContainer and calls sendCommand', () => { - const service = new ProtobufService(mockWebClient); + it('stores callback and increments cmdId', () => { + const service = new ProtobufService({ socket: mockSocket } as any); const cb = vi.fn(); - service.sendSessionCommand({ cmdType: 'test' }, cb); - expect(ProtoController.root.CommandContainer.create).toHaveBeenCalledWith( - expect.objectContaining({ sessionCommand: expect.anything() }) - ); + service.sendSessionCommand({} as any, cb); + expect((service as any).cmdId).toBe(1); + expect((service as any).pendingCommands[1]).toBeTypeOf('function'); }); it('invokes callback with raw response when the pending command is triggered', () => { - const service = new ProtobufService(mockWebClient); + const service = new ProtobufService({ socket: mockSocket } as any); const cb = vi.fn(); - service.sendSessionCommand({ cmdType: 'test' }, cb); + service.sendSessionCommand({} as any, cb); const storedCb = (service as any).pendingCommands[1]; storedCb({ responseData: true }); @@ -109,8 +110,8 @@ describe('ProtobufService', () => { }); it('does not throw when no callback is provided and pending command is triggered', () => { - const service = new ProtobufService(mockWebClient); - service.sendSessionCommand({ cmdType: 'test' }); + const service = new ProtobufService({ socket: mockSocket } as any); + service.sendSessionCommand({} as any); const storedCb = (service as any).pendingCommands[1]; expect(() => storedCb({ responseData: true })).not.toThrow(); @@ -118,18 +119,16 @@ describe('ProtobufService', () => { }); describe('sendRoomCommand', () => { - it('creates a CommandContainer with roomId and calls sendCommand', () => { - const service = new ProtobufService(mockWebClient); - service.sendRoomCommand(42, { roomCmdType: 'test' }, vi.fn()); - expect(ProtoController.root.CommandContainer.create).toHaveBeenCalledWith( - expect.objectContaining({ roomId: 42 }) - ); + it('stores callback and increments cmdId', () => { + const service = new ProtobufService({ socket: mockSocket } as any); + service.sendRoomCommand(42, {} as any, vi.fn()); + expect((service as any).cmdId).toBe(1); }); it('invokes callback with raw response when the pending command is triggered', () => { - const service = new ProtobufService(mockWebClient); + const service = new ProtobufService({ socket: mockSocket } as any); const cb = vi.fn(); - service.sendRoomCommand(42, { roomCmdType: 'test' }, cb); + service.sendRoomCommand(42, {} as any, cb); const storedCb = (service as any).pendingCommands[1]; storedCb({ responseData: true }); @@ -138,8 +137,8 @@ describe('ProtobufService', () => { }); it('does not throw when no callback is provided and pending command is triggered', () => { - const service = new ProtobufService(mockWebClient); - service.sendRoomCommand(42, { roomCmdType: 'test' }); + const service = new ProtobufService({ socket: mockSocket } as any); + service.sendRoomCommand(42, {} as any); const storedCb = (service as any).pendingCommands[1]; expect(() => storedCb({ responseData: true })).not.toThrow(); @@ -147,18 +146,16 @@ describe('ProtobufService', () => { }); describe('sendGameCommand', () => { - it('creates a CommandContainer with gameId and gameCommand', () => { - const service = new ProtobufService(mockWebClient); - service.sendGameCommand(7, { gameCmdType: 'test' }, vi.fn()); - expect(ProtoController.root.CommandContainer.create).toHaveBeenCalledWith( - expect.objectContaining({ gameId: 7, gameCommand: expect.anything() }) - ); + it('stores callback and increments cmdId', () => { + const service = new ProtobufService({ socket: mockSocket } as any); + service.sendGameCommand(7, {} as any, vi.fn()); + expect((service as any).cmdId).toBe(1); }); it('invokes callback with raw response when the pending command is triggered', () => { - const service = new ProtobufService(mockWebClient); + const service = new ProtobufService({ socket: mockSocket } as any); const cb = vi.fn(); - service.sendGameCommand(7, { gameCmdType: 'test' }, cb); + service.sendGameCommand(7, {} as any, cb); const storedCb = (service as any).pendingCommands[1]; storedCb({ responseData: true }); @@ -167,8 +164,8 @@ describe('ProtobufService', () => { }); it('does not throw when no callback is provided and pending command is triggered', () => { - const service = new ProtobufService(mockWebClient); - service.sendGameCommand(7, { gameCmdType: 'test' }); + const service = new ProtobufService({ socket: mockSocket } as any); + service.sendGameCommand(7, {} as any); const storedCb = (service as any).pendingCommands[1]; expect(() => storedCb({ responseData: true })).not.toThrow(); @@ -176,18 +173,16 @@ describe('ProtobufService', () => { }); describe('sendModeratorCommand', () => { - it('creates a CommandContainer with moderatorCommand', () => { - const service = new ProtobufService(mockWebClient); - service.sendModeratorCommand({ modCmdType: 'test' }, vi.fn()); - expect(ProtoController.root.CommandContainer.create).toHaveBeenCalledWith( - expect.objectContaining({ moderatorCommand: expect.anything() }) - ); + it('stores callback and increments cmdId', () => { + const service = new ProtobufService({ socket: mockSocket } as any); + service.sendModeratorCommand({} as any, vi.fn()); + expect((service as any).cmdId).toBe(1); }); it('invokes callback with raw response when the pending command is triggered', () => { - const service = new ProtobufService(mockWebClient); + const service = new ProtobufService({ socket: mockSocket } as any); const cb = vi.fn(); - service.sendModeratorCommand({ modCmdType: 'test' }, cb); + service.sendModeratorCommand({} as any, cb); const storedCb = (service as any).pendingCommands[1]; storedCb({ responseData: true }); @@ -196,8 +191,8 @@ describe('ProtobufService', () => { }); it('does not throw when no callback is provided and pending command is triggered', () => { - const service = new ProtobufService(mockWebClient); - service.sendModeratorCommand({ modCmdType: 'test' }); + const service = new ProtobufService({ socket: mockSocket } as any); + service.sendModeratorCommand({} as any); const storedCb = (service as any).pendingCommands[1]; expect(() => storedCb({ responseData: true })).not.toThrow(); @@ -205,18 +200,16 @@ describe('ProtobufService', () => { }); describe('sendAdminCommand', () => { - it('creates a CommandContainer with adminCommand', () => { - const service = new ProtobufService(mockWebClient); - service.sendAdminCommand({ adminCmdType: 'test' }, vi.fn()); - expect(ProtoController.root.CommandContainer.create).toHaveBeenCalledWith( - expect.objectContaining({ adminCommand: expect.anything() }) - ); + it('stores callback and increments cmdId', () => { + const service = new ProtobufService({ socket: mockSocket } as any); + service.sendAdminCommand({} as any, vi.fn()); + expect((service as any).cmdId).toBe(1); }); it('invokes callback with raw response when the pending command is triggered', () => { - const service = new ProtobufService(mockWebClient); + const service = new ProtobufService({ socket: mockSocket } as any); const cb = vi.fn(); - service.sendAdminCommand({ adminCmdType: 'test' }, cb); + service.sendAdminCommand({} as any, cb); const storedCb = (service as any).pendingCommands[1]; storedCb({ responseData: true }); @@ -225,8 +218,8 @@ describe('ProtobufService', () => { }); it('does not throw when no callback is provided and pending command is triggered', () => { - const service = new ProtobufService(mockWebClient); - service.sendAdminCommand({ adminCmdType: 'test' }); + const service = new ProtobufService({ socket: mockSocket } as any); + service.sendAdminCommand({} as any); const storedCb = (service as any).pendingCommands[1]; expect(() => storedCb({ responseData: true })).not.toThrow(); @@ -235,7 +228,7 @@ describe('ProtobufService', () => { describe('sendKeepAliveCommand', () => { it('delegates to SessionCommands.ping', () => { - const service = new ProtobufService(mockWebClient); + const service = new ProtobufService({ socket: mockSocket } as any); const pingReceived = vi.fn(); service.sendKeepAliveCommand(pingReceived); expect(sessionPing).toHaveBeenCalledWith(pingReceived); @@ -244,96 +237,83 @@ describe('ProtobufService', () => { describe('handleMessageEvent', () => { it('routes RESPONSE message to processServerResponse', () => { - const service = new ProtobufService(mockWebClient); - const cb = vi.fn(); - // store a callback for cmdId 1 - (service as any).cmdId = 1; - (service as any).pendingCommands[1] = cb; - - const response = { cmdId: 1 }; - ProtoController.root.ServerMessage.decode = vi.fn().mockReturnValue({ - messageType: ProtoController.root.ServerMessage.MessageType.RESPONSE, - response, - }); - - service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); - expect(cb).toHaveBeenCalledWith(response); - expect((service as any).pendingCommands[1]).toBeUndefined(); - }); - - it('resolves pending command when response cmdId is a protobufjs Long object', () => { - const service = new ProtobufService(mockWebClient); + const service = new ProtobufService({ socket: mockSocket } as any); const cb = vi.fn(); (service as any).cmdId = 1; (service as any).pendingCommands[1] = cb; - // Simulate protobufjs decoding cmdId as a Long object (low=1, high=0) - const longCmdId = { low: 1, high: 0, unsigned: false, toString: () => '1' }; - const response = { cmdId: longCmdId }; - ProtoController.root.ServerMessage.decode = vi.fn().mockReturnValue({ - messageType: ProtoController.root.ServerMessage.MessageType.RESPONSE, - response, - }); + vi.mocked(fromBinary).mockReturnValue({ + messageType: ServerMessage_MessageType.RESPONSE, + response: { cmdId: BigInt(1) }, + } as any); service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); - expect(cb).toHaveBeenCalledWith(response); + expect(cb).toHaveBeenCalledWith({ cmdId: BigInt(1) }); expect((service as any).pendingCommands[1]).toBeUndefined(); }); it('routes ROOM_EVENT message', () => { - const service = new ProtobufService(mockWebClient); + const service = new ProtobufService({ socket: mockSocket } as any); const processRoomEvent = vi.spyOn(service as any, 'processRoomEvent'); - ProtoController.root.ServerMessage.decode = vi.fn().mockReturnValue({ - messageType: ProtoController.root.ServerMessage.MessageType.ROOM_EVENT, - roomEvent: { '.Event_Room.ext': {} }, - }); + + vi.mocked(fromBinary).mockReturnValue({ + messageType: ServerMessage_MessageType.ROOM_EVENT, + roomEvent: {}, + } as any); + service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); expect(processRoomEvent).toHaveBeenCalled(); }); it('routes SESSION_EVENT message', () => { - const service = new ProtobufService(mockWebClient); + const service = new ProtobufService({ socket: mockSocket } as any); const processSessionEvent = vi.spyOn(service as any, 'processSessionEvent'); - ProtoController.root.ServerMessage.decode = vi.fn().mockReturnValue({ - messageType: ProtoController.root.ServerMessage.MessageType.SESSION_EVENT, - sessionEvent: { '.Event_Session.ext': {} }, - }); + + vi.mocked(fromBinary).mockReturnValue({ + messageType: ServerMessage_MessageType.SESSION_EVENT, + sessionEvent: {}, + } as any); + service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); expect(processSessionEvent).toHaveBeenCalled(); }); it('routes GAME_EVENT_CONTAINER message', () => { - const service = new ProtobufService(mockWebClient); + const service = new ProtobufService({ socket: mockSocket } as any); const processGameEvent = vi.spyOn(service as any, 'processGameEvent'); - ProtoController.root.ServerMessage.decode = vi.fn().mockReturnValue({ - messageType: ProtoController.root.ServerMessage.MessageType.GAME_EVENT_CONTAINER, - gameEvent: { '.Event_Game.ext': {} }, - }); + + vi.mocked(fromBinary).mockReturnValue({ + messageType: ServerMessage_MessageType.GAME_EVENT_CONTAINER, + gameEventContainer: {}, + } as any); + service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); expect(processGameEvent).toHaveBeenCalled(); }); it('logs unknown message types (default case)', () => { - const service = new ProtobufService(mockWebClient); + const service = new ProtobufService({ socket: mockSocket } as any); const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); - ProtoController.root.ServerMessage.decode = vi.fn().mockReturnValue({ - messageType: 'UNKNOWN_TYPE', - }); + + vi.mocked(fromBinary).mockReturnValue({ + messageType: 999, + } as any); + service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); expect(consoleSpy).toHaveBeenCalled(); consoleSpy.mockRestore(); }); it('does nothing when decoded message is null', () => { - const service = new ProtobufService(mockWebClient); - ProtoController.root.ServerMessage.decode = vi.fn().mockReturnValue(null); + const service = new ProtobufService({ socket: mockSocket } as any); + vi.mocked(fromBinary).mockReturnValue(null as any); expect(() => service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent)).not.toThrow(); }); it('catches and logs decode errors', () => { - const service = new ProtobufService(mockWebClient); + const service = new ProtobufService({ socket: mockSocket } as any); const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - ProtoController.root.ServerMessage.decode = vi.fn().mockImplementation(() => { + vi.mocked(fromBinary).mockImplementation(() => { throw new Error('decode error'); }); expect(() => service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent)).not.toThrow(); @@ -344,50 +324,60 @@ describe('ProtobufService', () => { describe('processGameEvent', () => { it('returns early when container has no eventList', () => { - const service = new ProtobufService(mockWebClient); - const gameEventHandler = (GameEvents as any)['.Event_Game.ext'] as vi.Mock; + const service = new ProtobufService({ socket: mockSocket } as any); + vi.mocked(hasExtension).mockReturnValue(false); (service as any).processGameEvent(null, {}); - expect(gameEventHandler).not.toHaveBeenCalled(); + expect(hasExtension).not.toHaveBeenCalled(); }); - it('dispatches to a GameEvents handler when event key matches', () => { - const service = new ProtobufService(mockWebClient); - const gameEventHandler = (GameEvents as any)['.Event_Game.ext'] as vi.Mock; + it('dispatches to a GameEvents handler when hasExtension returns true', () => { + const service = new ProtobufService({ socket: mockSocket } as any); + const handler = vi.fn(); + const mockExt = {}; const payload = { someData: 1 }; + + // Temporarily override GameEvents for this test + (GameEvents as any).push([mockExt, handler]); + vi.mocked(hasExtension).mockReturnValue(true); + vi.mocked(getExtension).mockReturnValue(payload); + (service as any).processGameEvent({ gameId: 42, - context: null, - secondsElapsed: 10, - forcedByJudge: 0, - eventList: [{ '.Event_Game.ext': payload, playerId: 5 }], + eventList: [{ playerId: 5 }], }, {}); - expect(gameEventHandler).toHaveBeenCalledWith(payload, expect.objectContaining({ gameId: 42, playerId: 5 })); - }); + expect(handler).toHaveBeenCalledWith(payload, expect.objectContaining({ gameId: 42, playerId: 5 })); + (GameEvents as any).pop(); + }); }); describe('processEvent', () => { it('calls matching event handler with payload and raw', () => { - const service = new ProtobufService(mockWebClient); + const service = new ProtobufService({ socket: mockSocket } as any); const handler = vi.fn(); - const events = { '.Event_Test.ext': handler }; + const mockExt = {}; + const registry = [[mockExt, handler]] as any; const payload = { someData: 1 }; - const response = { '.Event_Test.ext': payload }; const raw = { extra: true }; - (service as any).processEvent(response, events, raw); + vi.mocked(hasExtension).mockReturnValue(true); + vi.mocked(getExtension).mockReturnValue(payload); + + (service as any).processEvent({}, registry, raw); expect(handler).toHaveBeenCalledWith(payload, raw); }); it('stops after first matching event', () => { - const service = new ProtobufService(mockWebClient); + const service = new ProtobufService({ socket: mockSocket } as any); const handler1 = vi.fn(); const handler2 = vi.fn(); - const events = { '.Event_A.ext': handler1, '.Event_B.ext': handler2 }; - const response = { '.Event_A.ext': { x: 1 } }; + const registry = [[{}, handler1], [{}, handler2]] as any; - (service as any).processEvent(response, events, {}); + vi.mocked(hasExtension).mockReturnValueOnce(true).mockReturnValueOnce(false); + vi.mocked(getExtension).mockReturnValue({ x: 1 }); + + (service as any).processEvent({}, registry, {}); expect(handler1).toHaveBeenCalled(); expect(handler2).not.toHaveBeenCalled(); diff --git a/webclient/src/websocket/services/ProtobufService.ts b/webclient/src/websocket/services/ProtobufService.ts index 1be84d81d..1874cf50d 100644 --- a/webclient/src/websocket/services/ProtobufService.ts +++ b/webclient/src/websocket/services/ProtobufService.ts @@ -1,12 +1,25 @@ +import { create, fromBinary, hasExtension, getExtension, toBinary } from '@bufbuild/protobuf'; +import type { GenExtension, Message } from '@bufbuild/protobuf'; + +import type { Response } from 'generated/proto/response_pb'; +import type { RoomEvent } from 'generated/proto/room_event_pb'; +import type { SessionEvent } from 'generated/proto/session_event_pb'; +import type { GameEventContainer } from 'generated/proto/game_event_container_pb'; + import { GameEvents, RoomEvents, SessionEvents } from '../events'; import { WebClient } from '../WebClient'; import { SessionCommands } from 'websocket'; -import { ProtoController } from './ProtoController'; import { GameEventMeta } from 'types'; -export interface ProtobufEvents { - [event: string]: Function; -} +import { CommandContainerSchema, type CommandContainer } from 'generated/proto/commands_pb'; +import { ServerMessageSchema, ServerMessage_MessageType, type ServerMessage } from 'generated/proto/server_message_pb'; +import type { SessionCommand } from 'generated/proto/session_commands_pb'; +import type { GameCommand } from 'generated/proto/game_commands_pb'; +import type { RoomCommand } from 'generated/proto/room_commands_pb'; +import type { ModeratorCommand } from 'generated/proto/moderator_commands_pb'; +import type { AdminCommand } from 'generated/proto/admin_commands_pb'; + +export type ExtensionRegistry = Array<[GenExtension, (...args: unknown[]) => void]>; export class ProtobufService { private cmdId = 0; @@ -16,7 +29,6 @@ export class ProtobufService { constructor(webClient: WebClient) { this.webClient = webClient; - ProtoController.load(); } public resetCommands() { @@ -24,56 +36,51 @@ export class ProtobufService { this.pendingCommands = {}; } - public sendGameCommand(gameId: number, gameCmd: any, callback?: Function) { - const cmd = ProtoController.root.CommandContainer.create({ + public sendGameCommand(gameId: number, gameCmd: GameCommand, callback?: Function) { + const cmd = create(CommandContainerSchema, { gameId, gameCommand: [gameCmd], }); - - this.sendCommand(cmd, (raw: any) => callback && callback(raw)); + this.sendCommand(cmd, (raw: Response) => callback && callback(raw)); } - public sendRoomCommand(roomId: number, roomCmd: any, callback?: Function) { - const cmd = ProtoController.root.CommandContainer.create({ - 'roomId': roomId, - 'roomCommand': [roomCmd] + public sendRoomCommand(roomId: number, roomCmd: RoomCommand, callback?: Function) { + const cmd = create(CommandContainerSchema, { + roomId, + roomCommand: [roomCmd], }); - this.sendCommand(cmd, raw => callback && callback(raw)); } - public sendSessionCommand(sesCmd: any, callback?: Function) { - const cmd = ProtoController.root.CommandContainer.create({ - 'sessionCommand': [sesCmd] + public sendSessionCommand(sesCmd: SessionCommand, callback?: Function) { + const cmd = create(CommandContainerSchema, { + sessionCommand: [sesCmd], }); - this.sendCommand(cmd, (raw) => callback && callback(raw)); } - public sendModeratorCommand(modCmd: any, callback?: Function) { - const cmd = ProtoController.root.CommandContainer.create({ - 'moderatorCommand': [modCmd] + public sendModeratorCommand(modCmd: ModeratorCommand, callback?: Function) { + const cmd = create(CommandContainerSchema, { + moderatorCommand: [modCmd], }); - this.sendCommand(cmd, (raw) => callback && callback(raw)); } - public sendAdminCommand(adminCmd: any, callback?: Function) { - const cmd = ProtoController.root.CommandContainer.create({ - 'adminCommand': [adminCmd] + public sendAdminCommand(adminCmd: AdminCommand, callback?: Function) { + const cmd = create(CommandContainerSchema, { + adminCommand: [adminCmd], }); - this.sendCommand(cmd, (raw) => callback && callback(raw)); } - public sendCommand(cmd: any, callback: Function) { + public sendCommand(cmd: CommandContainer, callback: Function) { this.cmdId++; - cmd['cmdId'] = this.cmdId; + cmd.cmdId = BigInt(this.cmdId); this.pendingCommands[this.cmdId] = callback; if (this.webClient.socket.checkReadyState(WebSocket.OPEN)) { - this.webClient.socket.send(ProtoController.root.CommandContainer.encode(cmd).finish()); + this.webClient.socket.send(toBinary(CommandContainerSchema, cmd)); } } @@ -84,21 +91,21 @@ export class ProtobufService { public handleMessageEvent({ data }: MessageEvent): void { try { const uint8msg = new Uint8Array(data); - const msg = ProtoController.root.ServerMessage.decode(uint8msg); + const msg: ServerMessage = fromBinary(ServerMessageSchema, uint8msg); if (msg) { switch (msg.messageType) { - case ProtoController.root.ServerMessage.MessageType.RESPONSE: + case ServerMessage_MessageType.RESPONSE: this.processServerResponse(msg.response); break; - case ProtoController.root.ServerMessage.MessageType.ROOM_EVENT: - this.processRoomEvent(msg.roomEvent, msg); + case ServerMessage_MessageType.ROOM_EVENT: + this.processRoomEvent(msg.roomEvent); break; - case ProtoController.root.ServerMessage.MessageType.SESSION_EVENT: - this.processSessionEvent(msg.sessionEvent, msg); + case ServerMessage_MessageType.SESSION_EVENT: + this.processSessionEvent(msg.sessionEvent); break; - case ProtoController.root.ServerMessage.MessageType.GAME_EVENT_CONTAINER: - this.processGameEvent(msg.gameEventContainer, msg); + case ServerMessage_MessageType.GAME_EVENT_CONTAINER: + this.processGameEvent(msg.gameEventContainer); break; default: console.log(msg); @@ -110,8 +117,11 @@ export class ProtobufService { } } - private processServerResponse(response: any) { - const { cmdId } = response; + private processServerResponse(response: Response | undefined) { + if (!response) { + return; + } + const cmdId = Number(response.cmdId); if (this.pendingCommands[cmdId]) { this.pendingCommands[cmdId](response); @@ -119,15 +129,21 @@ export class ProtobufService { } } - private processRoomEvent(response: any, raw: any) { - this.processEvent(response, RoomEvents, raw); + private processRoomEvent(event: RoomEvent | undefined) { + if (!event) { + return; + } + this.processEvent(event, RoomEvents, event); } - private processSessionEvent(response: any, raw: any) { - this.processEvent(response, SessionEvents, raw); + private processSessionEvent(event: SessionEvent | undefined) { + if (!event) { + return; + } + this.processEvent(event, SessionEvents); } - private processGameEvent(container: any, raw: any): void { + private processGameEvent(container: GameEventContainer | undefined): void { if (!container?.eventList?.length) { return; } @@ -143,24 +159,22 @@ export class ProtobufService { forcedByJudge: forcedByJudge ?? 0, }; - for (const key of Object.keys(GameEvents)) { - const payload = event[key]; - if (payload !== undefined && payload !== null) { - (GameEvents[key] as Function)(payload, meta); + for (const [ext, handler] of GameEvents) { + if (hasExtension(event, ext)) { + (handler as Function)(getExtension(event, ext), meta); break; } } } } - private processEvent(response: any, events: ProtobufEvents, raw: any) { - for (const event in events) { - const payload = response[event]; - - if (payload !== undefined && payload !== null) { - events[event](payload, raw); + private processEvent(response: Message, registry: ExtensionRegistry, raw?: Message) { + for (const [ext, handler] of registry) { + if (hasExtension(response, ext)) { + (handler as Function)(getExtension(response, ext), raw); return; } } } } + diff --git a/webclient/src/websocket/utils/passwordHasher.spec.ts b/webclient/src/websocket/utils/passwordHasher.spec.ts index a8a4148af..7ab16d128 100644 --- a/webclient/src/websocket/utils/passwordHasher.spec.ts +++ b/webclient/src/websocket/utils/passwordHasher.spec.ts @@ -1,16 +1,9 @@ -import { makeMockProtoRoot } from '../__mocks__/helpers'; - -vi.mock('../services/ProtoController', () => ({ - ProtoController: { root: null }, +vi.mock('generated/proto/event_server_identification_pb', () => ({ + Event_ServerIdentification_ServerOptions: { SupportsPasswordHash: 2 }, })); -import { ProtoController } from '../services/ProtoController'; import { hashPassword, generateSalt, passwordSaltSupported } from './passwordHasher'; -beforeEach(() => { - ProtoController.root = makeMockProtoRoot(); -}); - describe('hashPassword', () => { it('returns a string starting with the salt', () => { const result = hashPassword('mysalt', 'mypassword'); diff --git a/webclient/src/websocket/utils/passwordHasher.ts b/webclient/src/websocket/utils/passwordHasher.ts index 164a91823..3f726f10f 100644 --- a/webclient/src/websocket/utils/passwordHasher.ts +++ b/webclient/src/websocket/utils/passwordHasher.ts @@ -1,6 +1,6 @@ import sha512 from 'crypto-js/sha512'; import Base64 from 'crypto-js/enc-base64'; -import { ProtoController } from '../services/ProtoController'; +import { Event_ServerIdentification_ServerOptions } from 'generated/proto/event_server_identification_pb'; const HASH_ROUNDS = 1_000; const SALT_LENGTH = 16; @@ -28,5 +28,5 @@ export const generateSalt = (): string => { export const passwordSaltSupported = (serverOptions: number): number => { // Intentional use of Bitwise operator b/c of how Servatrice Enums work - return serverOptions & ProtoController.root.Event_ServerIdentification.ServerOptions.SupportsPasswordHash; + return serverOptions & Event_ServerIdentification_ServerOptions.SupportsPasswordHash; } diff --git a/webclient/tsconfig.json b/webclient/tsconfig.json index 1287c0521..3ce50dc96 100644 --- a/webclient/tsconfig.json +++ b/webclient/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "baseUrl": "src", - "target": "es6", + "target": "es2020", "lib": [ "dom", "dom.iterable", From 19f5eefdd2cd9b927d90b5131a62a1cfe22da4c2 Mon Sep 17 00:00:00 2001 From: seavor Date: Tue, 14 Apr 2026 11:34:29 -0500 Subject: [PATCH 11/38] upgrade packages + improve typing --- webclient/.eslintrc.js | 48 - webclient/eslint.config.mjs | 78 + webclient/package-lock.json | 11370 ++++------------ webclient/package.json | 78 +- webclient/src/components/Guard/AuthGuard.tsx | 16 +- webclient/src/components/Guard/ModGuard.tsx | 27 +- .../components/InputAction/InputAction.tsx | 7 +- .../src/components/KnownHosts/KnownHosts.tsx | 4 +- .../LanguageDropdown/LanguageDropdown.tsx | 3 +- .../src/components/Message/CardCallout.tsx | 4 +- webclient/src/components/Message/Message.tsx | 2 +- .../ThreePaneLayout/ThreePaneLayout.tsx | 20 +- webclient/src/components/Toast/Toast.tsx | 18 +- .../src/components/Toast/ToastContext.tsx | 12 +- .../components/UserDisplay/UserDisplay.tsx | 189 +- .../components/VirtualList/VirtualList.tsx | 39 +- webclient/src/containers/Account/Account.tsx | 45 +- .../src/containers/App/AppShellRoutes.tsx | 2 +- .../src/containers/App/FeatureDetection.tsx | 2 +- .../src/containers/Initialize/Initialize.tsx | 16 +- webclient/src/containers/Layout/Layout.tsx | 2 +- webclient/src/containers/Layout/LeftNav.tsx | 43 +- webclient/src/containers/Login/Login.tsx | 18 +- webclient/src/containers/Logs/Logs.tsx | 112 +- webclient/src/containers/Room/Games.tsx | 205 +- webclient/src/containers/Room/Messages.tsx | 2 +- webclient/src/containers/Room/OpenGames.tsx | 205 +- webclient/src/containers/Room/Room.tsx | 31 +- webclient/src/containers/Server/Server.tsx | 35 +- .../containers/Unsupported/Unsupported.tsx | 7 +- .../AccountActivationDialog.tsx | 2 +- .../CardImportDialog/CardImportDialog.tsx | 2 +- .../KnownHostDialog/KnownHostDialog.tsx | 1 - .../RegistrationDialog/RegistrationDialog.tsx | 2 +- .../RequestPasswordResetDialog.tsx | 2 +- .../ResetPasswordDialog.tsx | 2 +- .../AccountActivationForm.tsx | 11 +- .../forms/CardImportForm/CardImportForm.tsx | 88 +- .../src/forms/KnownHostForm/KnownHostForm.tsx | 7 +- webclient/src/forms/LoginForm/LoginForm.tsx | 4 +- .../src/forms/RegisterForm/RegisterForm.tsx | 16 +- .../RequestPasswordResetForm.tsx | 6 +- .../ResetPasswordForm/ResetPasswordForm.tsx | 9 +- webclient/src/forms/SearchForm/SearchForm.tsx | 96 +- webclient/src/hooks/useAutoConnect.ts | 3 - .../hooks/useFireOnce/useFireOnce.spec.tsx | 6 +- .../src/hooks/useFireOnce/useFireOnce.ts | 2 - webclient/src/hooks/useReduxEffect.tsx | 6 +- webclient/src/index.tsx | 2 +- webclient/src/services/CardImporterService.ts | 4 +- webclient/src/setupTests.ts | 7 + webclient/src/store/actions/actionReducer.ts | 8 +- webclient/src/store/common/SortUtil.spec.ts | 16 +- webclient/src/store/common/SortUtil.ts | 16 +- webclient/src/store/common/index.ts | 1 + .../src/store/common/normalizers.spec.ts | 121 + webclient/src/store/common/normalizers.ts | 85 + .../src/store/game/__mocks__/fixtures.ts | 37 +- webclient/src/store/game/game.actions.spec.ts | 61 +- webclient/src/store/game/game.actions.ts | 2 + .../src/store/game/game.dispatch.spec.ts | 60 +- webclient/src/store/game/game.reducer.spec.ts | 10 +- webclient/src/store/game/game.reducer.ts | 16 +- .../src/store/game/game.selectors.spec.ts | 3 +- webclient/src/store/game/game.selectors.ts | 19 +- webclient/src/store/game/game.types.ts | 2 +- .../store/rooms/__mocks__/rooms-fixtures.ts | 73 +- webclient/src/store/rooms/rooms.actions.tsx | 52 +- .../src/store/rooms/rooms.dispatch.spec.ts | 14 +- webclient/src/store/rooms/rooms.dispatch.tsx | 31 +- .../src/store/rooms/rooms.interfaces.tsx | 9 +- .../src/store/rooms/rooms.reducer.spec.ts | 24 + webclient/src/store/rooms/rooms.reducer.tsx | 45 +- .../src/store/rooms/rooms.selectors.spec.ts | 10 +- webclient/src/store/rooms/rooms.selectors.tsx | 17 +- webclient/src/store/rooms/rooms.types.tsx | 2 +- webclient/src/store/rootReducer.ts | 7 +- .../store/server/__mocks__/server-fixtures.ts | 71 +- .../src/store/server/server.actions.spec.ts | 24 +- webclient/src/store/server/server.actions.ts | 85 +- .../src/store/server/server.dispatch.spec.ts | 49 +- webclient/src/store/server/server.dispatch.ts | 89 +- .../src/store/server/server.interfaces.ts | 1 + .../src/store/server/server.reducer.spec.ts | 121 +- webclient/src/store/server/server.reducer.ts | 101 +- .../src/store/server/server.selectors.ts | 1 + webclient/src/store/server/server.types.ts | 3 +- webclient/src/store/store.ts | 28 +- webclient/src/types/constants.spec.ts | 2 - webclient/src/types/deckList.ts | 25 +- webclient/src/types/forms.ts | 11 - webclient/src/types/game.ts | 69 +- webclient/src/types/index.ts | 2 - webclient/src/types/moderator.ts | 26 +- webclient/src/types/room.ts | 2 + webclient/src/types/server.ts | 13 +- webclient/src/websocket/WebClient.spec.ts | 57 +- webclient/src/websocket/WebClient.ts | 2 +- .../websocket/__mocks__/callbackHelpers.ts | 4 +- webclient/src/websocket/__mocks__/helpers.ts | 15 +- .../commands/admin/adminCommands.spec.ts | 6 +- .../commands/game/gameCommands.spec.ts | 4 +- .../commands/moderator/getWarnList.ts | 2 +- .../moderator/moderatorCommands.spec.ts | 12 +- .../commands/room/roomCommands.spec.ts | 6 +- .../websocket/commands/session/deckList.ts | 4 +- .../websocket/commands/session/deckUpload.ts | 4 +- .../websocket/commands/session/joinRoom.ts | 4 +- .../src/websocket/commands/session/ping.ts | 4 +- .../websocket/commands/session/register.ts | 12 +- .../session/sessionCommands-complex.spec.ts | 24 +- .../session/sessionCommands-simple.spec.ts | 21 +- .../src/websocket/events/common/index.ts | 4 +- webclient/src/websocket/events/game/index.ts | 62 +- webclient/src/websocket/events/room/index.ts | 14 +- .../src/websocket/events/room/interfaces.ts | 2 + .../websocket/events/room/roomEvents.spec.ts | 35 +- .../src/websocket/events/room/roomSay.ts | 5 +- .../src/websocket/events/session/index.ts | 32 +- .../events/session/sessionEvents.spec.ts | 170 +- .../persistence/GamePersistence.spec.ts | 64 +- .../persistence/ModeratorPersistence.spec.ts | 14 +- .../persistence/ModeratorPersistence.ts | 4 +- .../persistence/RoomPersistence.spec.ts | 49 +- .../websocket/persistence/RoomPersistence.ts | 30 +- .../persistence/SessionPersistence.spec.ts | 40 +- .../persistence/SessionPersistence.ts | 23 +- .../websocket/services/BackendService.spec.ts | 4 +- .../src/websocket/services/BackendService.ts | 65 +- .../services/KeepAliveService.spec.ts | 9 + .../websocket/services/KeepAliveService.ts | 2 +- .../services/ProtobufService.spec.ts | 34 +- .../src/websocket/services/ProtobufService.ts | 101 +- .../services/WebSocketService.spec.ts | 14 +- .../websocket/services/WebSocketService.ts | 4 +- .../websocket/utils/NormalizeService.spec.ts | 110 - .../src/websocket/utils/NormalizeService.ts | 63 - webclient/vite.config.ts | 2 +- 138 files changed, 4504 insertions(+), 11015 deletions(-) delete mode 100644 webclient/.eslintrc.js create mode 100644 webclient/eslint.config.mjs create mode 100644 webclient/src/store/common/normalizers.spec.ts create mode 100644 webclient/src/store/common/normalizers.ts delete mode 100644 webclient/src/types/forms.ts delete mode 100644 webclient/src/websocket/utils/NormalizeService.spec.ts delete mode 100644 webclient/src/websocket/utils/NormalizeService.ts diff --git a/webclient/.eslintrc.js b/webclient/.eslintrc.js deleted file mode 100644 index 8fb389073..000000000 --- a/webclient/.eslintrc.js +++ /dev/null @@ -1,48 +0,0 @@ -module.exports = { - "root": true, - "parser": "@typescript-eslint/parser", - "parserOptions": {"ecmaVersion": 2020, "sourceType": "module", "ecmaFeatures": {"jsx": true}}, - "plugins": [ - "@typescript-eslint" - ], - "ignorePatterns": ["node_modules/*", "build/*", "public/pb/*"], - "env": { - "jest": true - }, - "rules": { - "array-bracket-spacing": ["error", "never"], - "arrow-spacing": ["error", {"before": true, "after": true}], - "block-spacing": ["error", "always"], - "brace-style": ["error", "1tbs", {"allowSingleLine": false}], - "comma-spacing": ["error", {"before": false, "after": true}], - "comma-style": ["error", "last"], - "computed-property-spacing": ["error", "never"], - "curly": ["error", "all"], - "dot-location": ["error", "property"], - "eol-last": ["error"], - "func-names": ["warn"], - "indent": ["error", 2, {"SwitchCase": 1}], - "key-spacing": ["error", {"beforeColon": false, "afterColon": true}], - "keyword-spacing": ["error"], - "linebreak-style": ["error", (process.platform === "win32" ? "windows" : "unix")], - "max-len": ["error", {"code": 140}], - "no-eq-null": ["off"], - "no-func-assign": ["error"], - "no-inline-comments": ["error"], - "no-mixed-spaces-and-tabs": ["error"], - "no-multi-spaces": ["error"], - "no-spaced-func": ["error"], - "no-trailing-spaces": ["error"], - "no-var": ["error"], - "object-curly-spacing": ["error", "always"], - "one-var": ["error", "never"], - "one-var-declaration-per-line": ["error"], - "quotes": ["error", "single"], - "semi-spacing": ["error", {"before": false, "after": true}], - "space-before-blocks": ["error"], - "space-before-function-paren": ["error", {"asyncArrow": "always", "anonymous": "never", "named": "never"}], - "space-in-parens": ["error", "never"], - "space-infix-ops": ["error"], - "space-unary-ops": ["error", {"words": true, "nonwords": false}] - } -} \ No newline at end of file diff --git a/webclient/eslint.config.mjs b/webclient/eslint.config.mjs new file mode 100644 index 000000000..f5a46a5a7 --- /dev/null +++ b/webclient/eslint.config.mjs @@ -0,0 +1,78 @@ +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import globals from 'globals'; + +export default tseslint.config( + // Global ignores + { ignores: ['node_modules/**', 'build/**', 'public/pb/**', 'src/generated/**'] }, + + // Base JS recommended + js.configs.recommended, + + // TypeScript recommended (sets up parser + plugin) + ...tseslint.configs.recommended, + + // Project-specific config + { + languageOptions: { + ecmaVersion: 2020, + sourceType: 'module', + parserOptions: { + ecmaFeatures: { jsx: true }, + }, + globals: { + ...globals.browser, + ...globals.es2020, + }, + }, + rules: { + // TypeScript overrides + '@typescript-eslint/no-unused-vars': ['warn', { varsIgnorePattern: '^_', argsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }], + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-require-imports': 'off', + '@typescript-eslint/no-empty-object-type': 'off', + '@typescript-eslint/no-unsafe-function-type': 'off', + + // Disable new rules not in original config + 'prefer-const': 'off', + 'no-extra-boolean-cast': 'off', + 'no-case-declarations': 'off', + 'preserve-caught-error': 'off', + + // Spacing / formatting + 'array-bracket-spacing': ['error', 'never'], + 'arrow-spacing': ['error', { before: true, after: true }], + 'block-spacing': ['error', 'always'], + 'brace-style': ['error', '1tbs', { allowSingleLine: false }], + 'comma-spacing': ['error', { before: false, after: true }], + 'comma-style': ['error', 'last'], + 'computed-property-spacing': ['error', 'never'], + 'curly': ['error', 'all'], + 'dot-location': ['error', 'property'], + 'eol-last': ['error'], + 'func-names': ['warn'], + 'indent': ['error', 2, { SwitchCase: 1 }], + 'key-spacing': ['error', { beforeColon: false, afterColon: true }], + 'keyword-spacing': ['error'], + 'linebreak-style': ['error', process.platform === 'win32' ? 'windows' : 'unix'], + 'max-len': ['error', { code: 140 }], + 'no-eq-null': ['off'], + 'no-func-assign': ['error'], + 'no-inline-comments': ['error'], + 'no-mixed-spaces-and-tabs': ['error'], + 'no-multi-spaces': ['error'], + 'no-trailing-spaces': ['error'], + 'no-var': ['error'], + 'object-curly-spacing': ['error', 'always'], + 'one-var': ['error', 'never'], + 'one-var-declaration-per-line': ['error'], + 'quotes': ['error', 'single'], + 'semi-spacing': ['error', { before: false, after: true }], + 'space-before-blocks': ['error'], + 'space-before-function-paren': ['error', { asyncArrow: 'always', anonymous: 'never', named: 'never' }], + 'space-in-parens': ['error', 'never'], + 'space-infix-ops': ['error'], + 'space-unary-ops': ['error', { words: true, nonwords: false }], + }, + }, +); diff --git a/webclient/package-lock.json b/webclient/package-lock.json index dd410fe16..e51c495d6 100644 --- a/webclient/package-lock.json +++ b/webclient/package-lock.json @@ -1,7 +1,7 @@ { "name": "webclient", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -11,64 +11,61 @@ "@bufbuild/protobuf": "^2.11.0", "@emotion/react": "^11.8.2", "@emotion/styled": "^11.8.1", - "@mui/icons-material": "^5.5.1", - "@mui/material": "^5.5.1", + "@mui/icons-material": "^7.3.10", + "@mui/material": "^7.3.10", + "@reduxjs/toolkit": "^2.11.2", "crypto-js": "^4.2.0", - "dexie": "^3.2.2", + "dexie": "^4.4.2", "dompurify": "^3.3.3", - "final-form": "^4.20.6", + "final-form": "^5.0.0", "final-form-set-field-touched": "^1.0.1", - "i18next": "^22.0.4", - "i18next-browser-languagedetector": "^7.0.0", + "i18next": "^26.0.4", + "i18next-browser-languagedetector": "^8.2.1", "i18next-icu": "^2.0.3", - "intl-messageformat": "^10.2.1", + "intl-messageformat": "^11.2.1", "lodash": "^4.17.21", "prop-types": "^15.8.1", - "protobufjs": "^7.2.4", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-final-form": "^6.5.8", - "react-final-form-listeners": "^1.0.3", - "react-i18next": "^12.0.0", - "react-redux": "^8.0.4", - "react-router-dom": "^6.2.2", - "react-virtualized-auto-sizer": "^1.0.6", - "react-window": "^1.8.6", - "redux": "^4.1.2", - "redux-form": "^8.3.8", - "redux-thunk": "^2.4.1", + "react-final-form": "^7.0.0", + "react-final-form-listeners": "^3.0.0", + "react-i18next": "^17.0.2", + "react-redux": "^9.2.0", + "react-router-dom": "^7.14.1", + "react-virtualized-auto-sizer": "^2.0.3", + "react-window": "^2.2.7", "rxjs": "^7.5.4" }, "devDependencies": { "@bufbuild/buf": "^1.67.0", "@bufbuild/protoc-gen-es": "^2.11.0", + "@eslint/js": "^10.0.1", "@mui/types": "^7.1.3", + "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.4.0", - "@testing-library/react": "^13.4.0", + "@testing-library/react": "^16.3.2", "@types/dompurify": "^3.0.5", - "@types/jquery": "^3.5.14", "@types/lodash": "^4.14.179", - "@types/node": "18.11.7", + "@types/node": "^22.19.17", "@types/prop-types": "^15.7.4", "@types/react": "18.0.24", "@types/react-dom": "18.0.8", - "@types/react-redux": "^7.1.23", - "@types/react-router-dom": "^5.3.3", "@types/react-virtualized-auto-sizer": "^1.0.1", "@types/react-window": "^1.8.5", - "@types/redux-form": "^8.3.3", - "@typescript-eslint/eslint-plugin": "^5.14.0", - "@typescript-eslint/parser": "^5.14.0", - "@vitejs/plugin-react": "^4.2.0", - "@vitest/coverage-v8": "^1.3.0", - "eslint": "^8.0.0", - "fs-extra": "^10.0.1", - "husky": "^8.0.1", - "jsdom": "^24.0.0", - "typescript": "^4.6.2", - "vite": "^5.1.0", - "vite-tsconfig-paths": "^4.3.1", - "vitest": "^1.3.0" + "@typescript-eslint/eslint-plugin": "^8.58.2", + "@typescript-eslint/parser": "^8.58.2", + "@vitejs/plugin-react": "^5.2.0", + "@vitest/coverage-v8": "^4.1.4", + "eslint": "^10.2.0", + "fs-extra": "^11.3.4", + "globals": "^17.5.0", + "husky": "^9.1.7", + "jsdom": "^29.0.2", + "typescript": "~5.8", + "typescript-eslint": "^8.58.2", + "vite": "^6.4.2", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^4.1.4" } }, "node_modules/@adobe/css-tools": { @@ -78,40 +75,44 @@ "dev": true, "license": "MIT" }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@asamuzakjp/css-color": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", - "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "version": "5.1.10", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.10.tgz", + "integrity": "sha512-02OhhkKtgNRuicQ/nF3TRnGsxL9wp0r3Y7VlKWyOHHGmGyvXv03y+PnymU8FKFJMTjIr1Bk8U2g1HWSLrpAHww==", "dev": true, "license": "MIT", "dependencies": { - "@csstools/css-calc": "^2.1.3", - "@csstools/css-color-parser": "^3.0.9", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "lru-cache": "^10.4.3" + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.9.tgz", + "integrity": "sha512-r3ElRr7y8ucyN2KdICwGsmj19RoN13CLCa/pvGydghWK6ZzeKQ+TcDjVdtEZz2ElpndM5jXw//B9CEee0mWnVg==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" }, "node_modules/@babel/code-frame": { "version": "7.29.0", @@ -175,6 +176,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { "version": "7.29.1", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", @@ -208,23 +219,16 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -269,6 +273,7 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -278,6 +283,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -330,20 +336,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-react-jsx-self": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", @@ -377,9 +369,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", - "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -430,11 +423,27 @@ } }, "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } }, "node_modules/@bufbuild/buf": { "version": "1.67.0", @@ -638,9 +647,9 @@ } }, "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", "dev": true, "funding": [ { @@ -654,13 +663,13 @@ ], "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz", + "integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==", "dev": true, "funding": [ { @@ -674,17 +683,17 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.0.tgz", + "integrity": "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==", "dev": true, "funding": [ { @@ -698,21 +707,21 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.2.0" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", "dev": true, "funding": [ { @@ -726,16 +735,41 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz", + "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } } }, "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", "dev": true, "funding": [ { @@ -749,158 +783,159 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, "node_modules/@emotion/babel-plugin": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.5.tgz", - "integrity": "sha512-xE7/hyLHJac7D2Ve9dKroBBZqBT7WuPQmWcq7HSGb84sUuP4mlOWoB8dvVfD9yk5DHkU1m6RW7xSoDtnQHNQeA==", + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.16.7", - "@babel/plugin-syntax-jsx": "^7.17.12", "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/serialize": "^1.1.1", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", "find-root": "^1.1.0", "source-map": "^0.5.7", - "stylis": "4.1.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "stylis": "4.2.0" } }, "node_modules/@emotion/cache": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz", - "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==", + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", "dependencies": { - "@emotion/memoize": "^0.8.0", - "@emotion/sheet": "^1.2.1", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", - "stylis": "4.1.3" + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" } }, "node_modules/@emotion/hash": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", - "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==" + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" }, "node_modules/@emotion/is-prop-valid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", - "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", "dependencies": { - "@emotion/memoize": "^0.8.0" + "@emotion/memoize": "^0.9.0" } }, "node_modules/@emotion/memoize": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", - "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" }, "node_modules/@emotion/react": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.5.tgz", - "integrity": "sha512-TZs6235tCJ/7iF6/rvTaOH4oxQg2gMAcdHemjwLKIjKz4rRuYe1HJ2TQJKnAcRAfOUDdU8XoDadCe1rl72iv8A==", + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.5", - "@emotion/cache": "^11.10.5", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", "hoist-non-react-statics": "^3.3.1" }, "peerDependencies": { - "@babel/core": "^7.0.0", "react": ">=16.8.0" }, "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, "@types/react": { "optional": true } } }, "node_modules/@emotion/serialize": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz", - "integrity": "sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", "dependencies": { - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/unitless": "^0.8.0", - "@emotion/utils": "^1.2.0", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" } }, "node_modules/@emotion/sheet": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz", - "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" }, "node_modules/@emotion/styled": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.5.tgz", - "integrity": "sha512-8EP6dD7dMkdku2foLoruPCNkRevzdcBaY6q0l0OsbyJK+x8D9HWjX27ARiSIKNF634hY9Zdoedh8bJCiva8yZw==", + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.5", - "@emotion/is-prop-valid": "^1.2.0", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0" + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" }, "peerDependencies": { - "@babel/core": "^7.0.0", "@emotion/react": "^11.0.0-rc.0", "react": ">=16.8.0" }, "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, "@types/react": { "optional": true } } }, "node_modules/@emotion/unitless": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", - "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", - "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", "peerDependencies": { "react": ">=16.8.0" } }, "node_modules/@emotion/utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz", - "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==" + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" }, "node_modules/@emotion/weak-memoize": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", - "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "cpu": [ "ppc64" ], @@ -911,13 +946,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", "cpu": [ "arm" ], @@ -928,13 +963,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", "cpu": [ "arm64" ], @@ -945,13 +980,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "cpu": [ "x64" ], @@ -962,13 +997,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -979,13 +1014,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], @@ -996,13 +1031,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", "cpu": [ "arm64" ], @@ -1013,13 +1048,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], @@ -1030,13 +1065,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], @@ -1047,13 +1082,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], @@ -1064,13 +1099,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "cpu": [ "ia32" ], @@ -1081,13 +1116,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "cpu": [ "loong64" ], @@ -1098,13 +1133,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "cpu": [ "mips64el" ], @@ -1115,13 +1150,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "cpu": [ "ppc64" ], @@ -1132,13 +1167,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "cpu": [ "riscv64" ], @@ -1149,13 +1184,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "cpu": [ "s390x" ], @@ -1166,13 +1201,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "cpu": [ "x64" ], @@ -1183,13 +1218,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "cpu": [ "x64" ], @@ -1200,13 +1252,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "cpu": [ "x64" ], @@ -1217,13 +1286,30 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "cpu": [ "x64" ], @@ -1234,13 +1320,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", "cpu": [ "arm64" ], @@ -1251,13 +1337,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "cpu": [ "ia32" ], @@ -1268,13 +1354,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "cpu": [ "x64" ], @@ -1285,7 +1371,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -1317,98 +1403,153 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@formatjs/ecma402-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.13.0.tgz", - "integrity": "sha512-CQ8Ykd51jYD1n05dtoX6ns6B9n/+6ZAxnWUAonvHC4kkuAemROYBhHkEB4tm1uVrRlE7gLDqXkAnY51Y0pRCWQ==", - "dependencies": { - "@formatjs/intl-localematcher": "0.2.31", - "tslib": "2.4.0" - } - }, - "node_modules/@formatjs/fast-memoize": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.6.tgz", - "integrity": "sha512-9CWZ3+wCkClKHX+i5j+NyoBVqGf0pIskTo6Xl6ihGokYM2yqSSS68JIgeo+99UIHc+7vi9L3/SDSz/dWI9SNlA==", - "dependencies": { - "tslib": "2.4.0" - } - }, - "node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.10.tgz", - "integrity": "sha512-KkRMxhifWkRC45dhM9tqm0GXbb6NPYTGVYY3xx891IKc6p++DQrZTnmkVSNNO47OEERLfuP2KkPFPJBuu8z/wg==", - "dependencies": { - "@formatjs/ecma402-abstract": "1.13.0", - "@formatjs/icu-skeleton-parser": "1.3.14", - "tslib": "2.4.0" - } - }, - "node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.3.14", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.14.tgz", - "integrity": "sha512-7bv60HQQcBb3+TSj+45tOb/CHV5z1hOpwdtS50jsSBXfB+YpGhnoRsZxSRksXeCxMy6xn6tA6VY2601BrrK+OA==", - "dependencies": { - "@formatjs/ecma402-abstract": "1.13.0", - "tslib": "2.4.0" - } - }, - "node_modules/@formatjs/intl-localematcher": { - "version": "0.2.31", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.31.tgz", - "integrity": "sha512-9QTjdSBpQ7wHShZgsNzNig5qT3rCPvmZogS/wXZzKotns5skbXgs0I7J8cuN0PPqXyynvNVuN+iOKhNS2eb+ZA==", - "dependencies": { - "tslib": "2.4.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", + "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", - "minimatch": "^3.0.5" + "minimatch": "^10.2.4" }, "engines": { - "node": ">=10.10.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", + "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", + "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.2.tgz", + "integrity": "sha512-vPnriihkfK0lzoQGaXq+qXH23VsYyansRTkTgo2aTG0k1NjLFyZimFVdfj4C9JkSE5dm7CEngcQ5TTc1yAyBfQ==", + "license": "MIT" + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.4.tgz", + "integrity": "sha512-JVY39ROgLt+pIYngo6piyj4OVfZmXs/2FkC4wLS+ql1Eig/sGJKB7YwDO/5bkJFkfwaFAeIpgEiJc8hiYxNalw==", + "license": "MIT", + "dependencies": { + "@formatjs/icu-skeleton-parser": "2.1.4" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.4.tgz", + "integrity": "sha512-8bSFZbrlvGX11ywMZxtgkPBt5Q8/etyts7j7j+GWpOVK1g43zwMIH3LZxk43HAtEP7L/jtZ+OZaMiFTOiBj9CA==", + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -1425,44 +1566,20 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" + "node": ">=18.18" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@jest/schemas/node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", - "dev": true, - "license": "MIT" - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1485,9 +1602,10 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -1508,65 +1626,35 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@mui/base": { - "version": "5.0.0-alpha.103", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.103.tgz", - "integrity": "sha512-fJIyB2df3CHn7D26WHnutnY7vew6aytTlhmRJz6GX7ag19zU2GcOUhJAzY5qwWcrXKnlYgzimhEjaEnuiUWU4g==", - "dependencies": { - "@babel/runtime": "^7.19.0", - "@emotion/is-prop-valid": "^1.2.0", - "@mui/types": "^7.2.0", - "@mui/utils": "^5.10.9", - "@popperjs/core": "^2.11.6", - "clsx": "^1.2.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.10.11", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.10.11.tgz", - "integrity": "sha512-u5ff+UCFDHcR8MoQ8tuJR4c35vt7T/ki3aMEE2O3XQoGs8KJSrBiisFpFKyldg9/W2NSyoZxN+kxEGIfRxh+9Q==", + "version": "7.3.10", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.10.tgz", + "integrity": "sha512-vrOpWRmPJSuwLo23J62wggEm/jvGdzqctej+UOCtgDUz6nZJQuj3ByPccVyaa7eQmwAzUwKN56FQPMKkqbj1GA==", + "license": "MIT", "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/icons-material": { - "version": "5.10.9", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.10.9.tgz", - "integrity": "sha512-sqClXdEM39WKQJOQ0ZCPTptaZgqwibhj2EFV9N0v7BU1PO8y4OcX/a2wIQHn4fNuDjIZktJIBrmU23h7aqlGgg==", + "version": "7.3.10", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.10.tgz", + "integrity": "sha512-Au0ma4NSKGKNiimukj8UT/W1x2Qx6Qwn2RvFGykiSqVLYBNlIOPbjnIMvrwLGLu89EEpTVdu/ys/OduZR+tWqw==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.19.0" + "@babel/runtime": "^7.28.6" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^5.0.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "@mui/material": "^7.3.10", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -1575,36 +1663,38 @@ } }, "node_modules/@mui/material": { - "version": "5.10.11", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.10.11.tgz", - "integrity": "sha512-KJ0wPCTbv6sFzwA3dgg0gowdfF+SRl7D510J9l6Nl/KFX0EawcewQudqKY4slYGFXniKa5PykqokpaWXsCCPqg==", + "version": "7.3.10", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.10.tgz", + "integrity": "sha512-cHvGOk2ZEfbQt3LnGe0ZKd/ETs9gsUpkW66DCO+GSjMZhpdKU4XsuIr7zJ/B/2XaN8ihxuzHfYAR4zPtCN4RYg==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.19.0", - "@mui/base": "5.0.0-alpha.103", - "@mui/core-downloads-tracker": "^5.10.11", - "@mui/system": "^5.10.10", - "@mui/types": "^7.2.0", - "@mui/utils": "^5.10.9", - "@types/react-transition-group": "^4.4.5", - "clsx": "^1.2.1", - "csstype": "^3.1.1", + "@babel/runtime": "^7.28.6", + "@mui/core-downloads-tracker": "^7.3.10", + "@mui/system": "^7.3.10", + "@mui/types": "^7.4.12", + "@mui/utils": "^7.3.10", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.2.3", "prop-types": "^15.8.1", - "react-is": "^18.2.0", + "react-is": "^19.2.3", "react-transition-group": "^4.4.5" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" + "@mui/material-pigment-css": "^7.3.10", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -1613,30 +1703,34 @@ "@emotion/styled": { "optional": true }, + "@mui/material-pigment-css": { + "optional": true + }, "@types/react": { "optional": true } } }, "node_modules/@mui/private-theming": { - "version": "5.10.9", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.10.9.tgz", - "integrity": "sha512-BN7/CnsVPVyBaQpDTij4uV2xGYHHHhOgpdxeYLlIu+TqnsVM7wUeF+37kXvHovxM6xmL5qoaVUD98gDC0IZnHg==", + "version": "7.3.10", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.10.tgz", + "integrity": "sha512-j3EZN+zOctxUISvJSmsEPo5o2F8zse4l5vRkBY+ps6UtnL6J7o14kUaI4w7gwo73id9e3cDNMVQK/9BVaMHVBw==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.19.0", - "@mui/utils": "^5.10.9", + "@babel/runtime": "^7.28.6", + "@mui/utils": "^7.3.10", "prop-types": "^15.8.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -1645,26 +1739,29 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.10.8", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.10.8.tgz", - "integrity": "sha512-w+y8WI18EJV6zM/q41ug19cE70JTeO6sWFsQ7tgePQFpy6ToCVPh0YLrtqxUZXSoMStW5FMw0t9fHTFAqPbngw==", + "version": "7.3.10", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.10.tgz", + "integrity": "sha512-WxE9SiF8xskAQqGjsp0poXCkCqsoXFEsSr0HBXfApmGHR+DBnXRp+z46Vsltg4gpPM4Z96DeAQRpeAOnhNg7Ng==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.19.0", - "@emotion/cache": "^11.10.3", - "csstype": "^3.1.1", + "@babel/runtime": "^7.28.6", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.2.3", "prop-types": "^15.8.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0" + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -1676,31 +1773,32 @@ } }, "node_modules/@mui/system": { - "version": "5.10.10", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.10.10.tgz", - "integrity": "sha512-TXwtKN0adKpBrZmO+eilQWoPf2veh050HLYrN78Kps9OhlvO70v/2Kya0+mORFhu9yhpAwjHXO8JII/R4a5ZLA==", + "version": "7.3.10", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.10.tgz", + "integrity": "sha512-/sfPpdpJaQn7BSF+avjIdHSYmxHp0UOBYNxSG9QGKfMOD6sLANCpRPCnanq1Pe0lFf0NHkO2iUk0TNzdWC1USQ==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.19.0", - "@mui/private-theming": "^5.10.9", - "@mui/styled-engine": "^5.10.8", - "@mui/types": "^7.2.0", - "@mui/utils": "^5.10.9", - "clsx": "^1.2.1", - "csstype": "^3.1.1", + "@babel/runtime": "^7.28.6", + "@mui/private-theming": "^7.3.10", + "@mui/styled-engine": "^7.3.10", + "@mui/types": "^7.4.12", + "@mui/utils": "^7.3.10", + "clsx": "^2.1.1", + "csstype": "^3.2.3", "prop-types": "^15.8.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -1715,11 +1813,15 @@ } }, "node_modules/@mui/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.0.tgz", - "integrity": "sha512-lGXtFKe5lp3UxTBGqKI1l7G8sE2xBik8qCfrLHD5olwP/YU0/ReWoWT7Lp1//ri32dK39oPMrJN8TgbkCSbsNA==", + "version": "7.4.12", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.12.tgz", + "integrity": "sha512-iKNAF2u9PzSIj40CjvKJWxFXJo122jXVdrmdh0hMYd+FR+NuJMkr/L88XwWLCRiJ5P1j+uyac25+Kp6YC4hu6w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6" + }, "peerDependencies": { - "@types/react": "*" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -1728,137 +1830,90 @@ } }, "node_modules/@mui/utils": { - "version": "5.10.9", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.10.9.tgz", - "integrity": "sha512-2tdHWrq3+WCy+G6TIIaFx3cg7PorXZ71P375ExuX61od1NOAJP1mK90VxQ8N4aqnj2vmO3AQDkV4oV2Ktvt4bA==", + "version": "7.3.10", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.10.tgz", + "integrity": "sha512-7y2eIfy0h7JPz+Yy4pS+wgV68d46PuuxDqKBN4Q8VlPQSsCAGwroMCV6xWyc7g9dvEp8ZNFsknc59GHWO+r6Ow==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.19.0", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^16.7.1 || ^17.0.0", + "@babel/runtime": "^7.28.6", + "@mui/types": "^7.4.12", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "react-is": "^19.2.3" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "react": "^17.0.0 || ^18.0.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "license": "MIT", "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } } }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + "node_modules/@reduxjs/toolkit/node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "node_modules/@remix-run/router": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.2.tgz", - "integrity": "sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ==", - "engines": { - "node": ">=14" + "node_modules/@reduxjs/toolkit/node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", "dev": true, "license": "MIT" }, @@ -2212,25 +2267,55 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@testing-library/dom": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.19.0.tgz", - "integrity": "sha512-6YWYPPpxG3e/xOo6HIWwB/58HukkwIVTOaZ0VwdMVjhRUX/01E4FtQbck9GazOOj7MXHc5RBzMrU86iBJHbI+A==", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", - "@types/aria-query": "^4.2.0", - "aria-query": "^5.0.0", - "chalk": "^4.1.0", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.4.4", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", "pretty-format": "^27.0.2" }, "engines": { - "node": ">=12" + "node": ">=18" } }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@testing-library/dom/node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, "node_modules/@testing-library/jest-dom": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", @@ -2251,36 +2336,40 @@ "yarn": ">=1" } }, - "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", - "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", - "dev": true, - "license": "MIT" - }, "node_modules/@testing-library/react": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz", - "integrity": "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==", + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^8.5.0", - "@types/react-dom": "^18.0.0" + "@babel/runtime": "^7.12.5" }, "engines": { - "node": ">=12" + "node": ">=18" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, "node_modules/@types/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", - "dev": true + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -2297,33 +2386,54 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, + "license": "MIT", "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__traverse": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.2.tgz", - "integrity": "sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.3.0" + "@babel/types": "^7.28.2" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/dompurify": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", @@ -2334,67 +2444,62 @@ "@types/trusted-types": "*" } }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true - }, - "node_modules/@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", - "dev": true - }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, - "node_modules/@types/jquery": { - "version": "3.5.14", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz", - "integrity": "sha512-X1gtMRMbziVQkErhTQmSe2jFwwENA/Zr+PprCkF63vFq+Yt5PZ4AlKqgmeNlwgn7dhsXEK888eIW2520EpC+xg==", "dev": true, - "dependencies": { - "@types/sizzle": "*" - } + "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.14.186", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.186.tgz", - "integrity": "sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==", - "dev": true + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { - "version": "18.11.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.7.tgz", - "integrity": "sha512-LhFTglglr63mNXUSRYD8A+ZAIu5sFqNJ4Y2fPuY7UlrySJH87rRRlhtVmMHplmfk5WkoJGmDjE9oiTfyX94CpQ==" + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } }, "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" }, "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" }, "node_modules/@types/react": { "version": "18.0.24", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.24.tgz", "integrity": "sha512-wRJWT6ouziGUy+9uX0aW4YOJxAY0bG6/AOk5AW5QSvZqI7dk6VBIbXvcVgIw/W5Jrl24f77df98GEKTJGOLx7Q==", + "dev": true, + "license": "MIT", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -2406,103 +2511,46 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.8.tgz", "integrity": "sha512-C3GYO0HLaOkk9dDAz3Dl4sbe4AKUGTCfFIZsz3n/82dPNN8Du533HzKatDxeUYWu24wJgMP1xICqkWk1YOLOIw==", "dev": true, + "license": "MIT", "dependencies": { "@types/react": "*" } }, - "node_modules/@types/react-is": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", - "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-redux": { - "version": "7.1.24", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz", - "integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==", - "dev": true, - "dependencies": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" - } - }, - "node_modules/@types/react-router": { - "version": "5.1.19", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.19.tgz", - "integrity": "sha512-Fv/5kb2STAEMT3wHzdKQK2z8xKq38EDIGVrutYLmQVVLe+4orDFquU52hQrULnEHinMKv9FSA6lf9+uNT1ITtA==", - "dev": true, - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*" - } - }, - "node_modules/@types/react-router-dom": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", - "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", - "dev": true, - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "*" - } - }, "node_modules/@types/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", - "dependencies": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { "@types/react": "*" } }, "node_modules/@types/react-virtualized-auto-sizer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.1.tgz", - "integrity": "sha512-GH8sAnBEM5GV9LTeiz56r4ZhMOUSrP43tAQNSRVxNexDjcNKLCEtnxusAItg1owFUFE6k0NslV26gqVClVvong==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.4.tgz", + "integrity": "sha512-nhYwlFiYa8M3S+O2T9QO/e1FQUYMr/wJENUdf/O0dhRi1RS/93rjrYQFYdbUqtdFySuhrtnEDX29P6eKOttY+A==", "dev": true, + "license": "MIT", "dependencies": { "@types/react": "*" } }, "node_modules/@types/react-window": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.5.tgz", - "integrity": "sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==", + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz", + "integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==", "dev": true, + "license": "MIT", "dependencies": { "@types/react": "*" } }, - "node_modules/@types/redux-form": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@types/redux-form/-/redux-form-8.3.5.tgz", - "integrity": "sha512-SchB4i7nxgWNbJS4cXEZducztkvHzVrb5xlAXwfLpbrLPo6tMY06+kx1GqMv42+YnGy9TpCAkF51a21HatqWBA==", - "dev": true, - "dependencies": { - "@types/react": "*", - "redux": "^3.6.0 || ^4.0.0" - } - }, "node_modules/@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true - }, - "node_modules/@types/sizzle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", - "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", - "dev": true + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA==", + "dev": true, + "license": "MIT" }, "node_modules/@types/trusted-types": { "version": "2.0.7", @@ -2512,135 +2560,155 @@ "license": "MIT" }, "node_modules/@types/use-sync-external-store": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.41.0.tgz", - "integrity": "sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz", + "integrity": "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/type-utils": "5.41.0", - "@typescript-eslint/utils": "5.41.0", - "debug": "^4.3.4", - "ignore": "^5.2.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/type-utils": "8.58.2", + "@typescript-eslint/utils": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "@typescript-eslint/parser": "^8.58.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.41.0.tgz", - "integrity": "sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.2.tgz", + "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/typescript-estree": "5.41.0", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "debug": "^4.4.3" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.2.tgz", + "integrity": "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.58.2", + "@typescript-eslint/types": "^8.58.2", + "debug": "^4.4.3" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.41.0.tgz", - "integrity": "sha512-xOxPJCnuktUkY2xoEZBKXO5DBCugFzjrVndKdUnyQr3+9aDWZReKq9MhaoVnbL+maVwWJu/N0SEtrtEUNb62QQ==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz", + "integrity": "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/visitor-keys": "5.41.0" + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.41.0.tgz", - "integrity": "sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz", + "integrity": "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==", "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "5.41.0", - "@typescript-eslint/utils": "5.41.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.2.tgz", + "integrity": "sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.41.0.tgz", - "integrity": "sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.2.tgz", + "integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -2648,103 +2716,86 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.41.0.tgz", - "integrity": "sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz", + "integrity": "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/visitor-keys": "5.41.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "@typescript-eslint/project-service": "8.58.2", + "@typescript-eslint/tsconfig-utils": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.41.0.tgz", - "integrity": "sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/typescript-estree": "5.41.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/@typescript-eslint/utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz", + "integrity": "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==", "dev": true, + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2" }, "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.41.0.tgz", - "integrity": "sha512-vilqeHj267v8uzzakbm13HkPMl7cbYpKVjgFWZPIOHIJHZtinvypUhJ5xBXfWYg4eFKqztbMMpOgFpT9Gfx4fw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.41.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz", + "integrity": "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/@typescript/vfs": { @@ -2760,230 +2811,177 @@ "typescript": "*" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.28.0", + "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", + "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" + "react-refresh": "^0.18.0" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/@vitejs/plugin-react/node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/@vitest/coverage-v8": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.1.tgz", - "integrity": "sha512-6YeRZwuO4oTGKxD3bijok756oktHSIm3eczVVzNe3scqzuhLwltIF3S9ZL/vwOVIpURmU6SnZhziXXAfw8/Qlw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.4.tgz", + "integrity": "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.1", - "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.4", + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.4", + "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.4", - "istanbul-reports": "^3.1.6", - "magic-string": "^0.30.5", - "magicast": "^0.3.3", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "test-exclude": "^6.0.0" + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "1.6.1" + "@vitest/browser": "4.1.4", + "vitest": "4.1.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } } }, "node_modules/@vitest/expect": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", - "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", + "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "chai": "^4.3.10" + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", + "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz", + "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", - "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz", + "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "1.6.1", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" + "@vitest/utils": "4.1.4", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner/node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@vitest/runner/node_modules/yocto-queue": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", - "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@vitest/snapshot": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", - "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz", + "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==", "dev": true, "license": "MIT", "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "4.1.4", + "@vitest/utils": "4.1.4", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/snapshot/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@vitest/snapshot/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@vitest/spy": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", - "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz", + "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==", "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^2.2.0" - }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", - "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz", + "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==", "dev": true, "license": "MIT", "dependencies": { - "diff-sequences": "^29.6.3", - "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "4.1.4", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/utils/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/@vitest/utils/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@vitest/utils/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } + "license": "MIT" }, "node_modules/acorn": { "version": "8.16.0", @@ -3008,16 +3006,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/ajv": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", @@ -3040,84 +3028,68 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, "node_modules/aria-query": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.2.tgz", - "integrity": "sha512-JWydkr9MirMg2jGJstDqDgzoHqaFbv7n1ghfXYdtEgXWgdq3jz7IU3SQvtj9k3mAszQBiTpQhFdlH+JIRuGTzg==", - "dev": true, - "dependencies": { - "deep-equal": "^2.0.5" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": ">= 0.4" } }, "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { - "node": "*" + "node": ">=12" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", "dev": true, "license": "MIT" }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -3129,48 +3101,55 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", - "dev": true, - "bin": { - "baseline-browser-mapping": "dist/cli.js" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" } }, - "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "node_modules/baseline-browser-mapping": { + "version": "2.10.18", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.18.tgz", + "integrity": "sha512-VSnGQAOLtP5mib/DPyg2/t+Tlv65NTBz83BJBJvmLVHHuKJVaDOBvJJykiT5TR++em5nfAySPccDZDa4oSrn8A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "require-from-string": "^2.0.2" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=8" + "node": "18 || 20 || >=22" } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -3186,12 +3165,13 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -3200,61 +3180,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001769", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", - "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "version": "1.0.30001787", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz", + "integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==", "dev": true, "funding": [ { @@ -3269,131 +3207,52 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chai": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chai/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.2" - }, - "engines": { - "node": "*" + "node": ">=18" } }, "node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, - "license": "MIT" - }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -3410,6 +3269,7 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3422,52 +3282,48 @@ "node_modules/crypto-js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } }, "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true - }, - "node_modules/cssstyle": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", - "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/css-color": "^3.2.0", - "rrweb-cssom": "^0.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/cssstyle/node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", "dev": true, "license": "MIT" }, "node_modules/csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" }, "node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", "dev": true, "license": "MIT", "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/debug": { @@ -3494,45 +3350,6 @@ "dev": true, "license": "MIT" }, - "node_modules/deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-equal": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz", - "integrity": "sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "es-get-iterator": "^1.1.1", - "get-intrinsic": "^1.0.1", - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.2", - "is-regex": "^1.1.1", - "isarray": "^2.0.5", - "object-is": "^1.1.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.3", - "which-boxed-primitive": "^1.0.1", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3540,102 +3357,34 @@ "dev": true, "license": "MIT" }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dev": true, - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.4.0" + "node": ">=6" } }, "node_modules/dexie": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/dexie/-/dexie-3.2.2.tgz", - "integrity": "sha512-q5dC3HPmir2DERlX+toCBbHQXW5MsyrFqPFcovkH9N2S/UW/H3H5AWAB6iEOExeraAu+j+zRDG+zg/D7YhH0qg==", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.4.2.tgz", + "integrity": "sha512-zMtV8q79EFE5U8FKZvt0Y/77PCU/Hr/RDxv1EDeo228L+m/HTbeN2AjoQm674rhQCX8n3ljK87lajt7UQuZfvw==", + "license": "Apache-2.0" }, "node_modules/dom-accessibility-api": { - "version": "0.5.14", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", - "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==", - "dev": true + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" @@ -3650,166 +3399,55 @@ "@types/trusted-types": "^2.0.7" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", - "dev": true - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", - "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", + "version": "1.5.335", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.335.tgz", + "integrity": "sha512-q9n5T4BR4Xwa2cwbrwcsDJtHD/enpQ5S1xF1IAtdqf5AAgqDFmR/aakqH3ChFdqd/QXJhS3rnnXFtexU7rax6Q==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" - }, + "license": "ISC" + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.4" + "node": ">=0.12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "is-arrayish": "^0.2.1" } }, "node_modules/es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-get-iterator": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", - "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.0", - "has-symbols": "^1.0.1", - "is-arguments": "^1.1.0", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, "engines": { "node": ">= 0.4" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + "license": "MIT" }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3817,32 +3455,35 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, "node_modules/escalade": { @@ -3850,6 +3491,7 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -3858,6 +3500,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -3866,100 +3509,78 @@ } }, "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.0.tgz", + "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.4", + "@eslint/config-helpers": "^0.5.4", + "@eslint/core": "^1.2.0", + "@eslint/plugin-kit": "^0.7.0", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^10.2.4", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { @@ -3975,59 +3596,55 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, + "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" + "node": ">= 4" } }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4046,21 +3663,12 @@ "node": ">=0.10" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -4068,20 +3676,12 @@ "node": ">=4.0" } }, - "node_modules/esrecurse/node_modules/estraverse": { + "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -4106,6 +3706,16 @@ "node": ">=0.10.0" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4113,22 +3723,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -4143,47 +3737,48 @@ "dev": true, "license": "MIT" }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, - "dependencies": { - "reusify": "^1.0.4" + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" + "node": ">=16.0.0" } }, "node_modules/final-form": { - "version": "4.20.7", - "resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.7.tgz", - "integrity": "sha512-ii3X9wNfyBYFnDPunYN5jh1/HAvtOZ9aJI/TVk0MB86hZuOeYkb+W5L3icgwW9WWNztZR6MDU3En6eoZTUoFPg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/final-form/-/final-form-5.0.0.tgz", + "integrity": "sha512-HByosvP7x3N4bWTCPoBeUeoMatadewRifxaH3qhCQI2DBwFNO0m5wxETLVUXNGWz2yokdSCMdJEvtjfZoXnqDA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.0" }, + "engines": { + "node": ">=8" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/final-form" @@ -4193,6 +3788,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/final-form-set-field-touched/-/final-form-set-field-touched-1.0.1.tgz", "integrity": "sha512-yvE5AAs9U3OgJQ9YF8NhSF0I0mJEECvOpkaXNqovloxji5Q6gOZ0DCIAyLAKHluGSpsXKUGORyBm8Hq0beZIqQ==", + "license": "MIT", "peerDependencies": { "final-form": ">=1.2.0" } @@ -4200,7 +3796,8 @@ "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" }, "node_modules/find-up": { "version": "5.0.0", @@ -4220,18 +3817,17 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { @@ -4241,53 +3837,21 @@ "dev": true, "license": "ISC" }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", "dev": true, "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" }, "engines": { - "node": ">=12" + "node": ">=14.14" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -4307,33 +3871,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4343,140 +3881,32 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "is-glob": "^4.0.3" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" + "node": ">=10.13.0" } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "17.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", + "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", "dev": true, "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4489,51 +3919,12 @@ "dev": true, "license": "MIT" }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true, - "license": "MIT" - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "ISC" }, "node_modules/has-flag": { "version": "4.0.0", @@ -4545,52 +3936,11 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -4602,6 +3952,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", "dependencies": { "react-is": "^16.7.0" } @@ -4609,19 +3960,20 @@ "node_modules/hoist-non-react-statics/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" }, "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", "dev": true, "license": "MIT", "dependencies": { - "whatwg-encoding": "^3.1.1" + "@exodus/bytes": "^1.6.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/html-escaper": { @@ -4635,116 +3987,101 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", "dependencies": { "void-elements": "3.1.0" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/husky": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz", - "integrity": "sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==", + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, + "license": "MIT", "bin": { - "husky": "lib/bin.js" + "husky": "bin.js" }, "engines": { - "node": ">=14" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/typicode" } }, "node_modules/i18next": { - "version": "22.0.4", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.0.4.tgz", - "integrity": "sha512-TOp7BTMKDbUkOHMzDlVsCYWpyaFkKakrrO3HNXfSz4EeJaWwnBScRmgQSTaWHScXVHBUFXTvShrCW8uryBYFcg==", + "version": "26.0.4", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.0.4.tgz", + "integrity": "sha512-gXF7U9bfioXPLv7mw8Qt2nfO7vij5MyINvPgVv99pX3fL1Y01pw2mKBFrlYpRxRCl2wz3ISenj6VsMJT2isfuA==", "funding": [ { "type": "individual", - "url": "https://locize.com" - }, - { - "type": "individual", - "url": "https://locize.com/i18next.html" + "url": "https://www.locize.com/i18next" }, { "type": "individual", "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + }, + { + "type": "individual", + "url": "https://www.locize.com" } ], + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.17.2" + "@babel/runtime": "^7.29.2" + }, + "peerDependencies": { + "typescript": "^5 || ^6" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/i18next-browser-languagedetector": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.0.0.tgz", - "integrity": "sha512-RrH7z5/DbhzhgCLDFIKXBTZlb2aXi38ZHa5e5oZaPt9zGLWmgAX49mzkQL/E7R6Y9fTE8QbZFuyMV0ronu4H/Q==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz", + "integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.19.4" + "@babel/runtime": "^7.23.2" } }, "node_modules/i18next-icu": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/i18next-icu/-/i18next-icu-2.0.3.tgz", - "integrity": "sha512-sZ0VCWDnHysUYQL8j/0rVOxv6rLR+SBoaqQQ2UVNfLyJCuf/bAjYPkoUQgyuDkWFo1xZjeCf4G6GBNr7gD61bQ==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/i18next-icu/-/i18next-icu-2.4.3.tgz", + "integrity": "sha512-Clb5XCp416Z+BkJUTATCjmDcw2AFzSUDVLxLVK/KhtXP6TJQHrht+6MqoJU1hCpyoCBKe59wMO9pvCvYroNcKg==", + "license": "MIT", "peerDependencies": { - "intl-messageformat": "^9.0.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" + "intl-messageformat": ">=10.3.3 <12.0.0" } }, "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, + "node_modules/immer": { + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz", + "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -4771,141 +4108,34 @@ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/intl-messageformat": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.2.1.tgz", - "integrity": "sha512-1lrJG2qKzcC1TVzYu1VuB1yiY68LU5rwpbHa2THCzA67Vutkz7+1lv5U20K3Lz5RAiH78zxNztMEtchokMWv8A==", + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.1.tgz", + "integrity": "sha512-1gAVEUt3wEPvTqML4Fsw9klZV5j0vszQxayP/fi6gUroAc8AUHiNaisBKLWxybL1AdWq1mP07YV1q8v4N92ilQ==", + "license": "BSD-3-Clause", "dependencies": { - "@formatjs/ecma402-abstract": "1.13.0", - "@formatjs/fast-memoize": "1.2.6", - "@formatjs/icu-messageformat-parser": "2.1.10", - "tslib": "2.4.0" - } - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@formatjs/fast-memoize": "3.1.2", + "@formatjs/icu-messageformat-parser": "3.5.4" } }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" }, "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -4919,6 +4149,7 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -4928,6 +4159,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -4935,203 +4167,19 @@ "node": ">=0.10.0" } }, - "node_modules/is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "node_modules/is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.9.tgz", - "integrity": "sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", - "for-each": "^0.3.3", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -5158,21 +4206,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-reports": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", @@ -5190,55 +4223,43 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" }, "node_modules/jsdom": { - "version": "24.1.3", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.3.tgz", - "integrity": "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==", + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.2.tgz", + "integrity": "sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==", "dev": true, "license": "MIT", "dependencies": { - "cssstyle": "^4.0.1", - "data-urls": "^5.0.0", - "decimal.js": "^10.4.3", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.5", + "@asamuzakjp/css-color": "^5.1.5", + "@asamuzakjp/dom-selector": "^7.0.6", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.1", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.12", - "parse5": "^7.1.2", - "rrweb-cssom": "^0.7.1", + "lru-cache": "^11.2.7", + "parse5": "^8.0.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.4", + "tough-cookie": "^6.0.1", + "undici": "^7.24.5", "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0", - "ws": "^8.18.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" }, "peerDependencies": { - "canvas": "^2.11.2" + "canvas": "^3.0.0" }, "peerDependenciesMeta": { "canvas": { @@ -5246,6 +4267,16 @@ } } }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -5268,7 +4299,8 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -5289,6 +4321,7 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -5297,10 +4330,11 @@ } }, "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -5335,24 +4369,8 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "node_modules/local-pkg": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", - "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mlly": "^1.7.3", - "pkg-types": "^1.2.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" }, "node_modules/locate-path": { "version": "6.0.0", @@ -5371,26 +4389,16 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, - "node_modules/long": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", - "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==" - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -5398,33 +4406,22 @@ "loose-envify": "cli.js" } }, - "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.1" - } - }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, + "license": "ISC", "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "yallist": "^3.0.2" } }, "node_modules/lz-string": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", - "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, + "license": "MIT", "bin": { "lz-string": "bin/bin.js" } @@ -5440,15 +4437,15 @@ } }, "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" } }, "node_modules/make-dir": { @@ -5467,125 +4464,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } + "license": "CC0-1.0" }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mlly": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", - "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.16.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.3" - } - }, - "node_modules/mlly/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5593,9 +4504,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -5603,6 +4514,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -5618,15 +4530,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true - }, - "node_modules/nwsapi": { - "version": "2.2.23", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", - "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", "dev": true, "license": "MIT" }, @@ -5634,74 +4540,21 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" }, "node_modules/optionator": { "version": "0.9.4", @@ -5757,6 +4610,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -5768,6 +4622,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -5782,9 +4637,9 @@ } }, "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", "dev": true, "license": "MIT", "dependencies": { @@ -5794,19 +4649,6 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parse5/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5817,21 +4659,12 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -5839,73 +4672,35 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/pkg-types/node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", "dev": true, "funding": [ { @@ -5921,8 +4716,9 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -5945,6 +4741,7 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -5954,28 +4751,18 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/pretty-format/node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -5985,43 +4772,8 @@ "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/protobufjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", - "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/psl": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/lupomontero" - } + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" }, "node_modules/punycode": { "version": "2.3.1", @@ -6033,37 +4785,11 @@ "node": ">=6" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" }, @@ -6072,21 +4798,23 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-final-form": { - "version": "6.5.9", - "resolved": "https://registry.npmjs.org/react-final-form/-/react-final-form-6.5.9.tgz", - "integrity": "sha512-x3XYvozolECp3nIjly+4QqxdjSSWfcnpGEL5K8OBT6xmGrq5kBqbA6+/tOqoom9NwqIPPbxPNsOViFlbKgowbA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/react-final-form/-/react-final-form-7.0.0.tgz", + "integrity": "sha512-aEeAWbSsCLVXa4GBkJtjjyhPyX4L/Pgp5P/jXZwdz0YYcK6Zs/0PkgB+qWMSyIsbbGGE7m9yYlSpui5E5Gx26A==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.15.4" }, @@ -6095,35 +4823,38 @@ "url": "https://opencollective.com/final-form" }, "peerDependencies": { - "final-form": "^4.20.4", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "final-form": "^5.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/react-final-form-listeners": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/react-final-form-listeners/-/react-final-form-listeners-1.0.3.tgz", - "integrity": "sha512-OrdCNxSS4JQS/EXD+R530kZKFqaPfa+WcXPgVro/h4BpaBDF/Ja+BtHyCzDezCIb5rWaGGdOJIj+tN2YdtvrXg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-final-form-listeners/-/react-final-form-listeners-3.0.0.tgz", + "integrity": "sha512-uHq1fpXD8ssralaA2mF09CTUjBdGum9KvSym72bNDFBJHE28Ei47AUCcNWfH8MQPvWDOJjWsUw3tzfAM8tVC3A==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.12.5" + "@babel/runtime": "^7.27.4" }, "peerDependencies": { - "final-form": ">=4.0.0", - "prop-types": "^15.6.0", - "react": "^15.3.0 || ^16.0.0 || ^17.0.0", - "react-final-form": ">=3.0.0" + "final-form": ">=5.0.0", + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-final-form": ">=7.0.0" } }, "node_modules/react-i18next": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.0.0.tgz", - "integrity": "sha512-/O7N6aIEAl1FaWZBNvhdIo9itvF/MO/nRKr9pYqRc9LhuC1u21SlfwpiYQqvaeNSEW3g3qUXLREOWMt+gxrWbg==", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.2.tgz", + "integrity": "sha512-shBftH2vaTWK2Bsp7FiL+cevx3xFJlvFxmsDFQSrJc+6twHkP0tv/bGa01VVWzpreUVVwU+3Hev5iFqRg65RwA==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.14.5", - "html-parse-stringify": "^3.0.1" + "@babel/runtime": "^7.29.2", + "html-parse-stringify": "^3.0.1", + "use-sync-external-store": "^1.6.0" }, "peerDependencies": { - "i18next": ">= 19.0.0", - "react": ">= 16.8.0" + "i18next": ">= 26.0.1", + "react": ">= 16.8.0", + "typescript": "^5 || ^6" }, "peerDependenciesMeta": { "react-dom": { @@ -6131,86 +4862,94 @@ }, "react-native": { "optional": true + }, + "typescript": { + "optional": true } } }, "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.5.tgz", + "integrity": "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==", + "license": "MIT" }, "node_modules/react-redux": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.4.tgz", - "integrity": "sha512-yMfQ7mX6bWuicz2fids6cR1YT59VTuT8MKyyE310wJQlINKENCeT1UcPdEiX6znI5tF8zXyJ/VYvDgeGuaaNwQ==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.12.1", - "@types/hoist-non-react-statics": "^3.3.1", - "@types/use-sync-external-store": "^0.0.3", - "hoist-non-react-statics": "^3.3.2", - "react-is": "^18.0.0", - "use-sync-external-store": "^1.0.0" + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" }, "peerDependencies": { - "@types/react": "^16.8 || ^17.0 || ^18.0", - "@types/react-dom": "^16.8 || ^17.0 || ^18.0", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0", - "react-native": ">=0.59", - "redux": "^4" + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" }, "peerDependenciesMeta": { "@types/react": { "optional": true }, - "@types/react-dom": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - }, "redux": { "optional": true } } }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react-router": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.2.tgz", - "integrity": "sha512-Rb0BAX9KHhVzT1OKhMvCDMw776aTYM0DtkxqUBP8dNBom3mPXlfNs76JNGK8wKJ1IZEY1+WGj+cvZxHVk/GiKw==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.1.tgz", + "integrity": "sha512-5BCvFskyAAVumqhEKh/iPhLOIkfxcEUz8WqFIARCkMg8hZZzDYX9CtwxXA0e+qT8zAxmMC0x3Ckb9iMONwc5jg==", + "license": "MIT", "dependencies": { - "@remix-run/router": "1.0.2" + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" }, "engines": { - "node": ">=14" + "node": ">=20.0.0" }, "peerDependencies": { - "react": ">=16.8" + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } } }, "node_modules/react-router-dom": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.2.tgz", - "integrity": "sha512-yM1kjoTkpfjgczPrcyWrp+OuQMyB1WleICiiGfstnQYo/S8hPEEnVjr/RdmlH6yKK4Tnj1UGXFSa7uwAtmDoLQ==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.1.tgz", + "integrity": "sha512-ZkrQuwwhGibjQLqH1eCdyiZyLWglPxzxdl5tgwgKEyCSGC76vmAjleGocRe3J/MLfzMUIKwaFJWpFVJhK3d2xA==", + "license": "MIT", "dependencies": { - "@remix-run/router": "1.0.2", - "react-router": "6.4.2" + "react-router": "7.14.1" }, "engines": { - "node": ">=14" + "node": ">=20.0.0" }, "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" + "react": ">=18", + "react-dom": ">=18" } }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -6223,31 +4962,23 @@ } }, "node_modules/react-virtualized-auto-sizer": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.7.tgz", - "integrity": "sha512-Mxi6lwOmjwIjC1X4gABXMJcKHsOo0xWl3E3ugOgufB8GJU+MqrtY35aBuvCYv/razQ1Vbp7h1gWJjGjoNN5pmA==", - "engines": { - "node": ">8.0.0" - }, + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-2.0.3.tgz", + "integrity": "sha512-nonmCSUIh5HtbzazGcQ1NhnMFps/ZBu/UKJyhCt0Fhi7ondLAUZNETtRCWM8RWYZDzVlMYOQGgBmIxUutIhqgw==", + "license": "MIT", "peerDependencies": { - "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0-rc", - "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0-rc" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/react-window": { - "version": "1.8.7", - "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.7.tgz", - "integrity": "sha512-JHEZbPXBpKMmoNO1bNhoXOOLg/ujhL/BU4IqVU9r8eQPcy5KQnGHIHDRkJ0ns9IM5+Aq5LNwt3j8t3tIrePQzA==", - "dependencies": { - "@babel/runtime": "^7.0.0", - "memoize-one": ">=3.1.1 <6" - }, - "engines": { - "node": ">8.0.0" - }, + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-2.2.7.tgz", + "integrity": "sha512-SH5nvfUQwGHYyriDUAOt7wfPsfG9Qxd6OdzQxl5oQ4dsSsUicqQvjV7dR+NqZ4coY0fUn3w1jnC5PwzIUWEg5w==", + "license": "MIT", "peerDependencies": { - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/redent": { @@ -6255,6 +4986,7 @@ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, + "license": "MIT", "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" @@ -6263,107 +4995,39 @@ "node": ">=8" } }, - "node_modules/redux": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", - "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, - "node_modules/redux-form": { - "version": "8.3.8", - "resolved": "https://registry.npmjs.org/redux-form/-/redux-form-8.3.8.tgz", - "integrity": "sha512-PzXhA0d+awIc4PkuhbDa6dCEiraMrGMyyDlYEVNX6qEyW/G2SqZXrjav5zrpXb0CCeqQSc9iqwbMtYQXbJbOAQ==", - "dependencies": { - "@babel/runtime": "^7.9.2", - "es6-error": "^4.1.1", - "hoist-non-react-statics": "^3.3.2", - "invariant": "^2.2.4", - "is-promise": "^2.1.0", - "lodash": "^4.17.15", - "prop-types": "^15.6.1", - "react-is": "^16.4.2" - }, - "engines": { - "node": ">=8.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/redux-form" - }, - "peerDependencies": { - "immutable": "^3.8.2 || ^4.0.0", - "react": "^16.4.2 || ^17.0.0", - "react-redux": "^6.0.1 || ^7.0.0", - "redux": "^3.7.2 || ^4.0.0" - }, - "peerDependenciesMeta": { - "immutable": { - "optional": true - } - } - }, - "node_modules/redux-form/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/redux-thunk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", - "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", - "peerDependencies": { - "redux": "^4" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - }, + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", "dependencies": { - "is-core-module": "^2.9.0", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6372,738 +5036,12 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", "engines": { "node": ">=4" } }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rrweb-cssom": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", - "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", - "dev": true, - "license": "MIT" - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rxjs": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", - "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, - "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true, - "license": "MIT" - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-literal": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", - "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/strip-literal/node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/stylis": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", - "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinypool": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", - "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", - "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tsconfck": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", - "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", - "dev": true, - "license": "MIT", - "bin": { - "tsconfck": "bin/tsconfck.js" - }, - "engines": { - "node": "^18 || >=20" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", - "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "vite": "^5.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vite-tsconfig-paths": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz", - "integrity": "sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "globrex": "^0.1.2", - "tsconfck": "^3.0.3" - }, - "peerDependencies": { - "vite": "*" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/rollup": { + "node_modules/rollup": { "version": "4.60.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", @@ -7148,59 +5086,623 @@ "fsevents": "~2.3.2" } }, - "node_modules/vitest": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", - "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "1.6.1", - "@vitest/runner": "1.6.1", - "@vitest/snapshot": "1.6.1", - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.3", - "vite": "^5.0.0", - "vite-node": "1.6.1", - "why-is-node-running": "^2.2.2" + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.28.tgz", + "integrity": "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.28" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.28.tgz", + "integrity": "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "dev": true, + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.2.tgz", + "integrity": "sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.58.2", + "@typescript-eslint/parser": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-tsconfig-paths": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", + "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz", + "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.4", + "@vitest/mocker": "4.1.4", + "@vitest/pretty-format": "4.1.4", + "@vitest/runner": "4.1.4", + "@vitest/snapshot": "4.1.4", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.6.1", - "@vitest/ui": "1.6.1", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.4", + "@vitest/browser-preview": "4.1.4", + "@vitest/browser-webdriverio": "4.1.4", + "@vitest/coverage-istanbul": "4.1.4", + "@vitest/coverage-v8": "4.1.4", + "@vitest/ui": "4.1.4", "happy-dom": "*", - "jsdom": "*" + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { "optional": true }, + "@opentelemetry/api": { + "optional": true + }, "@types/node": { "optional": true }, - "@vitest/browser": { + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { "optional": true }, "@vitest/ui": { @@ -7211,170 +5713,30 @@ }, "jsdom": { "optional": true + }, + "vite": { + "optional": false } } }, - "node_modules/vitest/node_modules/acorn-walk": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", - "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/vitest/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/vitest/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/vitest/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/vitest/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -7393,51 +5755,38 @@ } }, "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "dev": true, "license": "BSD-2-Clause", "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", "dev": true, "license": "MIT", "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/which": { @@ -7445,6 +5794,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -7455,57 +5805,6 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dev": true, - "dependencies": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.8.tgz", - "integrity": "sha512-Jn4e5PItbcAHyLoRDwvPj1ypu27DJbtdYXUa5zsinrUx77Uvfb0cXwwnGMTn7cjUfhhqgVQnVJCwF+7cgU7tpw==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", - "for-each": "^0.3.3", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -7533,35 +5832,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -7580,15 +5850,17 @@ "license": "MIT" }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", "engines": { "node": ">= 6" } @@ -7606,4923 +5878,5 @@ "url": "https://github.com/sponsors/sindresorhus" } } - }, - "dependencies": { - "@adobe/css-tools": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", - "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", - "dev": true - }, - "@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "@asamuzakjp/css-color": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", - "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", - "dev": true, - "requires": { - "@csstools/css-calc": "^2.1.3", - "@csstools/css-color-parser": "^3.0.9", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "lru-cache": "^10.4.3" - }, - "dependencies": { - "lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true - } - } - }, - "@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "requires": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - } - }, - "@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true - }, - "@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "dependencies": { - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "requires": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, - "@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==" - }, - "@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "requires": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - } - }, - "@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==" - }, - "@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" - }, - "@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==" - }, - "@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true - }, - "@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", - "dev": true, - "requires": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" - } - }, - "@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", - "requires": { - "@babel/types": "^7.29.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.27.1" - } - }, - "@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.27.1" - } - }, - "@babel/runtime": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", - "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==" - }, - "@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "requires": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - } - }, - "@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "requires": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - } - }, - "@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "requires": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@bufbuild/buf": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.67.0.tgz", - "integrity": "sha512-BLfgGmNFiHM79PcaafFNiP/+xxbdyFp1neDDdJd6R0tu7McO+WgJHM6vyNYRm7vXOSgO1uUPE4X3YFdBgcWk2Q==", - "dev": true, - "requires": { - "@bufbuild/buf-darwin-arm64": "1.67.0", - "@bufbuild/buf-darwin-x64": "1.67.0", - "@bufbuild/buf-linux-aarch64": "1.67.0", - "@bufbuild/buf-linux-armv7": "1.67.0", - "@bufbuild/buf-linux-x64": "1.67.0", - "@bufbuild/buf-win32-arm64": "1.67.0", - "@bufbuild/buf-win32-x64": "1.67.0" - } - }, - "@bufbuild/buf-darwin-arm64": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.67.0.tgz", - "integrity": "sha512-9h/1E2FNCSIt9m4wriGiXt8gHrg8VBOOpmUPVr68axZxb17krPQrIZBPsx05yNpbyvSrPj26/jO2aoqpZsG1vw==", - "dev": true, - "optional": true - }, - "@bufbuild/buf-darwin-x64": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.67.0.tgz", - "integrity": "sha512-9kNu0JBR+TQvxCD6NBooy03g8sLNZGEd0umkWHzdO/05HuV/J6GecMGx1kJ2MYlZQHM4/MljfIuYQUblP1nP4A==", - "dev": true, - "optional": true - }, - "@bufbuild/buf-linux-aarch64": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.67.0.tgz", - "integrity": "sha512-hlA20Oot20nW/9CzPBMPPPMfUarKvzqni+Njgrw8T43IFoQWQv8iIRoWWOgOQTGCm4PmjYwiojzEHOEaaKrzTg==", - "dev": true, - "optional": true - }, - "@bufbuild/buf-linux-armv7": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-armv7/-/buf-linux-armv7-1.67.0.tgz", - "integrity": "sha512-hO9FEEtloITNaxW89rzKUjAsgnX1+rth7IZbK0Z+ohatXdanYg7Kv66yWffytaYf2iHltTbY6W/H4C3x0Uimbg==", - "dev": true, - "optional": true - }, - "@bufbuild/buf-linux-x64": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.67.0.tgz", - "integrity": "sha512-KBOWZ0NbhJSfXLM3JEX2AEs32jyHvTKD7wkIYudqOTxPUqwM1MXUg7m2Xw5nP1pcKH4RKS5HFijPMeOW/XUQ8Q==", - "dev": true, - "optional": true - }, - "@bufbuild/buf-win32-arm64": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.67.0.tgz", - "integrity": "sha512-ARGPwOv0lkUp3FU7bUMpYzqoJInx2qkk1ECBEC9XZMnRKmhCbyzmBoBKChBBJhEyDFdzPivhjg//zk5AlQ3bFA==", - "dev": true, - "optional": true - }, - "@bufbuild/buf-win32-x64": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.67.0.tgz", - "integrity": "sha512-x9fkxEbjb2U4petBbESvNx+sfSQJONJxKOQzPfEKALksqRlvh7ktoHrYbygErnRZBSTNgrXzAqFI1GxMGEGSLQ==", - "dev": true, - "optional": true - }, - "@bufbuild/protobuf": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz", - "integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==" - }, - "@bufbuild/protoc-gen-es": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@bufbuild/protoc-gen-es/-/protoc-gen-es-2.11.0.tgz", - "integrity": "sha512-VzQuwEQDXipbZ1soWUuAWm1Z0C3B/IDWGeysnbX6ogJ6As91C2mdvAND/ekQ4YIWgen4d5nqLfIBOWLqCCjYUA==", - "dev": true, - "requires": { - "@bufbuild/protobuf": "2.11.0", - "@bufbuild/protoplugin": "2.11.0" - } - }, - "@bufbuild/protoplugin": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@bufbuild/protoplugin/-/protoplugin-2.11.0.tgz", - "integrity": "sha512-lyZVNFUHArIOt4W0+dwYBe5GBwbKzbOy8ObaloEqsw9Mmiwv2O48TwddDoHN4itylC+BaEGqFdI1W8WQt2vWJQ==", - "dev": true, - "requires": { - "@bufbuild/protobuf": "2.11.0", - "@typescript/vfs": "^1.6.2", - "typescript": "5.4.5" - }, - "dependencies": { - "typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "dev": true - } - } - }, - "@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", - "dev": true - }, - "@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", - "dev": true - }, - "@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", - "dev": true, - "requires": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" - } - }, - "@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", - "dev": true - }, - "@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", - "dev": true - }, - "@emotion/babel-plugin": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.5.tgz", - "integrity": "sha512-xE7/hyLHJac7D2Ve9dKroBBZqBT7WuPQmWcq7HSGb84sUuP4mlOWoB8dvVfD9yk5DHkU1m6RW7xSoDtnQHNQeA==", - "requires": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/plugin-syntax-jsx": "^7.17.12", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/serialize": "^1.1.1", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.1.3" - } - }, - "@emotion/cache": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz", - "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==", - "requires": { - "@emotion/memoize": "^0.8.0", - "@emotion/sheet": "^1.2.1", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", - "stylis": "4.1.3" - } - }, - "@emotion/hash": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", - "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==" - }, - "@emotion/is-prop-valid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", - "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", - "requires": { - "@emotion/memoize": "^0.8.0" - } - }, - "@emotion/memoize": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", - "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" - }, - "@emotion/react": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.5.tgz", - "integrity": "sha512-TZs6235tCJ/7iF6/rvTaOH4oxQg2gMAcdHemjwLKIjKz4rRuYe1HJ2TQJKnAcRAfOUDdU8XoDadCe1rl72iv8A==", - "requires": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.5", - "@emotion/cache": "^11.10.5", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", - "hoist-non-react-statics": "^3.3.1" - } - }, - "@emotion/serialize": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz", - "integrity": "sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==", - "requires": { - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/unitless": "^0.8.0", - "@emotion/utils": "^1.2.0", - "csstype": "^3.0.2" - } - }, - "@emotion/sheet": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz", - "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==" - }, - "@emotion/styled": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.5.tgz", - "integrity": "sha512-8EP6dD7dMkdku2foLoruPCNkRevzdcBaY6q0l0OsbyJK+x8D9HWjX27ARiSIKNF634hY9Zdoedh8bJCiva8yZw==", - "requires": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.5", - "@emotion/is-prop-valid": "^1.2.0", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0" - } - }, - "@emotion/unitless": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", - "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" - }, - "@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", - "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==" - }, - "@emotion/utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz", - "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==" - }, - "@emotion/weak-memoize": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", - "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" - }, - "@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "dev": true, - "optional": true - }, - "@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^3.4.3" - } - }, - "@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - } - }, - "@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true - }, - "@formatjs/ecma402-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.13.0.tgz", - "integrity": "sha512-CQ8Ykd51jYD1n05dtoX6ns6B9n/+6ZAxnWUAonvHC4kkuAemROYBhHkEB4tm1uVrRlE7gLDqXkAnY51Y0pRCWQ==", - "requires": { - "@formatjs/intl-localematcher": "0.2.31", - "tslib": "2.4.0" - } - }, - "@formatjs/fast-memoize": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.6.tgz", - "integrity": "sha512-9CWZ3+wCkClKHX+i5j+NyoBVqGf0pIskTo6Xl6ihGokYM2yqSSS68JIgeo+99UIHc+7vi9L3/SDSz/dWI9SNlA==", - "requires": { - "tslib": "2.4.0" - } - }, - "@formatjs/icu-messageformat-parser": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.10.tgz", - "integrity": "sha512-KkRMxhifWkRC45dhM9tqm0GXbb6NPYTGVYY3xx891IKc6p++DQrZTnmkVSNNO47OEERLfuP2KkPFPJBuu8z/wg==", - "requires": { - "@formatjs/ecma402-abstract": "1.13.0", - "@formatjs/icu-skeleton-parser": "1.3.14", - "tslib": "2.4.0" - } - }, - "@formatjs/icu-skeleton-parser": { - "version": "1.3.14", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.14.tgz", - "integrity": "sha512-7bv60HQQcBb3+TSj+45tOb/CHV5z1hOpwdtS50jsSBXfB+YpGhnoRsZxSRksXeCxMy6xn6tA6VY2601BrrK+OA==", - "requires": { - "@formatjs/ecma402-abstract": "1.13.0", - "tslib": "2.4.0" - } - }, - "@formatjs/intl-localematcher": { - "version": "0.2.31", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.31.tgz", - "integrity": "sha512-9QTjdSBpQ7wHShZgsNzNig5qT3rCPvmZogS/wXZzKotns5skbXgs0I7J8cuN0PPqXyynvNVuN+iOKhNS2eb+ZA==", - "requires": { - "tslib": "2.4.0" - } - }, - "@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dev": true - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.27.8" - }, - "dependencies": { - "@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", - "dev": true - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "requires": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" - }, - "@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" - }, - "@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "@mui/base": { - "version": "5.0.0-alpha.103", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.103.tgz", - "integrity": "sha512-fJIyB2df3CHn7D26WHnutnY7vew6aytTlhmRJz6GX7ag19zU2GcOUhJAzY5qwWcrXKnlYgzimhEjaEnuiUWU4g==", - "requires": { - "@babel/runtime": "^7.19.0", - "@emotion/is-prop-valid": "^1.2.0", - "@mui/types": "^7.2.0", - "@mui/utils": "^5.10.9", - "@popperjs/core": "^2.11.6", - "clsx": "^1.2.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" - } - }, - "@mui/core-downloads-tracker": { - "version": "5.10.11", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.10.11.tgz", - "integrity": "sha512-u5ff+UCFDHcR8MoQ8tuJR4c35vt7T/ki3aMEE2O3XQoGs8KJSrBiisFpFKyldg9/W2NSyoZxN+kxEGIfRxh+9Q==" - }, - "@mui/icons-material": { - "version": "5.10.9", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.10.9.tgz", - "integrity": "sha512-sqClXdEM39WKQJOQ0ZCPTptaZgqwibhj2EFV9N0v7BU1PO8y4OcX/a2wIQHn4fNuDjIZktJIBrmU23h7aqlGgg==", - "requires": { - "@babel/runtime": "^7.19.0" - } - }, - "@mui/material": { - "version": "5.10.11", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.10.11.tgz", - "integrity": "sha512-KJ0wPCTbv6sFzwA3dgg0gowdfF+SRl7D510J9l6Nl/KFX0EawcewQudqKY4slYGFXniKa5PykqokpaWXsCCPqg==", - "requires": { - "@babel/runtime": "^7.19.0", - "@mui/base": "5.0.0-alpha.103", - "@mui/core-downloads-tracker": "^5.10.11", - "@mui/system": "^5.10.10", - "@mui/types": "^7.2.0", - "@mui/utils": "^5.10.9", - "@types/react-transition-group": "^4.4.5", - "clsx": "^1.2.1", - "csstype": "^3.1.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0", - "react-transition-group": "^4.4.5" - } - }, - "@mui/private-theming": { - "version": "5.10.9", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.10.9.tgz", - "integrity": "sha512-BN7/CnsVPVyBaQpDTij4uV2xGYHHHhOgpdxeYLlIu+TqnsVM7wUeF+37kXvHovxM6xmL5qoaVUD98gDC0IZnHg==", - "requires": { - "@babel/runtime": "^7.19.0", - "@mui/utils": "^5.10.9", - "prop-types": "^15.8.1" - } - }, - "@mui/styled-engine": { - "version": "5.10.8", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.10.8.tgz", - "integrity": "sha512-w+y8WI18EJV6zM/q41ug19cE70JTeO6sWFsQ7tgePQFpy6ToCVPh0YLrtqxUZXSoMStW5FMw0t9fHTFAqPbngw==", - "requires": { - "@babel/runtime": "^7.19.0", - "@emotion/cache": "^11.10.3", - "csstype": "^3.1.1", - "prop-types": "^15.8.1" - } - }, - "@mui/system": { - "version": "5.10.10", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.10.10.tgz", - "integrity": "sha512-TXwtKN0adKpBrZmO+eilQWoPf2veh050HLYrN78Kps9OhlvO70v/2Kya0+mORFhu9yhpAwjHXO8JII/R4a5ZLA==", - "requires": { - "@babel/runtime": "^7.19.0", - "@mui/private-theming": "^5.10.9", - "@mui/styled-engine": "^5.10.8", - "@mui/types": "^7.2.0", - "@mui/utils": "^5.10.9", - "clsx": "^1.2.1", - "csstype": "^3.1.1", - "prop-types": "^15.8.1" - } - }, - "@mui/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.0.tgz", - "integrity": "sha512-lGXtFKe5lp3UxTBGqKI1l7G8sE2xBik8qCfrLHD5olwP/YU0/ReWoWT7Lp1//ri32dK39oPMrJN8TgbkCSbsNA==" - }, - "@mui/utils": { - "version": "5.10.9", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.10.9.tgz", - "integrity": "sha512-2tdHWrq3+WCy+G6TIIaFx3cg7PorXZ71P375ExuX61od1NOAJP1mK90VxQ8N4aqnj2vmO3AQDkV4oV2Ktvt4bA==", - "requires": { - "@babel/runtime": "^7.19.0", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^16.7.1 || ^17.0.0", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" - }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "@remix-run/router": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.2.tgz", - "integrity": "sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ==" - }, - "@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true - }, - "@rollup/rollup-android-arm-eabi": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", - "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-android-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", - "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-darwin-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", - "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", - "dev": true, - "optional": true - }, - "@rollup/rollup-darwin-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", - "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", - "dev": true, - "optional": true - }, - "@rollup/rollup-freebsd-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", - "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", - "dev": true, - "optional": true - }, - "@rollup/rollup-freebsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", - "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", - "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", - "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", - "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", - "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", - "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-loong64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", - "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", - "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", - "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", - "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", - "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", - "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", - "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-x64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", - "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", - "dev": true, - "optional": true - }, - "@rollup/rollup-openbsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", - "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", - "dev": true, - "optional": true - }, - "@rollup/rollup-openharmony-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", - "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", - "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", - "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", - "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-x64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", - "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", - "dev": true, - "optional": true - }, - "@testing-library/dom": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.19.0.tgz", - "integrity": "sha512-6YWYPPpxG3e/xOo6HIWwB/58HukkwIVTOaZ0VwdMVjhRUX/01E4FtQbck9GazOOj7MXHc5RBzMrU86iBJHbI+A==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^4.2.0", - "aria-query": "^5.0.0", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.4.4", - "pretty-format": "^27.0.2" - } - }, - "@testing-library/jest-dom": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", - "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", - "dev": true, - "requires": { - "@adobe/css-tools": "^4.4.0", - "aria-query": "^5.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.6.3", - "picocolors": "^1.1.1", - "redent": "^3.0.0" - }, - "dependencies": { - "dom-accessibility-api": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", - "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", - "dev": true - } - } - }, - "@testing-library/react": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz", - "integrity": "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^8.5.0", - "@types/react-dom": "^18.0.0" - } - }, - "@types/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", - "dev": true - }, - "@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "requires": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.2.tgz", - "integrity": "sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/dompurify": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", - "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", - "dev": true, - "requires": { - "@types/trusted-types": "*" - } - }, - "@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true - }, - "@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", - "dev": true - }, - "@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "requires": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, - "@types/jquery": { - "version": "3.5.14", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz", - "integrity": "sha512-X1gtMRMbziVQkErhTQmSe2jFwwENA/Zr+PprCkF63vFq+Yt5PZ4AlKqgmeNlwgn7dhsXEK888eIW2520EpC+xg==", - "dev": true, - "requires": { - "@types/sizzle": "*" - } - }, - "@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "@types/lodash": { - "version": "4.14.186", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.186.tgz", - "integrity": "sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==", - "dev": true - }, - "@types/node": { - "version": "18.11.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.7.tgz", - "integrity": "sha512-LhFTglglr63mNXUSRYD8A+ZAIu5sFqNJ4Y2fPuY7UlrySJH87rRRlhtVmMHplmfk5WkoJGmDjE9oiTfyX94CpQ==" - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" - }, - "@types/react": { - "version": "18.0.24", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.24.tgz", - "integrity": "sha512-wRJWT6ouziGUy+9uX0aW4YOJxAY0bG6/AOk5AW5QSvZqI7dk6VBIbXvcVgIw/W5Jrl24f77df98GEKTJGOLx7Q==", - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "@types/react-dom": { - "version": "18.0.8", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.8.tgz", - "integrity": "sha512-C3GYO0HLaOkk9dDAz3Dl4sbe4AKUGTCfFIZsz3n/82dPNN8Du533HzKatDxeUYWu24wJgMP1xICqkWk1YOLOIw==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/react-is": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", - "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", - "requires": { - "@types/react": "*" - } - }, - "@types/react-redux": { - "version": "7.1.24", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz", - "integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==", - "dev": true, - "requires": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" - } - }, - "@types/react-router": { - "version": "5.1.19", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.19.tgz", - "integrity": "sha512-Fv/5kb2STAEMT3wHzdKQK2z8xKq38EDIGVrutYLmQVVLe+4orDFquU52hQrULnEHinMKv9FSA6lf9+uNT1ITtA==", - "dev": true, - "requires": { - "@types/history": "^4.7.11", - "@types/react": "*" - } - }, - "@types/react-router-dom": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", - "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", - "dev": true, - "requires": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "*" - } - }, - "@types/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", - "requires": { - "@types/react": "*" - } - }, - "@types/react-virtualized-auto-sizer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.1.tgz", - "integrity": "sha512-GH8sAnBEM5GV9LTeiz56r4ZhMOUSrP43tAQNSRVxNexDjcNKLCEtnxusAItg1owFUFE6k0NslV26gqVClVvong==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/react-window": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.5.tgz", - "integrity": "sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/redux-form": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@types/redux-form/-/redux-form-8.3.5.tgz", - "integrity": "sha512-SchB4i7nxgWNbJS4cXEZducztkvHzVrb5xlAXwfLpbrLPo6tMY06+kx1GqMv42+YnGy9TpCAkF51a21HatqWBA==", - "dev": true, - "requires": { - "@types/react": "*", - "redux": "^3.6.0 || ^4.0.0" - } - }, - "@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true - }, - "@types/sizzle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", - "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", - "dev": true - }, - "@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "devOptional": true - }, - "@types/use-sync-external-store": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.41.0.tgz", - "integrity": "sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/type-utils": "5.41.0", - "@typescript-eslint/utils": "5.41.0", - "debug": "^4.3.4", - "ignore": "^5.2.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/parser": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.41.0.tgz", - "integrity": "sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/typescript-estree": "5.41.0", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.41.0.tgz", - "integrity": "sha512-xOxPJCnuktUkY2xoEZBKXO5DBCugFzjrVndKdUnyQr3+9aDWZReKq9MhaoVnbL+maVwWJu/N0SEtrtEUNb62QQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/visitor-keys": "5.41.0" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.41.0.tgz", - "integrity": "sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA==", - "dev": true, - "requires": { - "@typescript-eslint/typescript-estree": "5.41.0", - "@typescript-eslint/utils": "5.41.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/types": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.41.0.tgz", - "integrity": "sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.41.0.tgz", - "integrity": "sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/visitor-keys": "5.41.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/utils": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.41.0.tgz", - "integrity": "sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/typescript-estree": "5.41.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", - "semver": "^7.3.7" - }, - "dependencies": { - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.41.0.tgz", - "integrity": "sha512-vilqeHj267v8uzzakbm13HkPMl7cbYpKVjgFWZPIOHIJHZtinvypUhJ5xBXfWYg4eFKqztbMMpOgFpT9Gfx4fw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.41.0", - "eslint-visitor-keys": "^3.3.0" - } - }, - "@typescript/vfs": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.6.4.tgz", - "integrity": "sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ==", - "dev": true, - "requires": { - "debug": "^4.4.3" - } - }, - "@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true - }, - "@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", - "dev": true, - "requires": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, - "dependencies": { - "react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true - } - } - }, - "@vitest/coverage-v8": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.1.tgz", - "integrity": "sha512-6YeRZwuO4oTGKxD3bijok756oktHSIm3eczVVzNe3scqzuhLwltIF3S9ZL/vwOVIpURmU6SnZhziXXAfw8/Qlw==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.2.1", - "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.4", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.4", - "istanbul-reports": "^3.1.6", - "magic-string": "^0.30.5", - "magicast": "^0.3.3", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "test-exclude": "^6.0.0" - } - }, - "@vitest/expect": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", - "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", - "dev": true, - "requires": { - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "chai": "^4.3.10" - } - }, - "@vitest/runner": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", - "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", - "dev": true, - "requires": { - "@vitest/utils": "1.6.1", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" - }, - "dependencies": { - "p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", - "dev": true, - "requires": { - "yocto-queue": "^1.0.0" - } - }, - "yocto-queue": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", - "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", - "dev": true - } - } - }, - "@vitest/snapshot": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", - "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", - "dev": true, - "requires": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - } - } - }, - "@vitest/spy": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", - "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", - "dev": true, - "requires": { - "tinyspy": "^2.2.0" - } - }, - "@vitest/utils": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", - "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", - "dev": true, - "requires": { - "diff-sequences": "^29.6.3", - "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - } - } - }, - "acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true - }, - "agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true - }, - "ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "aria-query": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.2.tgz", - "integrity": "sha512-JWydkr9MirMg2jGJstDqDgzoHqaFbv7n1ghfXYdtEgXWgdq3jz7IU3SQvtj9k3mAszQBiTpQhFdlH+JIRuGTzg==", - "dev": true, - "requires": { - "deep-equal": "^2.0.5" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true - }, - "babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "requires": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "requires": { - "fill-range": "^7.1.1" - } - }, - "browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "requires": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - } - }, - "cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true - }, - "call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - } - }, - "call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - }, - "caniuse-lite": { - "version": "1.0.30001769", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", - "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", - "dev": true - }, - "chai": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" - }, - "dependencies": { - "type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true - } - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, - "requires": { - "get-func-name": "^2.0.2" - } - }, - "clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true - }, - "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" - }, - "cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } - }, - "cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "crypto-js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" - }, - "css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true - }, - "cssstyle": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", - "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", - "dev": true, - "requires": { - "@asamuzakjp/css-color": "^3.2.0", - "rrweb-cssom": "^0.8.0" - }, - "dependencies": { - "rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "dev": true - } - } - }, - "csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" - }, - "data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", - "dev": true, - "requires": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" - } - }, - "debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "requires": { - "ms": "^2.1.3" - } - }, - "decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "dev": true - }, - "deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "deep-equal": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz", - "integrity": "sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "es-get-iterator": "^1.1.1", - "get-intrinsic": "^1.0.1", - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.2", - "is-regex": "^1.1.1", - "isarray": "^2.0.5", - "object-is": "^1.1.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.3", - "which-boxed-primitive": "^1.0.1", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.2" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - } - }, - "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dev": true, - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true - }, - "dexie": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/dexie/-/dexie-3.2.2.tgz", - "integrity": "sha512-q5dC3HPmir2DERlX+toCBbHQXW5MsyrFqPFcovkH9N2S/UW/H3H5AWAB6iEOExeraAu+j+zRDG+zg/D7YhH0qg==" - }, - "diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-accessibility-api": { - "version": "0.5.14", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", - "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==", - "dev": true - }, - "dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "requires": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "dompurify": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", - "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", - "requires": { - "@types/trusted-types": "^2.0.7" - } - }, - "dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "requires": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - } - }, - "electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", - "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" - } - }, - "es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true - }, - "es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true - }, - "es-get-iterator": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", - "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.0", - "has-symbols": "^1.0.1", - "is-arguments": "^1.1.0", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - } - }, - "es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "requires": { - "es-errors": "^1.3.0" - } - }, - "es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" - }, - "esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "requires": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" - }, - "eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "dependencies": { - "eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - } - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true - }, - "espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "requires": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - } - }, - "esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "requires": { - "@types/estree": "^1.0.0" - } - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "final-form": { - "version": "4.20.7", - "resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.7.tgz", - "integrity": "sha512-ii3X9wNfyBYFnDPunYN5jh1/HAvtOZ9aJI/TVk0MB86hZuOeYkb+W5L3icgwW9WWNztZR6MDU3En6eoZTUoFPg==", - "requires": { - "@babel/runtime": "^7.10.0" - } - }, - "final-form-set-field-touched": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/final-form-set-field-touched/-/final-form-set-field-touched-1.0.1.tgz", - "integrity": "sha512-yvE5AAs9U3OgJQ9YF8NhSF0I0mJEECvOpkaXNqovloxji5Q6gOZ0DCIAyLAKHluGSpsXKUGORyBm8Hq0beZIqQ==" - }, - "find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "requires": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "requires": { - "is-callable": "^1.1.3" - } - }, - "form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - } - }, - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" - }, - "function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - } - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true - }, - "get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "requires": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - } - }, - "get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "requires": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - } - }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true - }, - "gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0" - } - }, - "has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.3" - } - }, - "hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "requires": { - "function-bind": "^1.1.2" - } - }, - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "requires": { - "react-is": "^16.7.0" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", - "dev": true, - "requires": { - "whatwg-encoding": "^3.1.1" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "html-parse-stringify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", - "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", - "requires": { - "void-elements": "3.1.0" - } - }, - "http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "requires": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - } - }, - "https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "requires": { - "agent-base": "^7.1.2", - "debug": "4" - } - }, - "husky": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz", - "integrity": "sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==", - "dev": true - }, - "i18next": { - "version": "22.0.4", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.0.4.tgz", - "integrity": "sha512-TOp7BTMKDbUkOHMzDlVsCYWpyaFkKakrrO3HNXfSz4EeJaWwnBScRmgQSTaWHScXVHBUFXTvShrCW8uryBYFcg==", - "requires": { - "@babel/runtime": "^7.17.2" - } - }, - "i18next-browser-languagedetector": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.0.0.tgz", - "integrity": "sha512-RrH7z5/DbhzhgCLDFIKXBTZlb2aXi38ZHa5e5oZaPt9zGLWmgAX49mzkQL/E7R6Y9fTE8QbZFuyMV0ronu4H/Q==", - "requires": { - "@babel/runtime": "^7.19.4" - } - }, - "i18next-icu": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/i18next-icu/-/i18next-icu-2.0.3.tgz", - "integrity": "sha512-sZ0VCWDnHysUYQL8j/0rVOxv6rLR+SBoaqQQ2UVNfLyJCuf/bAjYPkoUQgyuDkWFo1xZjeCf4G6GBNr7gD61bQ==" - }, - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "intl-messageformat": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.2.1.tgz", - "integrity": "sha512-1lrJG2qKzcC1TVzYu1VuB1yiY68LU5rwpbHa2THCzA67Vutkz7+1lv5U20K3Lz5RAiH78zxNztMEtchokMWv8A==", - "requires": { - "@formatjs/ecma402-abstract": "1.13.0", - "@formatjs/fast-memoize": "1.2.6", - "@formatjs/icu-messageformat-parser": "2.1.10", - "tslib": "2.4.0" - } - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "requires": { - "loose-envify": "^1.0.0" - } - }, - "is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true - }, - "is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "dev": true - }, - "is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "dev": true - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.9.tgz", - "integrity": "sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", - "for-each": "^0.3.3", - "has-tostringtag": "^1.0.0" - } - }, - "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "dev": true - }, - "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true - }, - "istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - } - }, - "istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "jsdom": { - "version": "24.1.3", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.3.tgz", - "integrity": "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==", - "dev": true, - "requires": { - "cssstyle": "^4.0.1", - "data-urls": "^5.0.0", - "decimal.js": "^10.4.3", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.5", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.12", - "parse5": "^7.1.2", - "rrweb-cssom": "^0.7.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.4", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0", - "ws": "^8.18.0", - "xml-name-validator": "^5.0.0" - } - }, - "jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==" - }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "requires": { - "json-buffer": "3.0.1" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "local-pkg": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", - "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", - "dev": true, - "requires": { - "mlly": "^1.7.3", - "pkg-types": "^1.2.1" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "long": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", - "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "dev": true, - "requires": { - "get-func-name": "^2.0.1" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "lz-string": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", - "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", - "dev": true - }, - "magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", - "dev": true, - "requires": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" - } - }, - "make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "requires": { - "semver": "^7.5.3" - }, - "dependencies": { - "semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true - } - } - }, - "math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true - }, - "memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "requires": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - } - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true - }, - "minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "mlly": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", - "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", - "dev": true, - "requires": { - "acorn": "^8.16.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.3" - }, - "dependencies": { - "pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true - } - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true - }, - "nwsapi": { - "version": "2.2.23", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", - "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" - }, - "object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", - "dev": true - }, - "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, - "requires": { - "entities": "^6.0.0" - }, - "dependencies": { - "entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true - } - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" - }, - "pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true - }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, - "picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, - "requires": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - }, - "dependencies": { - "pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true - } - } - }, - "postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, - "requires": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - } - } - }, - "prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "protobufjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", - "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - } - }, - "psl": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "dev": true, - "requires": { - "punycode": "^2.3.1" - } - }, - "punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "requires": { - "loose-envify": "^1.1.0" - } - }, - "react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "requires": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - } - }, - "react-final-form": { - "version": "6.5.9", - "resolved": "https://registry.npmjs.org/react-final-form/-/react-final-form-6.5.9.tgz", - "integrity": "sha512-x3XYvozolECp3nIjly+4QqxdjSSWfcnpGEL5K8OBT6xmGrq5kBqbA6+/tOqoom9NwqIPPbxPNsOViFlbKgowbA==", - "requires": { - "@babel/runtime": "^7.15.4" - } - }, - "react-final-form-listeners": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/react-final-form-listeners/-/react-final-form-listeners-1.0.3.tgz", - "integrity": "sha512-OrdCNxSS4JQS/EXD+R530kZKFqaPfa+WcXPgVro/h4BpaBDF/Ja+BtHyCzDezCIb5rWaGGdOJIj+tN2YdtvrXg==", - "requires": { - "@babel/runtime": "^7.12.5" - } - }, - "react-i18next": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.0.0.tgz", - "integrity": "sha512-/O7N6aIEAl1FaWZBNvhdIo9itvF/MO/nRKr9pYqRc9LhuC1u21SlfwpiYQqvaeNSEW3g3qUXLREOWMt+gxrWbg==", - "requires": { - "@babel/runtime": "^7.14.5", - "html-parse-stringify": "^3.0.1" - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, - "react-redux": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.4.tgz", - "integrity": "sha512-yMfQ7mX6bWuicz2fids6cR1YT59VTuT8MKyyE310wJQlINKENCeT1UcPdEiX6znI5tF8zXyJ/VYvDgeGuaaNwQ==", - "requires": { - "@babel/runtime": "^7.12.1", - "@types/hoist-non-react-statics": "^3.3.1", - "@types/use-sync-external-store": "^0.0.3", - "hoist-non-react-statics": "^3.3.2", - "react-is": "^18.0.0", - "use-sync-external-store": "^1.0.0" - } - }, - "react-router": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.2.tgz", - "integrity": "sha512-Rb0BAX9KHhVzT1OKhMvCDMw776aTYM0DtkxqUBP8dNBom3mPXlfNs76JNGK8wKJ1IZEY1+WGj+cvZxHVk/GiKw==", - "requires": { - "@remix-run/router": "1.0.2" - } - }, - "react-router-dom": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.2.tgz", - "integrity": "sha512-yM1kjoTkpfjgczPrcyWrp+OuQMyB1WleICiiGfstnQYo/S8hPEEnVjr/RdmlH6yKK4Tnj1UGXFSa7uwAtmDoLQ==", - "requires": { - "@remix-run/router": "1.0.2", - "react-router": "6.4.2" - } - }, - "react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "requires": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - } - }, - "react-virtualized-auto-sizer": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.7.tgz", - "integrity": "sha512-Mxi6lwOmjwIjC1X4gABXMJcKHsOo0xWl3E3ugOgufB8GJU+MqrtY35aBuvCYv/razQ1Vbp7h1gWJjGjoNN5pmA==" - }, - "react-window": { - "version": "1.8.7", - "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.7.tgz", - "integrity": "sha512-JHEZbPXBpKMmoNO1bNhoXOOLg/ujhL/BU4IqVU9r8eQPcy5KQnGHIHDRkJ0ns9IM5+Aq5LNwt3j8t3tIrePQzA==", - "requires": { - "@babel/runtime": "^7.0.0", - "memoize-one": ">=3.1.1 <6" - } - }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, - "redux": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", - "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", - "requires": { - "@babel/runtime": "^7.9.2" - } - }, - "redux-form": { - "version": "8.3.8", - "resolved": "https://registry.npmjs.org/redux-form/-/redux-form-8.3.8.tgz", - "integrity": "sha512-PzXhA0d+awIc4PkuhbDa6dCEiraMrGMyyDlYEVNX6qEyW/G2SqZXrjav5zrpXb0CCeqQSc9iqwbMtYQXbJbOAQ==", - "requires": { - "@babel/runtime": "^7.9.2", - "es6-error": "^4.1.1", - "hoist-non-react-statics": "^3.3.2", - "invariant": "^2.2.4", - "is-promise": "^2.1.0", - "lodash": "^4.17.15", - "prop-types": "^15.6.1", - "react-is": "^16.4.2" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "redux-thunk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", - "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==" - }, - "regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rrweb-cssom": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", - "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", - "dev": true - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "rxjs": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", - "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", - "requires": { - "tslib": "^2.1.0" - } - }, - "safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "requires": { - "loose-envify": "^1.1.0" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - }, - "set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "requires": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, - "requires": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - } - }, - "siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" - }, - "source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true - }, - "stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true - }, - "std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true - }, - "string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "strip-literal": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", - "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", - "dev": true, - "requires": { - "js-tokens": "^9.0.1" - }, - "dependencies": { - "js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true - } - } - }, - "stylis": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", - "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true - }, - "tinypool": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", - "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", - "dev": true - }, - "tinyspy": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", - "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "dependencies": { - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true - } - } - }, - "tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", - "dev": true, - "requires": { - "punycode": "^2.3.1" - } - }, - "tsconfck": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", - "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", - "dev": true - }, - "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", - "dev": true - }, - "ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "dev": true - }, - "unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "requires": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==" - }, - "vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, - "requires": { - "esbuild": "^0.21.3", - "fsevents": "~2.3.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "dependencies": { - "rollup": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", - "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", - "dev": true, - "requires": { - "@rollup/rollup-android-arm-eabi": "4.60.1", - "@rollup/rollup-android-arm64": "4.60.1", - "@rollup/rollup-darwin-arm64": "4.60.1", - "@rollup/rollup-darwin-x64": "4.60.1", - "@rollup/rollup-freebsd-arm64": "4.60.1", - "@rollup/rollup-freebsd-x64": "4.60.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", - "@rollup/rollup-linux-arm-musleabihf": "4.60.1", - "@rollup/rollup-linux-arm64-gnu": "4.60.1", - "@rollup/rollup-linux-arm64-musl": "4.60.1", - "@rollup/rollup-linux-loong64-gnu": "4.60.1", - "@rollup/rollup-linux-loong64-musl": "4.60.1", - "@rollup/rollup-linux-ppc64-gnu": "4.60.1", - "@rollup/rollup-linux-ppc64-musl": "4.60.1", - "@rollup/rollup-linux-riscv64-gnu": "4.60.1", - "@rollup/rollup-linux-riscv64-musl": "4.60.1", - "@rollup/rollup-linux-s390x-gnu": "4.60.1", - "@rollup/rollup-linux-x64-gnu": "4.60.1", - "@rollup/rollup-linux-x64-musl": "4.60.1", - "@rollup/rollup-openbsd-x64": "4.60.1", - "@rollup/rollup-openharmony-arm64": "4.60.1", - "@rollup/rollup-win32-arm64-msvc": "4.60.1", - "@rollup/rollup-win32-ia32-msvc": "4.60.1", - "@rollup/rollup-win32-x64-gnu": "4.60.1", - "@rollup/rollup-win32-x64-msvc": "4.60.1", - "@types/estree": "1.0.8", - "fsevents": "~2.3.2" - } - } - } - }, - "vite-node": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", - "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", - "dev": true, - "requires": { - "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "vite": "^5.0.0" - } - }, - "vite-tsconfig-paths": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz", - "integrity": "sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "globrex": "^0.1.2", - "tsconfck": "^3.0.3" - } - }, - "vitest": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", - "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", - "dev": true, - "requires": { - "@vitest/expect": "1.6.1", - "@vitest/runner": "1.6.1", - "@vitest/snapshot": "1.6.1", - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.3", - "vite": "^5.0.0", - "vite-node": "1.6.1", - "why-is-node-running": "^2.2.2" - }, - "dependencies": { - "acorn-walk": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", - "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", - "dev": true, - "requires": { - "acorn": "^8.11.0" - } - }, - "execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - } - }, - "get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true - }, - "human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true - }, - "is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true - }, - "mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true - }, - "npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "requires": { - "path-key": "^4.0.0" - } - }, - "onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "requires": { - "mimic-fn": "^4.0.0" - } - }, - "path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true - }, - "signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true - }, - "strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true - } - } - }, - "void-elements": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", - "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==" - }, - "w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", - "dev": true, - "requires": { - "xml-name-validator": "^5.0.0" - } - }, - "webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true - }, - "whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "dev": true, - "requires": { - "iconv-lite": "0.6.3" - } - }, - "whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "dev": true - }, - "whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "dev": true, - "requires": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dev": true, - "requires": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - } - }, - "which-typed-array": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.8.tgz", - "integrity": "sha512-Jn4e5PItbcAHyLoRDwvPj1ypu27DJbtdYXUa5zsinrUx77Uvfb0cXwwnGMTn7cjUfhhqgVQnVJCwF+7cgU7tpw==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", - "for-each": "^0.3.3", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.9" - } - }, - "why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "requires": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - } - }, - "word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", - "dev": true - }, - "xml-name-validator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } } } diff --git a/webclient/package.json b/webclient/package.json index 527ebfa7c..1259a3aa7 100644 --- a/webclient/package.json +++ b/webclient/package.json @@ -3,17 +3,17 @@ "version": "1.0.0", "private": true, "scripts": { - "prebuild": "node prebuild.js", - "prestart": "node prebuild.js", + "prebuild": "npm run proto:generate && node prebuild.js", + "prestart": "npm run proto:generate && node prebuild.js", "build": "vite build", "start": "vite", "preview": "vite preview", "test": "vitest run", "test:watch": "vitest", - "lint": "eslint src/**/*.{ts,tsx}", - "lint:fix": "eslint src/**/*.{ts,tsx} --fix", + "lint": "eslint src/", + "lint:fix": "eslint src/ --fix", "golden": "npm run lint && npm run test", - "prepare": "cd .. && husky install", + "prepare": "cd .. && husky", "translate": "node prebuild.js -i18nOnly", "proto:generate": "npx buf generate" }, @@ -21,63 +21,61 @@ "@bufbuild/protobuf": "^2.11.0", "@emotion/react": "^11.8.2", "@emotion/styled": "^11.8.1", - "@mui/icons-material": "^5.5.1", - "@mui/material": "^5.5.1", + "@mui/icons-material": "^7.3.10", + "@mui/material": "^7.3.10", + "@reduxjs/toolkit": "^2.11.2", "crypto-js": "^4.2.0", - "dexie": "^3.2.2", + "dexie": "^4.4.2", "dompurify": "^3.3.3", - "final-form": "^4.20.6", + "final-form": "^5.0.0", "final-form-set-field-touched": "^1.0.1", - "i18next": "^22.0.4", - "i18next-browser-languagedetector": "^7.0.0", + "i18next": "^26.0.4", + "i18next-browser-languagedetector": "^8.2.1", "i18next-icu": "^2.0.3", - "intl-messageformat": "^10.2.1", + "intl-messageformat": "^11.2.1", "lodash": "^4.17.21", "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-final-form": "^6.5.8", - "react-final-form-listeners": "^1.0.3", - "react-i18next": "^12.0.0", - "react-redux": "^8.0.4", - "react-router-dom": "^6.2.2", - "react-virtualized-auto-sizer": "^1.0.6", - "react-window": "^1.8.6", - "redux": "^4.1.2", - "redux-form": "^8.3.8", - "redux-thunk": "^2.4.1", + "react-final-form": "^7.0.0", + "react-final-form-listeners": "^3.0.0", + "react-i18next": "^17.0.2", + "react-redux": "^9.2.0", + "react-router-dom": "^7.14.1", + "react-virtualized-auto-sizer": "^2.0.3", + "react-window": "^2.2.7", "rxjs": "^7.5.4" }, "devDependencies": { "@bufbuild/buf": "^1.67.0", "@bufbuild/protoc-gen-es": "^2.11.0", + "@eslint/js": "^10.0.1", "@mui/types": "^7.1.3", + "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.4.0", - "@testing-library/react": "^13.4.0", + "@testing-library/react": "^16.3.2", "@types/dompurify": "^3.0.5", - "@types/jquery": "^3.5.14", "@types/lodash": "^4.14.179", - "@types/node": "18.11.7", + "@types/node": "^22.19.17", "@types/prop-types": "^15.7.4", "@types/react": "18.0.24", "@types/react-dom": "18.0.8", - "@types/react-redux": "^7.1.23", - "@types/react-router-dom": "^5.3.3", "@types/react-virtualized-auto-sizer": "^1.0.1", "@types/react-window": "^1.8.5", - "@types/redux-form": "^8.3.3", - "@typescript-eslint/eslint-plugin": "^5.14.0", - "@typescript-eslint/parser": "^5.14.0", - "@vitejs/plugin-react": "^4.2.0", - "@vitest/coverage-v8": "^1.3.0", - "eslint": "^8.0.0", - "fs-extra": "^10.0.1", - "husky": "^8.0.1", - "jsdom": "^24.0.0", - "typescript": "^4.6.2", - "vite": "^5.1.0", - "vite-tsconfig-paths": "^4.3.1", - "vitest": "^1.3.0" + "@typescript-eslint/eslint-plugin": "^8.58.2", + "@typescript-eslint/parser": "^8.58.2", + "@vitejs/plugin-react": "^5.2.0", + "@vitest/coverage-v8": "^4.1.4", + "eslint": "^10.2.0", + "fs-extra": "^11.3.4", + "globals": "^17.5.0", + "husky": "^9.1.7", + "jsdom": "^29.0.2", + "typescript": "~5.8", + "typescript-eslint": "^8.58.2", + "vite": "^6.4.2", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^4.1.4" }, "browserslist": { "production": [ diff --git a/webclient/src/components/Guard/AuthGuard.tsx b/webclient/src/components/Guard/AuthGuard.tsx index eea42dc7b..3b0790a62 100644 --- a/webclient/src/components/Guard/AuthGuard.tsx +++ b/webclient/src/components/Guard/AuthGuard.tsx @@ -1,24 +1,16 @@ import React from 'react'; -import { connect } from 'react-redux'; import { Navigate } from 'react-router-dom'; import { ServerSelectors } from 'store'; import { RouteEnum } from 'types'; - +import { useAppSelector } from 'store/store'; import { AuthenticationService } from 'api'; -const AuthGuard = ({ state }: AuthGuardProps) => { +const AuthGuard = () => { + const state = useAppSelector(s => ServerSelectors.getState(s)); return !AuthenticationService.isConnected(state) ? :
; }; -interface AuthGuardProps { - state: number; -} - -const mapStateToProps = state => ({ - state: ServerSelectors.getState(state), -}); - -export default connect(mapStateToProps)(AuthGuard); +export default AuthGuard; diff --git a/webclient/src/components/Guard/ModGuard.tsx b/webclient/src/components/Guard/ModGuard.tsx index c8bc6d663..f8f629898 100644 --- a/webclient/src/components/Guard/ModGuard.tsx +++ b/webclient/src/components/Guard/ModGuard.tsx @@ -1,27 +1,16 @@ -import React, { Component } from 'react'; -import { connect } from 'react-redux'; +import React from 'react'; import { Navigate } from 'react-router-dom'; import { ServerSelectors } from 'store'; -import { User } from 'types'; - import { AuthenticationService } from 'api'; import { RouteEnum } from 'types'; +import { useAppSelector } from 'store/store'; -class ModGuard extends Component { - render() { - return !AuthenticationService.isModerator(this.props.user) - ? - : ''; - } +const ModGuard = () => { + const user = useAppSelector(state => ServerSelectors.getUser(state)); + return !AuthenticationService.isModerator(user) + ? + : <>; }; -interface ModGuardProps { - user: User; -} - -const mapStateToProps = state => ({ - user: ServerSelectors.getUser(state), -}); - -export default connect(mapStateToProps)(ModGuard); +export default ModGuard; diff --git a/webclient/src/components/InputAction/InputAction.tsx b/webclient/src/components/InputAction/InputAction.tsx index 459b89904..6c5e61474 100644 --- a/webclient/src/components/InputAction/InputAction.tsx +++ b/webclient/src/components/InputAction/InputAction.tsx @@ -6,7 +6,7 @@ import { InputField } from 'components'; import './InputAction.css'; -const InputAction = ({ action, label, name, validate, disabled }) => ( +const InputAction = ({ action, label, name, validate = () => false, disabled = false }) => (
@@ -19,9 +19,4 @@ const InputAction = ({ action, label, name, validate, disabled }) => (
); -InputAction.defaultProps = { - disabled: false, - validate: () => false, -} - export default InputAction; diff --git a/webclient/src/components/KnownHosts/KnownHosts.tsx b/webclient/src/components/KnownHosts/KnownHosts.tsx index f65a98c38..cb19d9f56 100644 --- a/webclient/src/components/KnownHosts/KnownHosts.tsx +++ b/webclient/src/components/KnownHosts/KnownHosts.tsx @@ -99,7 +99,7 @@ const KnownHosts = (props) => { }, [loadKnownHosts]); useEffect(() => { - const { hosts, selectedHost } = hostsState; + const { selectedHost } = hostsState; if (selectedHost?.id) { updateLastSelectedHost(selectedHost.id).then(() => { @@ -255,7 +255,7 @@ const KnownHosts = (props) => {
{ host.editable && ( - { + { openEditKnownHostDialog(hostsState.hosts[index]); }}> diff --git a/webclient/src/components/LanguageDropdown/LanguageDropdown.tsx b/webclient/src/components/LanguageDropdown/LanguageDropdown.tsx index 8f63bd549..0cd0ccbe1 100644 --- a/webclient/src/components/LanguageDropdown/LanguageDropdown.tsx +++ b/webclient/src/components/LanguageDropdown/LanguageDropdown.tsx @@ -1,9 +1,8 @@ -// eslint-disable-next-line + import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Select, MenuItem } from '@mui/material'; import FormControl from '@mui/material/FormControl'; -import InputLabel from '@mui/material/InputLabel'; import { Images } from 'images/Images'; import { Language, LanguageCountry, LanguageNative } from 'types'; diff --git a/webclient/src/components/Message/CardCallout.tsx b/webclient/src/components/Message/CardCallout.tsx index d34316541..9db660a30 100644 --- a/webclient/src/components/Message/CardCallout.tsx +++ b/webclient/src/components/Message/CardCallout.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line + import React, { useMemo, useState } from 'react'; import { styled } from '@mui/material/styles'; import Popover from '@mui/material/Popover'; @@ -17,7 +17,7 @@ const classes = { popoverContent: `${PREFIX}-popoverContent` }; -const Root = styled('span')(({ theme }) => ({ +const Root = styled('span')(() => ({ [`& .${classes.popover}`]: { pointerEvents: 'none', }, diff --git a/webclient/src/components/Message/Message.tsx b/webclient/src/components/Message/Message.tsx index ec0b05507..b7d433d18 100644 --- a/webclient/src/components/Message/Message.tsx +++ b/webclient/src/components/Message/Message.tsx @@ -15,7 +15,7 @@ import { import CardCallout from './CardCallout'; import './Message.css'; -const Message = ({ message: { message, messageType, timeOf, timeReceived } }) => ( +const Message = ({ message: { message } }) => (
diff --git a/webclient/src/components/ThreePaneLayout/ThreePaneLayout.tsx b/webclient/src/components/ThreePaneLayout/ThreePaneLayout.tsx index 7919f9d35..2b202238c 100644 --- a/webclient/src/components/ThreePaneLayout/ThreePaneLayout.tsx +++ b/webclient/src/components/ThreePaneLayout/ThreePaneLayout.tsx @@ -1,7 +1,5 @@ import { Component, CElement } from 'react'; -import { connect } from 'react-redux'; import Grid from '@mui/material/Grid'; -import Hidden from '@mui/material/Hidden'; import './ThreePaneLayout.css'; @@ -12,25 +10,23 @@ class ThreePaneLayout extends Component { return (
- - + {this.props.top} - {this.props.bottom} - - - {this.props.side} - - + + {this.props.side} +
); @@ -44,6 +40,4 @@ interface ThreePaneLayoutProps { fixedHeight?: boolean, } -const mapStateToProps = state => ({}); - -export default connect(mapStateToProps)(ThreePaneLayout); +export default ThreePaneLayout; diff --git a/webclient/src/components/Toast/Toast.tsx b/webclient/src/components/Toast/Toast.tsx index 4ef8a3cad..ac7c029e7 100644 --- a/webclient/src/components/Toast/Toast.tsx +++ b/webclient/src/components/Toast/Toast.tsx @@ -1,9 +1,9 @@ import * as React from 'react' import ReactDOM from 'react-dom' -import Alert, { AlertProps } from '@mui/material/Alert'; +import Alert from '@mui/material/Alert'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; -import Slide, { SlideProps } from '@mui/material/Slide'; +import Slide from '@mui/material/Slide'; import Snackbar from '@mui/material/Snackbar'; const iconMapping = { @@ -11,7 +11,7 @@ const iconMapping = { } function Toast(props) { - const { open, onClose, severity, autoHideDuration, children } = props + const { open, onClose, severity = 'success', autoHideDuration = 10000, children } = props const rootElemRef = React.useRef(document.createElement('div')); @@ -37,9 +37,9 @@ function Toast(props) { TransitionComponent={TransitionLeft} anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }} > - - {children} - + ) if (!rootElemRef.current) { @@ -52,12 +52,6 @@ function Toast(props) { ); } -Toast.defaultProps = { - severity: 'success', - // 10s wait before automatically dismissing the Toast. - autoHideDuration: 10000, -} - function TransitionLeft(props) { return ; } diff --git a/webclient/src/components/Toast/ToastContext.tsx b/webclient/src/components/Toast/ToastContext.tsx index 44753d87f..0b92fdde3 100644 --- a/webclient/src/components/Toast/ToastContext.tsx +++ b/webclient/src/components/Toast/ToastContext.tsx @@ -1,4 +1,4 @@ -import { createContext, FC, PropsWithChildren, ReactChild, ReactNode, useContext, useEffect, useReducer, ContextType, Context } from 'react' +import { createContext, FC, PropsWithChildren, ReactChild, ReactNode, useContext, useEffect, useReducer, Context } from 'react' import { ACTIONS, initialState, reducer } from './reducer'; import Toast from './Toast' @@ -18,10 +18,10 @@ interface ToastState { const ToastContext: Context = createContext({ toasts: new Map(), - addToast: (key, children) => {}, - openToast: (key) => {}, - closeToast: (key) => {}, - removeToast: (key) => {}, + addToast: (_key, _children) => {}, + openToast: (_key) => {}, + closeToast: (_key) => {}, + removeToast: (_key) => {}, }); export const ToastProvider: FC = (props) => { @@ -56,7 +56,7 @@ export interface ToastHookOptions { children: ReactNode } -export function useToast({ key, children }) { +export function useToast({ key, children }) { const { addToast, openToast, closeToast, removeToast } = useContext(ToastContext) useEffect(() => { diff --git a/webclient/src/components/UserDisplay/UserDisplay.tsx b/webclient/src/components/UserDisplay/UserDisplay.tsx index 9c45a0f51..b2ea7ab41 100644 --- a/webclient/src/components/UserDisplay/UserDisplay.tsx +++ b/webclient/src/components/UserDisplay/UserDisplay.tsx @@ -1,6 +1,5 @@ -// eslint-disable-next-line -import React, { Component } from "react"; -import { connect } from 'react-redux'; + +import React, { useState } from 'react'; import { NavLink, generatePath } from 'react-router-dom'; import Menu from '@mui/material/Menu'; @@ -10,141 +9,85 @@ import { Images } from 'images/Images'; import { SessionService } from 'api'; import { ServerSelectors } from 'store'; import { RouteEnum, User } from 'types'; +import { useAppSelector } from 'store/store'; import './UserDisplay.css'; -class UserDisplay extends Component { - constructor(props) { - super(props); +const UserDisplay = ({ user }: UserDisplayProps) => { + const buddyList = useAppSelector(state => ServerSelectors.getBuddyList(state)); + const ignoreList = useAppSelector(state => ServerSelectors.getIgnoreList(state)); + const [position, setPosition] = useState<{ x: number; y: number } | null>(null); - this.handleClick = this.handleClick.bind(this); - this.handleClose = this.handleClose.bind(this); - this.navigateToUserProfile = this.navigateToUserProfile.bind(this); - this.addToBuddyList = this.addToBuddyList.bind(this); - this.removeFromBuddyList = this.removeFromBuddyList.bind(this); - this.addToIgnoreList = this.addToIgnoreList.bind(this); - this.removeFromIgnoreList = this.removeFromIgnoreList.bind(this); + const { name, country } = user; - this.isABuddy = this.isABuddy.bind(this); - this.isIgnored = this.isIgnored.bind(this); - - this.state = { - position: null - }; - } - - handleClick(event) { + const handleClick = (event) => { event.preventDefault(); + setPosition({ x: event.clientX + 2, y: event.clientY + 4 }); + }; - this.setState({ - position: { - x: event.clientX + 2, - y: event.clientY + 4, - } - }); - } + const handleClose = () => setPosition(null); - handleClose() { - this.setState({ - position: null - }); - } + const isABuddy = buddyList.filter(u => u.name === user.name).length; + const isIgnored = ignoreList.filter(u => u.name === user.name).length; - navigateToUserProfile() { - this.handleClose(); - } + const onAddBuddy = () => { + SessionService.addToBuddyList(user.name); + handleClose(); + }; + const onRemoveBuddy = () => { + SessionService.removeFromBuddyList(user.name); + handleClose(); + }; + const onAddIgnore = () => { + SessionService.addToIgnoreList(user.name); + handleClose(); + }; + const onRemoveIgnore = () => { + SessionService.removeFromIgnoreList(user.name); + handleClose(); + }; - addToBuddyList() { - SessionService.addToBuddyList(this.props.user.name); - this.handleClose(); - } - - removeFromBuddyList() { - SessionService.removeFromBuddyList(this.props.user.name); - this.handleClose(); - } - - addToIgnoreList() { - SessionService.addToIgnoreList(this.props.user.name); - this.handleClose(); - } - - removeFromIgnoreList() { - SessionService.removeFromIgnoreList(this.props.user.name); - this.handleClose(); - } - - isABuddy() { - return this.props.buddyList.filter(user => user.name === this.props.user.name).length; - } - - isIgnored() { - return this.props.ignoreList.filter(user => user.name === this.props.user.name).length; - } - - render() { - const { user } = this.props; - const { position } = this.state; - const { name, country } = user; - - const isABuddy = this.isABuddy(); - const isIgnored = this.isIgnored(); - - // console.log('user', name, !!isABuddy, !!isIgnored); - - return ( -
- -
- {country} -
{name}
-
-
-
- - - Chat - - { - !isABuddy - ? (Add to Buddy List) - : (Remove From Buddy List) - } - { - !isIgnored - ? (Add to Ignore List) - : (Remove From Ignore List) - } - + return ( +
+ +
+ {country} +
{name}
+
+
+ + + Chat + + { + !isABuddy + ? (Add to Buddy List) + : (Remove From Buddy List) + } + { + !isIgnored + ? (Add to Ignore List) + : (Remove From Ignore List) + } +
- ); - } -} +
+ ); +}; interface UserDisplayProps { user: User; - buddyList: User[]; - ignoreList: User[]; } -interface UserDisplayState { - position: any; -} - -const mapStateToProps = (state) => ({ - buddyList: ServerSelectors.getBuddyList(state), - ignoreList: ServerSelectors.getIgnoreList(state) -}); - -export default connect(mapStateToProps)(UserDisplay); +export default UserDisplay; diff --git a/webclient/src/components/VirtualList/VirtualList.tsx b/webclient/src/components/VirtualList/VirtualList.tsx index e40ef712c..fc2868f2d 100644 --- a/webclient/src/components/VirtualList/VirtualList.tsx +++ b/webclient/src/components/VirtualList/VirtualList.tsx @@ -1,34 +1,29 @@ // eslint-disable-next-line import React from "react"; -import { FixedSizeList as List } from 'react-window'; -import AutoSizer from 'react-virtualized-auto-sizer'; +import { List, RowComponentProps } from 'react-window'; import './VirtualList.css'; -const VirtualList = ({ items, itemKey, className = {}, size = 30 }) => ( -
- - {({ height, width }) => ( - - {Row} - - )} - +interface RowData { + items: any[]; +} + +const Row = ({ index, style, items }: RowComponentProps) => ( +
+ {items[index]}
); -const Row = ({ data, index, style }) => ( -
- {data[index]} +const VirtualList = ({ items, className = {}, size = 30 }) => ( +
+ + className={`virtual-list__list ${className}`} + rowCount={items.length} + rowHeight={size} + rowComponent={Row} + rowProps={{ items }} + />
); diff --git a/webclient/src/containers/Account/Account.tsx b/webclient/src/containers/Account/Account.tsx index 0c4e001a0..e25f59b72 100644 --- a/webclient/src/containers/Account/Account.tsx +++ b/webclient/src/containers/Account/Account.tsx @@ -1,27 +1,30 @@ // eslint-disable-next-line import React, { Component } from "react"; import { useTranslation } from 'react-i18next'; -import { connect } from 'react-redux'; import Button from '@mui/material/Button'; -import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; import Paper from '@mui/material/Paper'; import { UserDisplay, VirtualList, AuthGuard, LanguageDropdown } from 'components'; import { AuthenticationService, SessionService } from 'api'; import { ServerSelectors } from 'store'; -import { User } from 'types'; import Layout from 'containers/Layout/Layout'; +import { useAppSelector } from 'store/store'; import AddToBuddies from './AddToBuddies'; import AddToIgnore from './AddToIgnore'; import './Account.css'; -const Account = (props: AccountProps) => { - const { buddyList, ignoreList, serverName, serverVersion, user } = props; +const Account = () => { + const buddyList = useAppSelector(state => ServerSelectors.getBuddyList(state)); + const ignoreList = useAppSelector(state => ServerSelectors.getIgnoreList(state)); + const serverName = useAppSelector(state => ServerSelectors.getName(state)); + const serverVersion = useAppSelector(state => ServerSelectors.getVersion(state)); + const user = useAppSelector(state => ServerSelectors.getUser(state)); const { country, realName, name, userLevel, accountageSecs, avatarBmp } = user || {}; - let url = URL.createObjectURL(new Blob([avatarBmp], { 'type': 'image/png' })); + let url = URL.createObjectURL(new Blob([avatarBmp as BlobPart], { 'type': 'image/png' })); const { t } = useTranslation(); @@ -42,11 +45,10 @@ const Account = (props: AccountProps) => { Buddies Online: ?/{buddyList.length}
buddyList[index].name } items={ buddyList.map(user => ( - + - + )) } />
@@ -60,11 +62,10 @@ const Account = (props: AccountProps) => { Ignored Users Online: ?/{ignoreList.length}
ignoreList[index].name } items={ ignoreList.map(user => ( - + - + )) } />
@@ -78,7 +79,7 @@ const Account = (props: AccountProps) => {

{name}

Location: ({country?.toUpperCase()})

User Level: {userLevel}

-

Account Age: {accountageSecs}

+

Account Age: {String(accountageSecs)}

Real Name: {realName}

@@ -101,20 +102,4 @@ const Account = (props: AccountProps) => { ) } -interface AccountProps { - buddyList: User[]; - ignoreList: User[]; - serverName: string; - serverVersion: string; - user: User; -} - -const mapStateToProps = state => ({ - buddyList: ServerSelectors.getBuddyList(state), - ignoreList: ServerSelectors.getIgnoreList(state), - serverName: ServerSelectors.getName(state), - serverVersion: ServerSelectors.getVersion(state), - user: ServerSelectors.getUser(state), -}); - -export default connect(mapStateToProps)(Account); +export default Account; diff --git a/webclient/src/containers/App/AppShellRoutes.tsx b/webclient/src/containers/App/AppShellRoutes.tsx index c80f74c30..b07961eef 100644 --- a/webclient/src/containers/App/AppShellRoutes.tsx +++ b/webclient/src/containers/App/AppShellRoutes.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Navigate, Route, Routes } from 'react-router-dom'; +import { Route, Routes } from 'react-router-dom'; import { RouteEnum } from 'types'; import { diff --git a/webclient/src/containers/App/FeatureDetection.tsx b/webclient/src/containers/App/FeatureDetection.tsx index ed9e1f241..d7ef75aeb 100644 --- a/webclient/src/containers/App/FeatureDetection.tsx +++ b/webclient/src/containers/App/FeatureDetection.tsx @@ -11,7 +11,7 @@ const FeatureDetection = () => { detectIndexedDB(), ]; - Promise.all(features).catch((e) => setUnsupported(true)); + Promise.all(features).catch(() => setUnsupported(true)); }, []); return unsupported diff --git a/webclient/src/containers/Initialize/Initialize.tsx b/webclient/src/containers/Initialize/Initialize.tsx index bc777f065..92f8223e7 100644 --- a/webclient/src/containers/Initialize/Initialize.tsx +++ b/webclient/src/containers/Initialize/Initialize.tsx @@ -1,7 +1,5 @@ -import { useState } from 'react'; import { styled } from '@mui/material/styles'; import { useTranslation, Trans } from 'react-i18next'; -import { connect } from 'react-redux'; import { Navigate } from 'react-router-dom'; import Typography from '@mui/material/Typography'; @@ -9,6 +7,7 @@ import { Images } from 'images'; import { ServerSelectors } from 'store'; import { RouteEnum } from 'types'; import Layout from 'containers/Layout/Layout'; +import { useAppSelector } from 'store/store'; import './Initialize.css'; @@ -30,7 +29,8 @@ const Root = styled('div')(({ theme }) => ({ } })); -const Initialize = ({ initialized }: InitializeProps) => { +const Initialize = () => { + const initialized = useAppSelector(state => ServerSelectors.getInitialized(state)); const { t } = useTranslation(); return initialized @@ -60,12 +60,4 @@ const Initialize = ({ initialized }: InitializeProps) => { ); } -interface InitializeProps { - initialized: boolean; -} - -const mapStateToProps = state => ({ - initialized: ServerSelectors.getInitialized(state), -}); - -export default connect(mapStateToProps)(Initialize); +export default Initialize; diff --git a/webclient/src/containers/Layout/Layout.tsx b/webclient/src/containers/Layout/Layout.tsx index 6d04172ba..858011b42 100644 --- a/webclient/src/containers/Layout/Layout.tsx +++ b/webclient/src/containers/Layout/Layout.tsx @@ -22,7 +22,7 @@ function Layout(props:LayoutProps) { ) } -function BottomBar(props) { +function BottomBar() { return (
diff --git a/webclient/src/containers/Layout/LeftNav.tsx b/webclient/src/containers/Layout/LeftNav.tsx index a81eb6ce2..196087d7a 100644 --- a/webclient/src/containers/Layout/LeftNav.tsx +++ b/webclient/src/containers/Layout/LeftNav.tsx @@ -1,5 +1,4 @@ import React, { useState, useEffect } from 'react'; -import { connect } from 'react-redux'; import { NavLink, useNavigate, generatePath } from 'react-router-dom'; import IconButton from '@mui/material/IconButton'; import Menu from '@mui/material/Menu'; @@ -8,17 +7,26 @@ import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import CloseIcon from '@mui/icons-material/Close'; import MailOutlineRoundedIcon from '@mui/icons-material/MailOutline'; import MenuRoundedIcon from '@mui/icons-material/MenuRounded'; -import * as _ from 'lodash'; import { AuthenticationService, RoomsService } from 'api'; import { CardImportDialog } from 'dialogs'; import { Images } from 'images'; import { RoomsSelectors, ServerSelectors } from 'store'; -import { Room, RouteEnum, User } from 'types'; +import { RouteEnum } from 'types'; +import { useAppSelector } from 'store/store'; import './LeftNav.css'; -const LeftNav = ({ joinedRooms, serverState, user }: LeftNavProps) => { +interface LeftNavState { + anchorEl: Element; + showCardImportDialog: boolean; + options: string[]; +} + +const LeftNav = () => { + const joinedRooms = useAppSelector(state => RoomsSelectors.getJoinedRooms(state)); + const serverState = useAppSelector(state => ServerSelectors.getState(state)); + const user = useAppSelector(state => ServerSelectors.getUser(state)); const navigate = useNavigate(); const [state, setState] = useState({ anchorEl: null, @@ -147,12 +155,12 @@ const LeftNav = ({ joinedRooms, serverState, user }: LeftNavProps) => { }} > {state.options.map((option) => ( - handleMenuItemClick(option)}> + handleMenuItemClick(option)}> {option} ))} - openImportCardWizard()}> + openImportCardWizard()}> Import Cards @@ -171,25 +179,4 @@ const LeftNav = ({ joinedRooms, serverState, user }: LeftNavProps) => { ); } -interface LeftNavProps { - serverState: number; - server: string; - user: User; - joinedRooms: Room[]; - showNav?: boolean; -} - -interface LeftNavState { - anchorEl: Element; - showCardImportDialog: boolean; - options: string[]; -} - -const mapStateToProps = state => ({ - serverState: ServerSelectors.getState(state), - server: ServerSelectors.getName(state), - user: ServerSelectors.getUser(state), - joinedRooms: RoomsSelectors.getJoinedRooms(state), -}); - -export default connect(mapStateToProps)(LeftNav); +export default LeftNav; diff --git a/webclient/src/containers/Login/Login.tsx b/webclient/src/containers/Login/Login.tsx index 965078759..d15332564 100644 --- a/webclient/src/containers/Login/Login.tsx +++ b/webclient/src/containers/Login/Login.tsx @@ -1,7 +1,6 @@ import { useState, useCallback } from 'react'; import { styled } from '@mui/material/styles'; import { useTranslation } from 'react-i18next'; -import { connect } from 'react-redux'; import { Navigate } from 'react-router-dom'; import Button from '@mui/material/Button'; import Paper from '@mui/material/Paper'; @@ -17,6 +16,7 @@ import { HostDTO, serverProps } from 'services'; import { RouteEnum, WebSocketConnectOptions, getHostPort } from 'types'; import { ServerSelectors, ServerTypes } from 'store'; import Layout from 'containers/Layout/Layout'; +import { useAppSelector } from 'store/store'; import './Login.css'; import { useToast } from 'components/Toast'; @@ -64,7 +64,9 @@ const Root = styled('div')(({ theme }) => ({ } })); -const Login = ({ state, description }: LoginProps) => { +const Login = () => { + const state = useAppSelector(s => ServerSelectors.getState(s)); + const description = useAppSelector(s => ServerSelectors.getDescription(s)); const { t } = useTranslation(); const isConnected = AuthenticationService.isConnected(state); @@ -349,14 +351,4 @@ const Login = ({ state, description }: LoginProps) => { ); } -interface LoginProps { - state: number; - description: string; -} - -const mapStateToProps = state => ({ - state: ServerSelectors.getState(state), - description: ServerSelectors.getDescription(state), -}); - -export default connect(mapStateToProps)(Login); +export default Login; diff --git a/webclient/src/containers/Logs/Logs.tsx b/webclient/src/containers/Logs/Logs.tsx index a34a7b008..37fa4d27a 100644 --- a/webclient/src/containers/Logs/Logs.tsx +++ b/webclient/src/containers/Logs/Logs.tsx @@ -1,33 +1,50 @@ // eslint-disable-next-line -import React, { Component } from "react"; -import { connect } from 'react-redux'; +import React, { useEffect } from "react"; import * as _ from 'lodash'; import { ModeratorService } from 'api'; import { AuthGuard, ModGuard } from 'components'; import { SearchForm } from 'forms'; -import { ServerDispatch, ServerSelectors, ServerStateLogs } from 'store'; +import { ServerDispatch, ServerSelectors } from 'store'; import { LogFilters } from 'types'; +import { useAppSelector } from 'store/store'; import LogResults from './LogResults'; import './Logs.css'; -class Logs extends Component { - MAXIMUM_RESULTS = 1000; +const Logs = () => { + const logs = useAppSelector(state => ServerSelectors.getLogs(state)); + const MAXIMUM_RESULTS = 1000; - constructor(props) { - super(props); + useEffect(() => { + return () => { + ServerDispatch.clearLogs(); + }; + }, []); - this.onSubmit = this.onSubmit.bind(this); - } + const trimFields = (fields) => { + return _.reduce(fields, (obj: any, field, key) => { + if (typeof field === 'string') { + const trimmed = _.trim(field); + if (!!trimmed) { + obj[key] = trimmed; + } + } else { + obj[key] = field; + } + return obj; + }, {}); + }; - componentWillUnmount() { - ServerDispatch.clearLogs(); - } - - onSubmit(fields: LogFilters) { - const trimmedFields: any = this.trimFields(fields); + const flattenLogLocations = (logLocations) => { + return _.reduce(logLocations, (arr: any[], loc, key) => { + arr.push(key); + return arr; + }, []); + }; + const onSubmit = (fields: LogFilters) => { + const trimmedFields: any = trimFields(fields); const { userName, ipAddress, gameName, gameId, message, logLocation } = trimmedFields; const required = _.filter({ @@ -35,68 +52,35 @@ class Logs extends Component { }, field => field); if (logLocation) { - trimmedFields.logLocation = this.flattenLogLocations(logLocation); + trimmedFields.logLocation = flattenLogLocations(logLocation); } - trimmedFields.maximumResults = this.MAXIMUM_RESULTS; + trimmedFields.maximumResults = MAXIMUM_RESULTS; if (_.size(required)) { ModeratorService.viewLogHistory(trimmedFields); } else { // @TODO use yet-to-be-implemented banner/alert } - } + }; - private trimFields(fields) { - return _.reduce(fields, (obj, field, key) => { - if (typeof field === 'string') { - const trimmed = _.trim(field); + return ( +
+ + - if (!!trimmed) { - obj[key] = trimmed; - } - } else { - obj[key] = field; - } - - return obj; - }, {}); - } - - private flattenLogLocations(logLocations) { - return _.reduce(logLocations, (arr, loc, key) => { - arr.push(key); - return arr; - }, []) - } - - render() { - return ( -
- - - -
- -
- -
- -
+
+
- ) - } -} -interface LogsTypes { - logs: ServerStateLogs -} +
+ +
+
+ ); +}; -const mapStateToProps = state => ({ - logs: ServerSelectors.getLogs(state) -}); - -export default connect(mapStateToProps)(Logs); +export default Logs; diff --git a/webclient/src/containers/Room/Games.tsx b/webclient/src/containers/Room/Games.tsx index 67a9239d3..f2489a29f 100644 --- a/webclient/src/containers/Room/Games.tsx +++ b/webclient/src/containers/Room/Games.tsx @@ -1,6 +1,5 @@ // eslint-disable-next-line -import React, { Component } from "react"; -import { connect } from 'react-redux'; +import React from "react"; import * as _ from 'lodash'; import Table from '@mui/material/Table'; @@ -15,129 +14,97 @@ import Tooltip from '@mui/material/Tooltip'; import { SortUtil, RoomsDispatch, RoomsSelectors } from 'store'; import { UserDisplay } from 'components'; +import { useAppSelector } from 'store/store'; import './Games.css'; // @TODO run interval to update timeSinceCreated -class Games extends Component { - private headerCells = [ - { - label: 'Age', - field: 'startTime' - }, - { - label: 'Description', - field: 'description' - }, - { - label: 'Creator', - field: 'creatorInfo.name' - }, - { - label: 'Type', - field: 'gameType' - }, - { - label: 'Restrictions', - // field: "?" - }, - { - label: 'Players', - // field: ["maxPlayers", "playerCount"] - }, - { - label: 'Spectators', - field: 'spectatorsCount' - }, - ]; - - handleSort(sortByField) { - const { room: { roomId }, sortBy } = this.props; - const { field, order } = SortUtil.toggleSortBy(sortByField, sortBy); - RoomsDispatch.sortGames(roomId, field, order); - } - - private isUnavailableGame({ started, maxPlayers, playerCount }) { - return !started && playerCount < maxPlayers; - } - - private isPasswordProtectedGame({ withPassword }) { - return !withPassword; - } - - private isBuddiesOnlyGame({ onlyBuddies }) { - return !onlyBuddies; - } - - render() { - const { room, sortBy } = this.props; - - const games = room.gameList.filter(game => ( - this.isUnavailableGame(game) && - this.isPasswordProtectedGame(game) && - this.isBuddiesOnlyGame(game) - )); - - return ( -
- - - - { _.map(this.headerCells, ({ label, field }) => { - const active = field === sortBy.field; - const order = sortBy.order.toLowerCase(); - const sortDirection = active ? order : false; - - return ( - - {!field ? label : ( - this.handleSort(field)} - > - {label} - - )} - - ); - })} - - - - { _.map(games, ({ description, gameId, gameType, creatorInfo, maxPlayers, playerCount, spectatorsCount, startTime }) => ( - - {startTime} - - -
- {description} -
-
-
- - - - {gameType} - ? - {`${playerCount}/${maxPlayers}`} - {spectatorsCount} -
- ))} -
-
-
- ); - } -} - interface GamesProps { room: any; - sortBy: any; } -const mapStateToProps = state => ({ - sortBy: RoomsSelectors.getSortGamesBy(state) -}); +const Games = ({ room }: GamesProps) => { + const sortBy = useAppSelector(state => RoomsSelectors.getSortGamesBy(state)); -export default connect(mapStateToProps)(Games); + const headerCells = [ + { label: 'Age', field: 'startTime' }, + { label: 'Description', field: 'description' }, + { label: 'Creator', field: 'creatorInfo.name' }, + { label: 'Type', field: 'gameType' }, + { label: 'Restrictions' }, + { label: 'Players' }, + { label: 'Spectators', field: 'spectatorsCount' }, + ]; + + const handleSort = (sortByField) => { + const { roomId } = room; + const { field, order } = SortUtil.toggleSortBy(sortByField, sortBy); + RoomsDispatch.sortGames(roomId, field, order); + }; + + const isUnavailableGame = ({ started, maxPlayers, playerCount }) => + !started && playerCount < maxPlayers; + + const isPasswordProtectedGame = ({ withPassword }) => !withPassword; + + const isBuddiesOnlyGame = ({ onlyBuddies }) => !onlyBuddies; + + const games = room.gameList.filter(game => ( + isUnavailableGame(game) && + isPasswordProtectedGame(game) && + isBuddiesOnlyGame(game) + )); + + return ( +
+ + + + { _.map(headerCells, ({ label, field }) => { + const active = field === sortBy.field; + const order = sortBy.order.toLowerCase(); + const sortDirection = active ? (order === 'asc' ? 'asc' : 'desc') : false; + + return ( + + {!field ? label : ( + handleSort(field)} + > + {label} + + )} + + ); + })} + + + + { _.map(games, ({ description, gameId, gameType, creatorInfo, maxPlayers, playerCount, spectatorsCount, startTime }) => ( + + {startTime} + + +
+ {description} +
+
+
+ + + + {gameType} + ? + {`${playerCount}/${maxPlayers}`} + {spectatorsCount} +
+ ))} +
+
+
+ ); +}; + +export default Games; diff --git a/webclient/src/containers/Room/Messages.tsx b/webclient/src/containers/Room/Messages.tsx index 24b576759..81aa671b8 100644 --- a/webclient/src/containers/Room/Messages.tsx +++ b/webclient/src/containers/Room/Messages.tsx @@ -8,7 +8,7 @@ import './Messages.css'; const Messages = ({ messages }) => (
{ - messages && messages.map((message, index) => ( + messages && messages.map((message) => (
diff --git a/webclient/src/containers/Room/OpenGames.tsx b/webclient/src/containers/Room/OpenGames.tsx index 49d4d2503..79c2a855d 100644 --- a/webclient/src/containers/Room/OpenGames.tsx +++ b/webclient/src/containers/Room/OpenGames.tsx @@ -1,6 +1,5 @@ // eslint-disable-next-line -import React, { Component } from "react"; -import { connect } from 'react-redux'; +import React from "react"; import * as _ from 'lodash'; import Table from '@mui/material/Table'; @@ -15,129 +14,97 @@ import Tooltip from '@mui/material/Tooltip'; import { SortUtil, RoomsDispatch, RoomsSelectors } from 'store'; import { UserDisplay } from 'components'; +import { useAppSelector } from 'store/store'; import './OpenGames.css'; // @TODO run interval to update timeSinceCreated -class OpenGames extends Component { - private headerCells = [ - { - label: 'Age', - field: 'startTime' - }, - { - label: 'Description', - field: 'description' - }, - { - label: 'Creator', - field: 'creatorInfo.name' - }, - { - label: 'Type', - field: 'gameType' - }, - { - label: 'Restrictions', - // field: "?" - }, - { - label: 'Players', - // field: ["maxPlayers", "playerCount"] - }, - { - label: 'Spectators', - field: 'spectatorsCount' - }, - ]; - - handleSort(sortByField) { - const { room: { roomId }, sortBy } = this.props; - const { field, order } = SortUtil.toggleSortBy(sortByField, sortBy); - RoomsDispatch.sortGames(roomId, field, order); - } - - private isUnavailableGame({ started, maxPlayers, playerCount }) { - return !started && playerCount < maxPlayers; - } - - private isPasswordProtectedGame({ withPassword }) { - return !withPassword; - } - - private isBuddiesOnlyGame({ onlyBuddies }) { - return !onlyBuddies; - } - - render() { - const { room, sortBy } = this.props; - - const games = room.gameList.filter(game => ( - this.isUnavailableGame(game) && - this.isPasswordProtectedGame(game) && - this.isBuddiesOnlyGame(game) - )); - - return ( -
- - - - { _.map(this.headerCells, ({ label, field }) => { - const active = field === sortBy.field; - const order = sortBy.order.toLowerCase(); - const sortDirection = active ? order : false; - - return ( - - {!field ? label : ( - this.handleSort(field)} - > - {label} - - )} - - ); - })} - - - - { _.map(games, ({ description, gameId, gameType, creatorInfo, maxPlayers, playerCount, spectatorsCount, startTime }) => ( - - {startTime} - - -
- {description} -
-
-
- - - - {gameType} - ? - {`${playerCount}/${maxPlayers}`} - {spectatorsCount} -
- ))} -
-
-
- ); - } -} - interface OpenGamesProps { room: any; - sortBy: any; } -const mapStateToProps = state => ({ - sortBy: RoomsSelectors.getSortGamesBy(state) -}); +const OpenGames = ({ room }: OpenGamesProps) => { + const sortBy = useAppSelector(state => RoomsSelectors.getSortGamesBy(state)); -export default connect(mapStateToProps)(OpenGames); + const headerCells = [ + { label: 'Age', field: 'startTime' }, + { label: 'Description', field: 'description' }, + { label: 'Creator', field: 'creatorInfo.name' }, + { label: 'Type', field: 'gameType' }, + { label: 'Restrictions' }, + { label: 'Players' }, + { label: 'Spectators', field: 'spectatorsCount' }, + ]; + + const handleSort = (sortByField) => { + const { roomId } = room; + const { field, order } = SortUtil.toggleSortBy(sortByField, sortBy); + RoomsDispatch.sortGames(roomId, field, order); + }; + + const isUnavailableGame = ({ started, maxPlayers, playerCount }) => + !started && playerCount < maxPlayers; + + const isPasswordProtectedGame = ({ withPassword }) => !withPassword; + + const isBuddiesOnlyGame = ({ onlyBuddies }) => !onlyBuddies; + + const games = room.gameList.filter(game => ( + isUnavailableGame(game) && + isPasswordProtectedGame(game) && + isBuddiesOnlyGame(game) + )); + + return ( +
+ + + + { _.map(headerCells, ({ label, field }) => { + const active = field === sortBy.field; + const order = sortBy.order.toLowerCase(); + const sortDirection = active ? (order === 'asc' ? 'asc' : 'desc') : false; + + return ( + + {!field ? label : ( + handleSort(field)} + > + {label} + + )} + + ); + })} + + + + { _.map(games, ({ description, gameId, gameType, creatorInfo, maxPlayers, playerCount, spectatorsCount, startTime }) => ( + + {startTime} + + +
+ {description} +
+
+
+ + + + {gameType} + ? + {`${playerCount}/${maxPlayers}`} + {spectatorsCount} +
+ ))} +
+
+
+ ); +}; + +export default OpenGames; diff --git a/webclient/src/containers/Room/Room.tsx b/webclient/src/containers/Room/Room.tsx index c73930d7f..4c470d0fa 100644 --- a/webclient/src/containers/Room/Room.tsx +++ b/webclient/src/containers/Room/Room.tsx @@ -1,13 +1,13 @@ import React, { useEffect } from 'react'; -import { connect } from 'react-redux'; import { useNavigate, useParams, generatePath } from 'react-router-dom'; -import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; import Paper from '@mui/material/Paper'; import { RoomsService } from 'api'; import { ScrollToBottomOnChanges, ThreePaneLayout, UserDisplay, VirtualList, AuthGuard } from 'components'; -import { RoomsStateMessages, RoomsStateRooms, JoinedRooms, RoomsSelectors, RoomsTypes } from 'store'; +import { RoomsSelectors } from 'store'; +import { useAppSelector } from 'store/store'; import { RouteEnum } from 'types'; import Layout from 'containers/Layout/Layout'; @@ -18,8 +18,10 @@ import SayMessage from './SayMessage'; import './Room.css'; // @TODO (3) -const Room = (props) => { - const { joined, rooms, messages } = props; +const Room = () => { + const joined = useAppSelector(state => RoomsSelectors.getJoinedRooms(state)); + const rooms = useAppSelector(state => RoomsSelectors.getRooms(state)); + const messages = useAppSelector(state => RoomsSelectors.getMessages(state)); const navigate = useNavigate(); const params = useParams(); @@ -74,11 +76,10 @@ const Room = (props) => {
users[index].name } items={ users.map(user => ( - + - + )) } /> @@ -89,16 +90,4 @@ const Room = (props) => { ); } -interface RoomProps { - messages: RoomsStateMessages; - rooms: RoomsStateRooms; - joined: JoinedRooms; -} - -const mapStateToProps = state => ({ - messages: RoomsSelectors.getMessages(state), - rooms: RoomsSelectors.getRooms(state), - joined: RoomsSelectors.getJoinedRooms(state), -}); - -export default connect(mapStateToProps)(Room); +export default Room; diff --git a/webclient/src/containers/Server/Server.tsx b/webclient/src/containers/Server/Server.tsx index 7ed5e2464..3d1778343 100644 --- a/webclient/src/containers/Server/Server.tsx +++ b/webclient/src/containers/Server/Server.tsx @@ -1,21 +1,25 @@ // eslint-disable-next-line -import React, { Component } from "react"; -import { connect } from 'react-redux'; +import React from "react"; import { generatePath, useNavigate } from 'react-router-dom'; -import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; import Paper from '@mui/material/Paper'; import { AuthGuard, ThreePaneLayout, UserDisplay, VirtualList } from 'components'; import { useReduxEffect } from 'hooks'; import { RoomsSelectors, RoomsTypes, ServerSelectors } from 'store'; -import { Room, RouteEnum, User } from 'types'; +import { RouteEnum } from 'types'; +import { useAppSelector } from 'store/store'; import Rooms from './Rooms'; import Layout from 'containers/Layout/Layout'; import './Server.css'; -const Server = ({ message, rooms, joinedRooms, users }: ServerProps) => { +const Server = () => { + const message = useAppSelector(state => ServerSelectors.getMessage(state)); + const rooms = useAppSelector(state => RoomsSelectors.getRooms(state)); + const joinedRooms = useAppSelector(state => RoomsSelectors.getJoinedRooms(state)); + const users = useAppSelector(state => ServerSelectors.getUsers(state)); const navigate = useNavigate(); useReduxEffect((action: any) => { @@ -46,11 +50,10 @@ const Server = ({ message, rooms, joinedRooms, users }: ServerProps) => { Users connected to server: {users.length}
users[index].name } items={ users.map(user => ( - + - + )) } /> @@ -60,18 +63,4 @@ const Server = ({ message, rooms, joinedRooms, users }: ServerProps) => { ); } -interface ServerProps { - message: string; - rooms: Room[]; - joinedRooms: Room[]; - users: User[]; -} - -const mapStateToProps = state => ({ - message: ServerSelectors.getMessage(state), - rooms: RoomsSelectors.getRooms(state), - joinedRooms: RoomsSelectors.getJoinedRooms(state), - users: ServerSelectors.getUsers(state) -}); - -export default connect(mapStateToProps)(Server); +export default Server; diff --git a/webclient/src/containers/Unsupported/Unsupported.tsx b/webclient/src/containers/Unsupported/Unsupported.tsx index b666da12e..a4eec9d72 100644 --- a/webclient/src/containers/Unsupported/Unsupported.tsx +++ b/webclient/src/containers/Unsupported/Unsupported.tsx @@ -1,4 +1,3 @@ -import { connect } from 'react-redux'; import { useTranslation } from 'react-i18next'; import Paper from '@mui/material/Paper'; import Typography from '@mui/material/Typography'; @@ -23,8 +22,4 @@ const Unsupported = () => { ); }; -const mapStateToProps = state => ({ - -}); - -export default connect(mapStateToProps)(Unsupported); +export default Unsupported; diff --git a/webclient/src/dialogs/AccountActivationDialog/AccountActivationDialog.tsx b/webclient/src/dialogs/AccountActivationDialog/AccountActivationDialog.tsx index cb5239619..b14383a20 100644 --- a/webclient/src/dialogs/AccountActivationDialog/AccountActivationDialog.tsx +++ b/webclient/src/dialogs/AccountActivationDialog/AccountActivationDialog.tsx @@ -11,7 +11,7 @@ import { AccountActivationForm } from 'forms'; import './AccountActivationDialog.css'; -const AccountActivationDialog = ({ classes, handleClose, isOpen, onSubmit }: any) => { +const AccountActivationDialog = ({ handleClose, isOpen, onSubmit }: any) => { const { t } = useTranslation(); const handleOnClose = () => { diff --git a/webclient/src/dialogs/CardImportDialog/CardImportDialog.tsx b/webclient/src/dialogs/CardImportDialog/CardImportDialog.tsx index 8011d317a..1293fa5ba 100644 --- a/webclient/src/dialogs/CardImportDialog/CardImportDialog.tsx +++ b/webclient/src/dialogs/CardImportDialog/CardImportDialog.tsx @@ -10,7 +10,7 @@ import { CardImportForm } from 'forms'; import './CardImportDialog.css'; -const CardImportDialog = ({ classes, handleClose, isOpen }: any) => { +const CardImportDialog = ({ handleClose, isOpen }: any) => { const handleOnClose = () => { handleClose(); } diff --git a/webclient/src/dialogs/KnownHostDialog/KnownHostDialog.tsx b/webclient/src/dialogs/KnownHostDialog/KnownHostDialog.tsx index 5bde19d92..bfc164e0d 100644 --- a/webclient/src/dialogs/KnownHostDialog/KnownHostDialog.tsx +++ b/webclient/src/dialogs/KnownHostDialog/KnownHostDialog.tsx @@ -4,7 +4,6 @@ import Dialog from '@mui/material/Dialog'; import DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; import IconButton from '@mui/material/IconButton'; -import AddIcon from '@mui/icons-material/Add'; import CloseIcon from '@mui/icons-material/Close'; import Typography from '@mui/material/Typography'; import { useTranslation } from 'react-i18next'; diff --git a/webclient/src/dialogs/RegistrationDialog/RegistrationDialog.tsx b/webclient/src/dialogs/RegistrationDialog/RegistrationDialog.tsx index 2388dddb3..098306077 100644 --- a/webclient/src/dialogs/RegistrationDialog/RegistrationDialog.tsx +++ b/webclient/src/dialogs/RegistrationDialog/RegistrationDialog.tsx @@ -11,7 +11,7 @@ import { RegisterForm } from 'forms'; import './RegistrationDialog.css'; -const RegistrationDialog = ({ classes, handleClose, isOpen, onSubmit }: any) => { +const RegistrationDialog = ({ handleClose, isOpen, onSubmit }: any) => { const { t } = useTranslation(); const handleOnClose = () => { diff --git a/webclient/src/dialogs/RequestPasswordResetDialog/RequestPasswordResetDialog.tsx b/webclient/src/dialogs/RequestPasswordResetDialog/RequestPasswordResetDialog.tsx index be2032a08..6b0158314 100644 --- a/webclient/src/dialogs/RequestPasswordResetDialog/RequestPasswordResetDialog.tsx +++ b/webclient/src/dialogs/RequestPasswordResetDialog/RequestPasswordResetDialog.tsx @@ -11,7 +11,7 @@ import { RequestPasswordResetForm } from 'forms'; import './RequestPasswordResetDialog.css'; -const RequestPasswordResetDialog = ({ classes, handleClose, isOpen, onSubmit, skipTokenRequest }: any) => { +const RequestPasswordResetDialog = ({ handleClose, isOpen, onSubmit, skipTokenRequest }: any) => { const { t } = useTranslation(); const handleOnClose = () => { diff --git a/webclient/src/dialogs/ResetPasswordDialog/ResetPasswordDialog.tsx b/webclient/src/dialogs/ResetPasswordDialog/ResetPasswordDialog.tsx index 877c8729f..f4e1cc520 100644 --- a/webclient/src/dialogs/ResetPasswordDialog/ResetPasswordDialog.tsx +++ b/webclient/src/dialogs/ResetPasswordDialog/ResetPasswordDialog.tsx @@ -11,7 +11,7 @@ import { ResetPasswordForm } from 'forms'; import './ResetPasswordDialog.css'; -const ResetPasswordDialog = ({ classes, handleClose, isOpen, onSubmit, userName }: any) => { +const ResetPasswordDialog = ({ handleClose, isOpen, onSubmit, userName }: any) => { const { t } = useTranslation(); const handleOnClose = () => { diff --git a/webclient/src/forms/AccountActivationForm/AccountActivationForm.tsx b/webclient/src/forms/AccountActivationForm/AccountActivationForm.tsx index 61b20f9a5..d2ec8e662 100644 --- a/webclient/src/forms/AccountActivationForm/AccountActivationForm.tsx +++ b/webclient/src/forms/AccountActivationForm/AccountActivationForm.tsx @@ -1,20 +1,17 @@ // eslint-disable-next-line import React, { useState } from "react"; -import { connect } from 'react-redux'; import { Form, Field } from 'react-final-form'; -import { OnChange } from 'react-final-form-listeners'; import { useTranslation } from 'react-i18next'; import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; -import { InputField, KnownHosts } from 'components'; -import { FormKey } from 'types'; - -import './AccountActivationForm.css'; +import { InputField } from 'components'; import { useReduxEffect } from 'hooks'; import { ServerTypes } from 'store'; +import './AccountActivationForm.css'; + const AccountActivationForm = ({ onSubmit }) => { const [errorMessage, setErrorMessage] = useState(false); const { t } = useTranslation(); @@ -43,7 +40,7 @@ const AccountActivationForm = ({ onSubmit }) => { return (
- {({ handleSubmit, form }) => { + {({ handleSubmit }) => { return (
diff --git a/webclient/src/forms/CardImportForm/CardImportForm.tsx b/webclient/src/forms/CardImportForm/CardImportForm.tsx index b9745f31e..38a1893cb 100644 --- a/webclient/src/forms/CardImportForm/CardImportForm.tsx +++ b/webclient/src/forms/CardImportForm/CardImportForm.tsx @@ -1,7 +1,6 @@ -// eslint-disable-next-line + import React, { useEffect, useState } from 'react'; -import { connect } from 'react-redux'; -import { Form, Field, reduxForm } from 'redux-form' +import { Form, Field } from 'react-final-form'; import Button from '@mui/material/Button'; import Stepper from '@mui/material/Stepper'; @@ -11,12 +10,10 @@ import CircularProgress from '@mui/material/CircularProgress'; import { InputField, VirtualList } from 'components'; import { cardImporterService, CardDTO, SetDTO, TokenDTO } from 'services'; -import { FormKey } from 'types'; import './CardImportForm.css'; -const CardImportForm = (props) => { - const { handleSubmit, onSubmit: onClose } = props; +const CardImportForm = ({ onSubmit: onClose }) => { const [loading, setLoading] = useState(false); const [activeStep, setActiveStep] = useState(0); @@ -85,20 +82,27 @@ const CardImportForm = (props) => { const getStepContent = (stepIndex) => { switch (stepIndex) { case 0: return ( - -
- -
+ + {({ handleSubmit }) => ( + +
+ +
-
- -
+
+ +
-
- -
+
+ +
+ + )} ); @@ -122,21 +126,28 @@ const CardImportForm = (props) => { ); case 2: return ( -
-
- -
+ + {({ handleSubmit }) => ( + +
+ +
-
- - -
+
+ + +
-
- -
+
+ +
+
+ )} ); @@ -204,7 +215,6 @@ const CardsImported = ({ cards, sets }) => { return (
index } items={items} size={15} /> @@ -212,16 +222,4 @@ const CardsImported = ({ cards, sets }) => { ); }; -const propsMap = { - form: FormKey.CARD_IMPORT, - onClose: Function -}; - -const mapStateToProps = () => ({ - initialValues: { - cardDownloadUrl: 'https://www.mtgjson.com/api/v5/AllPrintings.json', - tokenDownloadUrl: 'https://raw.githubusercontent.com/Cockatrice/Magic-Token/master/tokens.xml' - }, -}); - -export default connect(mapStateToProps)(reduxForm(propsMap)(CardImportForm)); +export default CardImportForm; diff --git a/webclient/src/forms/KnownHostForm/KnownHostForm.tsx b/webclient/src/forms/KnownHostForm/KnownHostForm.tsx index 1241336f3..5fd31e13f 100644 --- a/webclient/src/forms/KnownHostForm/KnownHostForm.tsx +++ b/webclient/src/forms/KnownHostForm/KnownHostForm.tsx @@ -1,6 +1,5 @@ // eslint-disable-next-line import React, { useState } from "react"; -import { connect } from 'react-redux'; import { Form, Field } from 'react-final-form' import { useTranslation } from 'react-i18next'; @@ -87,8 +86,4 @@ const KnownHostForm = ({ host, onRemove, onSubmit }) => { ); }; -const mapStateToProps = () => ({ - -}); - -export default connect(mapStateToProps)(KnownHostForm); +export default KnownHostForm; diff --git a/webclient/src/forms/LoginForm/LoginForm.tsx b/webclient/src/forms/LoginForm/LoginForm.tsx index 1e8f104fe..ed387c045 100644 --- a/webclient/src/forms/LoginForm/LoginForm.tsx +++ b/webclient/src/forms/LoginForm/LoginForm.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useCallback } from 'react'; +import React, { useEffect, useState } from 'react'; import { Form, Field } from 'react-final-form'; import { OnChange } from 'react-final-form-listeners'; import { useTranslation } from 'react-i18next'; @@ -87,7 +87,7 @@ const LoginForm = ({ onSubmit, disableSubmitButton, onResetPassword }: LoginForm const onUserNameChange = (userName) => { const fieldChanged = host?.userName?.toLowerCase() !== values.userName?.toLowerCase(); if (useStoredPassword(values.remember, values.password) && fieldChanged) { - setHost(({ hashedPassword, ...s }) => ({ ...s, userName })); + setHost(({ hashedPassword: _hashedPassword, ...s }) => ({ ...s, userName })); } } diff --git a/webclient/src/forms/RegisterForm/RegisterForm.tsx b/webclient/src/forms/RegisterForm/RegisterForm.tsx index 086aec0df..42b34fda0 100644 --- a/webclient/src/forms/RegisterForm/RegisterForm.tsx +++ b/webclient/src/forms/RegisterForm/RegisterForm.tsx @@ -1,4 +1,5 @@ import { useState } from 'react'; +import { useSelector } from 'react-redux'; import { Form, Field } from 'react-final-form'; import { OnChange } from 'react-final-form-listeners'; import setFieldTouched from 'final-form-set-field-touched'; @@ -9,7 +10,7 @@ import Typography from '@mui/material/Typography'; import { CountryDropdown, InputField, KnownHosts } from 'components'; import { useReduxEffect } from 'hooks'; -import { ServerTypes } from 'store'; +import { ServerDispatch, ServerSelectors, ServerTypes } from 'store'; import './RegisterForm.css'; import { useToast } from 'components/Toast'; @@ -17,13 +18,13 @@ import { useToast } from 'components/Toast'; const RegisterForm = ({ onSubmit }: RegisterFormProps) => { const { t } = useTranslation(); const [emailRequired, setEmailRequired] = useState(false); - const [error, setError] = useState(null); const [emailError, setEmailError] = useState(null); const [passwordError, setPasswordError] = useState(null); const [userNameError, setUserNameError] = useState(null); + const error = useSelector(ServerSelectors.getRegistrationError); const { openToast } = useToast({ key: 'registration-success', children: t('RegisterForm.toast.registerSuccess') }) - const onHostChange = (host) => setEmailRequired(false); + const onHostChange = () => setEmailRequired(false); const onEmailChange = () => emailError && setEmailError(null); const onPasswordChange = () => passwordError && setPasswordError(null); const onUserNameChange = () => userNameError && setUserNameError(null); @@ -32,10 +33,6 @@ const RegisterForm = ({ onSubmit }: RegisterFormProps) => { setEmailRequired(true); }, ServerTypes.REGISTRATION_REQUIRES_EMAIL); - useReduxEffect(({ error }) => { - setError(error); - }, ServerTypes.REGISTRATION_FAILED); - useReduxEffect(() => { openToast() }, ServerTypes.REGISTRATION_SUCCESS); @@ -53,7 +50,7 @@ const RegisterForm = ({ onSubmit }: RegisterFormProps) => { }, ServerTypes.REGISTRATION_USERNAME_ERROR); const handleOnSubmit = ({ userName, email, realName, ...values }) => { - setError(null); + ServerDispatch.clearRegistrationErrors(); userName = userName?.trim(); email = email?.trim(); @@ -100,8 +97,7 @@ const RegisterForm = ({ onSubmit }: RegisterFormProps) => { return (
- {({ handleSubmit, form, ...args }) => { - const { values } = form.getState(); + {({ handleSubmit, form }) => { if (emailRequired) { // Allow form render to complete diff --git a/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.tsx b/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.tsx index 01d4a2959..589f8da1e 100644 --- a/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.tsx +++ b/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.tsx @@ -1,6 +1,5 @@ // eslint-disable-next-line import React, { useState } from "react"; -import { connect } from 'react-redux'; import { Form, Field } from 'react-final-form'; import { OnChange } from 'react-final-form-listeners'; import { useTranslation } from 'react-i18next'; @@ -9,12 +8,11 @@ import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; import { InputField, KnownHosts } from 'components'; -import { FormKey } from 'types'; - -import './RequestPasswordResetForm.css'; import { useReduxEffect } from 'hooks'; import { ServerTypes } from 'store'; +import './RequestPasswordResetForm.css'; + const RequestPasswordResetForm = ({ onSubmit, skipTokenRequest }) => { const [errorMessage, setErrorMessage] = useState(false); const [isMFA, setIsMFA] = useState(false); diff --git a/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.tsx b/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.tsx index 8b56f4b69..f0440363f 100644 --- a/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.tsx +++ b/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.tsx @@ -1,20 +1,17 @@ // eslint-disable-next-line import React, { useEffect, useState } from 'react'; -import { connect } from 'react-redux'; import { Form, Field } from 'react-final-form' -import { OnChange } from 'react-final-form-listeners'; import { useTranslation } from 'react-i18next'; import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; import { InputField, KnownHosts } from 'components'; -import { FormKey } from 'types'; - -import './ResetPasswordForm.css'; import { useReduxEffect } from '../../hooks'; import { ServerTypes } from '../../store'; +import './ResetPasswordForm.css'; + const ResetPasswordForm = ({ onSubmit, userName }) => { const [errorMessage, setErrorMessage] = useState(false); const { t } = useTranslation(); @@ -60,7 +57,7 @@ const ResetPasswordForm = ({ onSubmit, userName }) => { return ( - {({ handleSubmit, form }) => ( + {({ handleSubmit }) => (
diff --git a/webclient/src/forms/SearchForm/SearchForm.tsx b/webclient/src/forms/SearchForm/SearchForm.tsx index 793543086..bca8a498f 100644 --- a/webclient/src/forms/SearchForm/SearchForm.tsx +++ b/webclient/src/forms/SearchForm/SearchForm.tsx @@ -1,63 +1,57 @@ // eslint-disable-next-line -import React, { Component } from "react"; -import { connect } from 'react-redux'; -import { Form, Field, reduxForm } from 'redux-form' +import React from "react"; +import { Form, Field } from 'react-final-form'; import Button from '@mui/material/Button'; import Divider from '@mui/material/Divider'; import Paper from '@mui/material/Paper'; import { InputField, CheckboxField } from 'components'; -import { FormKey } from 'types'; import './SearchForm.css'; -const SearchForm = ({ handleSubmit }) => ( - - -
- -
-
- -
-
- -
-
- -
-
- -
- -
- - - -
- -
- Date Range: Coming Soon -
- -
- Maximum Results: 1000 -
- - - -
+const SearchForm = ({ onSubmit }) => ( +
+ {({ handleSubmit }) => ( + + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ + + +
+ +
+ Date Range: Coming Soon +
+ +
+ Maximum Results: 1000 +
+ + + +
+ )} + ); -const propsMap = { - form: FormKey.SEARCH_LOGS, -}; - -const mapStateToProps = () => ({ - -}); - -export default connect(mapStateToProps)(reduxForm(propsMap)(SearchForm)); +export default SearchForm; diff --git a/webclient/src/hooks/useAutoConnect.ts b/webclient/src/hooks/useAutoConnect.ts index 5258f58a7..6a2690975 100644 --- a/webclient/src/hooks/useAutoConnect.ts +++ b/webclient/src/hooks/useAutoConnect.ts @@ -1,11 +1,8 @@ import { useEffect, useState } from 'react'; -import { debounce, DebouncedFunc } from 'lodash'; import { SettingDTO } from 'services'; import { APP_USER } from 'types'; -type OnChange = () => void; - export function useAutoConnect() { const [setting, setSetting] = useState(undefined); const [autoConnect, setAutoConnect] = useState(undefined); diff --git a/webclient/src/hooks/useFireOnce/useFireOnce.spec.tsx b/webclient/src/hooks/useFireOnce/useFireOnce.spec.tsx index 8445bb567..9c87ccf64 100644 --- a/webclient/src/hooks/useFireOnce/useFireOnce.spec.tsx +++ b/webclient/src/hooks/useFireOnce/useFireOnce.spec.tsx @@ -1,9 +1,7 @@ import { render, fireEvent, - getByRole, waitFor, - act } from '@testing-library/react'; import { useFireOnce } from './useFireOnce'; @@ -21,7 +19,7 @@ describe('useFireOnce hook', () => { function Button(props) { const { children, onClick } = props - const [buttonIsDisabled, setButtonIsDisabled, handleClickOnce] = useFireOnce(onClick) + const [buttonIsDisabled, _setButtonIsDisabled, handleClickOnce] = useFireOnce(onClick) return } @@ -65,7 +63,7 @@ describe('useFireOnce hook', () => { function Form(props) { const { onSubmit } = props - const [buttonIsDisabled, setButtonIsDisabled, handleSubmitOnce] = useFireOnce(onSubmit) + const [buttonIsDisabled, _setButtonIsDisabled, handleSubmitOnce] = useFireOnce(onSubmit) return (
diff --git a/webclient/src/hooks/useFireOnce/useFireOnce.ts b/webclient/src/hooks/useFireOnce/useFireOnce.ts index 0b042a907..54c184160 100644 --- a/webclient/src/hooks/useFireOnce/useFireOnce.ts +++ b/webclient/src/hooks/useFireOnce/useFireOnce.ts @@ -1,6 +1,4 @@ import { useCallback, useState } from 'react'; -import { useReduxEffect } from 'hooks'; -import { ServerTypes } from 'store'; type UseFireOnceType = (...args: any) => any; diff --git a/webclient/src/hooks/useReduxEffect.tsx b/webclient/src/hooks/useReduxEffect.tsx index 83273a6d3..a84d5d728 100644 --- a/webclient/src/hooks/useReduxEffect.tsx +++ b/webclient/src/hooks/useReduxEffect.tsx @@ -6,10 +6,12 @@ File is adapted from https://github.com/Qeepsake/use-redux-effect under MIT Lice import { useRef, useEffect, DependencyList } from 'react' import { useStore } from 'react-redux' -import { AnyAction } from 'redux' import { castArray } from 'lodash' -export type ReduxEffect = (action: AnyAction) => void +// Actions are identified by string `type` at runtime, so the callback +// receives an untyped action object to allow free property access. + +export type ReduxEffect = (action: any) => void /** * Subscribes to redux store events diff --git a/webclient/src/index.tsx b/webclient/src/index.tsx index 97b5bb014..959d2adc7 100644 --- a/webclient/src/index.tsx +++ b/webclient/src/index.tsx @@ -1,6 +1,6 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; -import { Theme, StyledEngineProvider } from '@mui/material'; +import { StyledEngineProvider } from '@mui/material'; import { ThemeProvider } from '@mui/material/styles'; import { AppShell } from './containers'; diff --git a/webclient/src/services/CardImporterService.ts b/webclient/src/services/CardImporterService.ts index 691e86f0e..27014b57c 100644 --- a/webclient/src/services/CardImporterService.ts +++ b/webclient/src/services/CardImporterService.ts @@ -34,7 +34,7 @@ class CardImporterService { .map(key => unsortedCards[key]); return { cards, sets }; - } catch (e) { + } catch { throw new Error(error); } }); @@ -61,7 +61,7 @@ class CardImporterService { ); return tokens; - } catch (e) { + } catch { throw new Error(error); } }) diff --git a/webclient/src/setupTests.ts b/webclient/src/setupTests.ts index 5b9a2b986..a4fe5ea8d 100644 --- a/webclient/src/setupTests.ts +++ b/webclient/src/setupTests.ts @@ -1,2 +1,9 @@ // ensure jest-dom is always available during testing to cut down on boilerplate import '@testing-library/jest-dom/vitest'; + +// With isolate: false, all test files share the same module context. +// Restore all mocks/spies after each test to prevent leakage between tests. +afterEach(() => { + vi.restoreAllMocks(); + vi.useRealTimers(); +}); diff --git a/webclient/src/store/actions/actionReducer.ts b/webclient/src/store/actions/actionReducer.ts index a01dba984..b3e883ee1 100644 --- a/webclient/src/store/actions/actionReducer.ts +++ b/webclient/src/store/actions/actionReducer.ts @@ -3,12 +3,12 @@ * @description Application reducer. */ -import { AnyAction } from 'redux' +import { UnknownAction } from '@reduxjs/toolkit' interface InitialState { type: string | null - payload: any - meta: any + payload: unknown + meta: unknown error: boolean count: number } @@ -33,7 +33,7 @@ const initialState: InitialState = { */ export const actionReducer = ( state = initialState, - action: AnyAction, + action: UnknownAction, ): InitialState => { return { ...state, diff --git a/webclient/src/store/common/SortUtil.spec.ts b/webclient/src/store/common/SortUtil.spec.ts index e3b5a4a6d..acd117506 100644 --- a/webclient/src/store/common/SortUtil.spec.ts +++ b/webclient/src/store/common/SortUtil.spec.ts @@ -1,4 +1,6 @@ +import { create } from '@bufbuild/protobuf'; import { SortDirection } from 'types'; +import { ServerInfo_UserSchema } from 'generated/proto/serverinfo_user_pb'; import SortUtil from './SortUtil'; // ── sortByField ─────────────────────────────────────────────────────────────── @@ -118,11 +120,11 @@ describe('sortByFields', () => { describe('sortUsersByField', () => { it('sorts by userLevel DESC first, then name ASC', () => { const users = [ - { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }, - { name: 'Bob', userLevel: 8, accountageSecs: 0n, privlevel: '' }, - { name: 'Carol', userLevel: 1, accountageSecs: 0n, privlevel: '' }, + create(ServerInfo_UserSchema, { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }), + create(ServerInfo_UserSchema, { name: 'Bob', userLevel: 8, accountageSecs: 0n, privlevel: '' }), + create(ServerInfo_UserSchema, { name: 'Carol', userLevel: 1, accountageSecs: 0n, privlevel: '' }), ]; - SortUtil.sortUsersByField(users as any, { field: 'name', order: SortDirection.ASC }); + SortUtil.sortUsersByField(users, { field: 'name', order: SortDirection.ASC }); expect(users[0].name).toBe('Bob'); expect(users[1].name).toBe('Alice'); expect(users[2].name).toBe('Carol'); @@ -136,11 +138,11 @@ describe('sortUsersByField', () => { it('returns 0 (stable) when two users tie on both userLevel and name', () => { const users = [ - { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }, - { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }, + create(ServerInfo_UserSchema, { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }), + create(ServerInfo_UserSchema, { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }), ]; expect(() => - SortUtil.sortUsersByField(users as any, { field: 'name', order: SortDirection.ASC }) + SortUtil.sortUsersByField(users, { field: 'name', order: SortDirection.ASC }) ).not.toThrow(); expect(users).toHaveLength(2); }); diff --git a/webclient/src/store/common/SortUtil.ts b/webclient/src/store/common/SortUtil.ts index f6aca821d..7dada2c9c 100644 --- a/webclient/src/store/common/SortUtil.ts +++ b/webclient/src/store/common/SortUtil.ts @@ -1,7 +1,7 @@ import { SortBy, SortDirection, User } from 'types'; export default class SortUtil { - static sortByField(arr: any[], sortBy: SortBy): void { + static sortByField(arr: T[], sortBy: SortBy): void { if (arr.length) { const field = SortUtil.resolveFieldChain(arr[0], sortBy.field); const fieldType = typeof field; @@ -20,7 +20,7 @@ export default class SortUtil { } } - static sortByFields(arr: any[], sorts: SortBy[]) { + static sortByFields(arr: T[], sorts: SortBy[]) { if (arr.length) { arr.sort((a, b) => { for (let i = 0; i < sorts.length; i++) { @@ -57,7 +57,7 @@ export default class SortUtil { } } - static toggleSortBy(field: string, sortBy: SortBy) { + static toggleSortBy(field: F, sortBy: SortBy): { field: F; order: SortDirection } { const sameField = field === sortBy.field; const isASC = sortBy.order === SortDirection.ASC; @@ -67,15 +67,15 @@ export default class SortUtil { } } - private static sortByNumber(arr: any[], sortBy: SortBy): void { + private static sortByNumber(arr: T[], sortBy: SortBy): void { arr.sort((a, b) => SortUtil.numberComparator(a, b, sortBy)); } - private static sortByString(arr: any[], sortBy: SortBy): void { + private static sortByString(arr: T[], sortBy: SortBy): void { arr.sort((a, b) => SortUtil.stringComparator(a, b, sortBy)); } - private static userComparator(a, b, sortBy, sortByUserLevel = true) { + private static userComparator(a: User, b: User, sortBy: SortBy, sortByUserLevel = true) { if (sortByUserLevel) { const adminSortBy = { field: 'userLevel', @@ -98,7 +98,7 @@ export default class SortUtil { return 0; } - private static numberComparator(a, b, { field, order }: SortBy) { + private static numberComparator(a: T, b: T, { field, order }: SortBy) { const aResolved = SortUtil.resolveFieldChain(a, field); const bResolved = SortUtil.resolveFieldChain(b, field); @@ -109,7 +109,7 @@ export default class SortUtil { } } - private static stringComparator(a, b, { field, order }: SortBy) { + private static stringComparator(a: T, b: T, { field, order }: SortBy) { const aResolved = SortUtil.resolveFieldChain(a, field); const bResolved = SortUtil.resolveFieldChain(b, field); diff --git a/webclient/src/store/common/index.ts b/webclient/src/store/common/index.ts index 340676b6d..405723028 100644 --- a/webclient/src/store/common/index.ts +++ b/webclient/src/store/common/index.ts @@ -1 +1,2 @@ export { default as SortUtil } from './SortUtil'; +export * from './normalizers'; diff --git a/webclient/src/store/common/normalizers.spec.ts b/webclient/src/store/common/normalizers.spec.ts new file mode 100644 index 000000000..ddd0a8f1e --- /dev/null +++ b/webclient/src/store/common/normalizers.spec.ts @@ -0,0 +1,121 @@ +import { normalizeRoomInfo, normalizeGameObject, normalizeLogs, normalizeBannedUserError, normalizeUserMessage } from './normalizers'; +import { create } from '@bufbuild/protobuf'; +import { ServerInfo_RoomSchema } from 'generated/proto/serverinfo_room_pb'; +import { ServerInfo_GameSchema } from 'generated/proto/serverinfo_game_pb'; +import { Event_RoomSaySchema } from 'generated/proto/event_room_say_pb'; +import { Message } from 'types'; + +describe('normalizeRoomInfo', () => { + it('builds gametypeMap from gametypeList and normalises games', () => { + const room = create(ServerInfo_RoomSchema, { + roomId: 1, + name: 'Lobby', + gametypeList: [{ gameTypeId: 1, description: 'Standard' }], + gameList: [ + create(ServerInfo_GameSchema, { gameId: 10, gameTypes: [1], description: 'My Game' }), + ], + }); + + const result = normalizeRoomInfo(room); + + expect(result.gametypeMap).toEqual({ 1: 'Standard' }); + expect(result.gameList).toHaveLength(1); + expect(result.gameList[0].gameType).toBe('Standard'); + expect(result.order).toBe(0); + }); + + it('handles room with empty gametypeList', () => { + const room = create(ServerInfo_RoomSchema, { roomId: 2, name: 'Empty' }); + const result = normalizeRoomInfo(room); + expect(result.gametypeMap).toEqual({}); + expect(result.gameList).toEqual([]); + }); +}); + +describe('normalizeGameObject', () => { + it('maps gameTypes[0] to gameType string via gametypeMap', () => { + const game = create(ServerInfo_GameSchema, { gameId: 1, gameTypes: [5] }); + const result = normalizeGameObject(game, { 5: 'Legacy' }); + expect(result.gameType).toBe('Legacy'); + }); + + it('returns empty string when no gameTypes', () => { + const game = create(ServerInfo_GameSchema, { gameId: 2 }); + const result = normalizeGameObject(game, {}); + expect(result.gameType).toBe(''); + }); + + it('fills empty description with empty string', () => { + const game = create(ServerInfo_GameSchema, { gameId: 3 }); + const result = normalizeGameObject(game, {}); + expect(result.description).toBe(''); + }); +}); + +describe('normalizeLogs', () => { + it('groups logs by targetType', () => { + const logs = [ + { targetType: 'room' }, + { targetType: 'game' }, + { targetType: 'room' }, + ] as any[]; + const result = normalizeLogs(logs); + expect(result.room).toHaveLength(2); + expect(result.game).toHaveLength(1); + expect(result.chat).toBeUndefined(); + }); + + it('returns empty object for empty logs', () => { + expect(normalizeLogs([])).toEqual({}); + }); +}); + +describe('normalizeBannedUserError', () => { + it('returns permanently banned message when endTime is 0', () => { + expect(normalizeBannedUserError('', 0)).toBe('You are permanently banned'); + }); + + it('returns banned until date when endTime is given', () => { + const endTime = new Date('2030-01-01').getTime(); + const result = normalizeBannedUserError('', endTime); + expect(result).toContain('You are banned until'); + expect(result).toContain(new Date(endTime).toString()); + }); + + it('appends reason when provided', () => { + expect(normalizeBannedUserError('bad behavior', 0)).toContain('\n\nbad behavior'); + }); + + it('does not append separator when reason is empty', () => { + expect(normalizeBannedUserError('', 0)).not.toContain('\n\n'); + }); +}); + +describe('normalizeUserMessage', () => { + const makeMsg = (fields: Partial): Message => ({ + ...create(Event_RoomSaySchema), + timeReceived: 0, + ...fields, + } as Message); + + it('prepends "name: " to message when name is present', () => { + const result = normalizeUserMessage(makeMsg({ name: 'Alice', message: 'hello' })); + expect(result.message).toBe('Alice: hello'); + }); + + it('returns message unchanged when name is empty', () => { + const result = normalizeUserMessage(makeMsg({ name: '', message: 'system msg' })); + expect(result.message).toBe('system msg'); + }); + + it('does not mutate the original message', () => { + const original = makeMsg({ name: 'Bob', message: 'hi' }); + normalizeUserMessage(original); + expect(original.message).toBe('hi'); + }); + + it('returns the original reference when no name (no allocation)', () => { + const original = makeMsg({ name: '', message: 'hi' }); + expect(normalizeUserMessage(original)).toBe(original); + }); +}); diff --git a/webclient/src/store/common/normalizers.ts b/webclient/src/store/common/normalizers.ts new file mode 100644 index 000000000..cd08a8fd5 --- /dev/null +++ b/webclient/src/store/common/normalizers.ts @@ -0,0 +1,85 @@ +import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; +import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; +import type { ServerInfo_GameType } from 'generated/proto/serverinfo_gametype_pb'; +import { Game, GametypeMap, LogItem, LogGroups, Message, Room } from 'types'; + +/** Flatten a gametype list into a lookup map of { gameTypeId → description }. */ +export function normalizeGametypeMap(gametypeList: ServerInfo_GameType[]): GametypeMap { + return gametypeList.reduce((map, type) => { + map[type.gameTypeId] = type.description; + return map; + }, {}); +} + +/** Flatten room gameTypes into a map object and normalize all games inside. */ +export function normalizeRoomInfo(roomInfo: ServerInfo_Room): Room { + const gametypeMap = normalizeGametypeMap(roomInfo.gametypeList); + + const gameList = roomInfo.gameList.map( + (game) => normalizeGameObject(game, gametypeMap), + ); + + return { + ...roomInfo, + gametypeMap, + gameList, + order: 0, + }; +} + +/** Flatten gameTypes[] into a gameType string; fill in default sortable values. */ +export function normalizeGameObject(game: ServerInfo_Game, gametypeMap: GametypeMap): Game { + const { gameTypes, description } = game; + const hasType = gameTypes && gameTypes.length; + + return { + ...game, + gameType: hasType ? gametypeMap[gameTypes[0]] : '', + description: description || '', + }; +} + +/** Group a flat LogItem[] into { room, game, chat } buckets for the server store. */ +export function normalizeLogs(logs: LogItem[]): LogGroups { + return logs.reduce((obj, log) => { + const type = log.targetType as keyof LogGroups; + obj[type] = obj[type] || []; + obj[type]!.push(log); + return obj; + }, {} as LogGroups); +} + +/** + * Prepend "name: " to the message text when a sender name is present. + * Messages from the current user are sent without a name by the server, + * so this is a no-op for those. + * Returns a new Message — does not mutate the original. + */ +export function normalizeUserMessage(message: Message): Message { + if (!message.name) { + return message; + } + return { ...message, message: `${message.name}: ${message.message}` }; +} + +/** + * Build the user-facing ban error string from raw server data. + * The server sends a reason string and an endTime epoch ms (0 = permanent). + * Messages from the current user do not carry the username — this quirk is + * handled at the dispatch layer so the redux store always stores a clean string. + */ +export function normalizeBannedUserError(reason: string, endTime: number): string { + let error: string; + + if (endTime) { + error = 'You are banned until ' + new Date(endTime).toString(); + } else { + error = 'You are permanently banned'; + } + + if (reason) { + error += '\n\n' + reason; + } + + return error; +} diff --git a/webclient/src/store/game/__mocks__/fixtures.ts b/webclient/src/store/game/__mocks__/fixtures.ts index 19df4d398..b491de17d 100644 --- a/webclient/src/store/game/__mocks__/fixtures.ts +++ b/webclient/src/store/game/__mocks__/fixtures.ts @@ -1,8 +1,14 @@ -import { ArrowInfo, CardInfo, CounterInfo, PlayerProperties } from 'types'; +import { ArrowInfo, CardInfo, CounterInfo, PlayerProperties, ProtoInit } from 'types'; +import { create } from '@bufbuild/protobuf'; +import { ServerInfo_CardSchema } from 'generated/proto/serverinfo_card_pb'; +import { ServerInfo_CounterSchema } from 'generated/proto/serverinfo_counter_pb'; +import { colorSchema } from 'generated/proto/color_pb'; +import { ServerInfo_ArrowSchema } from 'generated/proto/serverinfo_arrow_pb'; +import { ServerInfo_PlayerPropertiesSchema } from 'generated/proto/serverinfo_playerproperties_pb'; import { GameEntry, GamesState, PlayerEntry, ZoneEntry } from '../game.interfaces'; -export function makeCard(overrides: Partial = {}): CardInfo { - return { +export function makeCard(overrides: ProtoInit = {}): CardInfo { + return create(ServerInfo_CardSchema, { id: 1, name: 'Test Card', x: 0, @@ -21,22 +27,22 @@ export function makeCard(overrides: Partial = {}): CardInfo { attachCardId: -1, providerId: '', ...overrides, - }; + }); } -export function makeCounter(overrides: Partial = {}): CounterInfo { - return { +export function makeCounter(overrides: ProtoInit = {}): CounterInfo { + return create(ServerInfo_CounterSchema, { id: 1, name: 'Life', - counterColor: { r: 0, g: 0, b: 0, a: 255 }, + counterColor: create(colorSchema, { r: 0, g: 0, b: 0, a: 255 }), radius: 1, count: 20, ...overrides, - }; + }); } -export function makeArrow(overrides: Partial = {}): ArrowInfo { - return { +export function makeArrow(overrides: ProtoInit = {}): ArrowInfo { + return create(ServerInfo_ArrowSchema, { id: 1, startPlayerId: 1, startZone: 'table', @@ -44,9 +50,9 @@ export function makeArrow(overrides: Partial = {}): ArrowInfo { targetPlayerId: 1, targetZone: 'table', targetCardId: 2, - arrowColor: { r: 255, g: 0, b: 0, a: 255 }, + arrowColor: create(colorSchema, { r: 255, g: 0, b: 0, a: 255 }), ...overrides, - }; + }); } export function makeZoneEntry(overrides: Partial = {}): ZoneEntry { @@ -62,10 +68,9 @@ export function makeZoneEntry(overrides: Partial = {}): ZoneEntry { }; } -export function makePlayerProperties(overrides: Partial = {}): PlayerProperties { - return { +export function makePlayerProperties(overrides: ProtoInit = {}): PlayerProperties { + return create(ServerInfo_PlayerPropertiesSchema, { playerId: 1, - userInfo: null, spectator: false, conceded: false, readyStart: false, @@ -74,7 +79,7 @@ export function makePlayerProperties(overrides: Partial = {}): sideboardLocked: false, judge: false, ...overrides, - }; + }); } export function makePlayerEntry(overrides: Partial = {}): PlayerEntry { diff --git a/webclient/src/store/game/game.actions.spec.ts b/webclient/src/store/game/game.actions.spec.ts index 0fb2c361a..00afd55d9 100644 --- a/webclient/src/store/game/game.actions.spec.ts +++ b/webclient/src/store/game/game.actions.spec.ts @@ -1,3 +1,4 @@ +import { create } from '@bufbuild/protobuf'; import { Actions } from './game.actions'; import { Types } from './game.types'; import { @@ -6,8 +7,26 @@ import { makeCounter, makeGameEntry, makePlayerProperties, - makeZoneEntry, } from './__mocks__/fixtures'; +import { Event_GameStateChangedSchema } from 'generated/proto/event_game_state_changed_pb'; +import { Event_MoveCardSchema } from 'generated/proto/event_move_card_pb'; +import { Event_FlipCardSchema } from 'generated/proto/event_flip_card_pb'; +import { Event_DestroyCardSchema } from 'generated/proto/event_destroy_card_pb'; +import { Event_AttachCardSchema } from 'generated/proto/event_attach_card_pb'; +import { Event_CreateTokenSchema } from 'generated/proto/event_create_token_pb'; +import { Event_SetCardAttrSchema } from 'generated/proto/event_set_card_attr_pb'; +import { Event_SetCardCounterSchema } from 'generated/proto/event_set_card_counter_pb'; +import { Event_CreateArrowSchema } from 'generated/proto/event_create_arrow_pb'; +import { Event_DeleteArrowSchema } from 'generated/proto/event_delete_arrow_pb'; +import { Event_CreateCounterSchema } from 'generated/proto/event_create_counter_pb'; +import { Event_SetCounterSchema } from 'generated/proto/event_set_counter_pb'; +import { Event_DelCounterSchema } from 'generated/proto/event_del_counter_pb'; +import { Event_DrawCardsSchema } from 'generated/proto/event_draw_cards_pb'; +import { Event_RevealCardsSchema } from 'generated/proto/event_reveal_cards_pb'; +import { Event_ShuffleSchema } from 'generated/proto/event_shuffle_pb'; +import { Event_RollDieSchema } from 'generated/proto/event_roll_die_pb'; +import { Event_DumpZoneSchema } from 'generated/proto/event_dump_zone_pb'; +import { Event_ChangeZonePropertiesSchema } from 'generated/proto/event_change_zone_properties_pb'; describe('Actions', () => { it('clearStore', () => { @@ -32,7 +51,9 @@ describe('Actions', () => { }); it('gameStateChanged', () => { - const data = { playerList: [], gameStarted: true, activePlayerId: 1, activePhase: 0, secondsElapsed: 0 }; + const data = create(Event_GameStateChangedSchema, { + playerList: [], gameStarted: true, activePlayerId: 1, activePhase: 0, secondsElapsed: 0 + }); expect(Actions.gameStateChanged(1, data)).toEqual({ type: Types.GAME_STATE_CHANGED, gameId: 1, data }); }); @@ -60,85 +81,85 @@ describe('Actions', () => { }); it('cardMoved', () => { - const data = { cardId: 1 } as any; + const data = create(Event_MoveCardSchema, { cardId: 1 }); expect(Actions.cardMoved(1, 2, data)).toEqual({ type: Types.CARD_MOVED, gameId: 1, playerId: 2, data }); }); it('cardFlipped', () => { - const data = { cardId: 1 } as any; + const data = create(Event_FlipCardSchema, { cardId: 1 }); expect(Actions.cardFlipped(1, 2, data)).toEqual({ type: Types.CARD_FLIPPED, gameId: 1, playerId: 2, data }); }); it('cardDestroyed', () => { - const data = { cardId: 1 } as any; + const data = create(Event_DestroyCardSchema, { cardId: 1 }); expect(Actions.cardDestroyed(1, 2, data)).toEqual({ type: Types.CARD_DESTROYED, gameId: 1, playerId: 2, data }); }); it('cardAttached', () => { - const data = { cardId: 1 } as any; + const data = create(Event_AttachCardSchema, { cardId: 1 }); expect(Actions.cardAttached(1, 2, data)).toEqual({ type: Types.CARD_ATTACHED, gameId: 1, playerId: 2, data }); }); it('tokenCreated', () => { - const data = { cardId: 1 } as any; + const data = create(Event_CreateTokenSchema, { cardId: 1 }); expect(Actions.tokenCreated(1, 2, data)).toEqual({ type: Types.TOKEN_CREATED, gameId: 1, playerId: 2, data }); }); it('cardAttrChanged', () => { - const data = { cardId: 1 } as any; + const data = create(Event_SetCardAttrSchema, { cardId: 1 }); expect(Actions.cardAttrChanged(1, 2, data)).toEqual({ type: Types.CARD_ATTR_CHANGED, gameId: 1, playerId: 2, data }); }); it('cardCounterChanged', () => { - const data = { cardId: 1 } as any; + const data = create(Event_SetCardCounterSchema, { cardId: 1 }); expect(Actions.cardCounterChanged(1, 2, data)).toEqual({ type: Types.CARD_COUNTER_CHANGED, gameId: 1, playerId: 2, data }); }); it('arrowCreated', () => { const arrow = makeArrow(); - const data = { arrowInfo: arrow }; + const data = create(Event_CreateArrowSchema, { arrowInfo: arrow }); expect(Actions.arrowCreated(1, 2, data)).toEqual({ type: Types.ARROW_CREATED, gameId: 1, playerId: 2, data }); }); it('arrowDeleted', () => { - const data = { arrowId: 3 }; + const data = create(Event_DeleteArrowSchema, { arrowId: 3 }); expect(Actions.arrowDeleted(1, 2, data)).toEqual({ type: Types.ARROW_DELETED, gameId: 1, playerId: 2, data }); }); it('counterCreated', () => { const counter = makeCounter(); - const data = { counterInfo: counter }; + const data = create(Event_CreateCounterSchema, { counterInfo: counter }); expect(Actions.counterCreated(1, 2, data)).toEqual({ type: Types.COUNTER_CREATED, gameId: 1, playerId: 2, data }); }); it('counterSet', () => { - const data = { counterId: 1, value: 10 }; + const data = create(Event_SetCounterSchema, { counterId: 1, value: 10 }); expect(Actions.counterSet(1, 2, data)).toEqual({ type: Types.COUNTER_SET, gameId: 1, playerId: 2, data }); }); it('counterDeleted', () => { - const data = { counterId: 1 }; + const data = create(Event_DelCounterSchema, { counterId: 1 }); expect(Actions.counterDeleted(1, 2, data)).toEqual({ type: Types.COUNTER_DELETED, gameId: 1, playerId: 2, data }); }); it('cardsDrawn', () => { const card = makeCard(); - const data = { number: 2, cards: [card] }; + const data = create(Event_DrawCardsSchema, { number: 2, cards: [card] }); expect(Actions.cardsDrawn(1, 2, data)).toEqual({ type: Types.CARDS_DRAWN, gameId: 1, playerId: 2, data }); }); it('cardsRevealed', () => { - const data = { zoneName: 'hand', cards: [] } as any; + const data = create(Event_RevealCardsSchema, { zoneName: 'hand', cards: [] }); expect(Actions.cardsRevealed(1, 2, data)).toEqual({ type: Types.CARDS_REVEALED, gameId: 1, playerId: 2, data }); }); it('zoneShuffled', () => { - const data = { zoneName: 'deck', start: 0, end: 39 }; + const data = create(Event_ShuffleSchema, { zoneName: 'deck', start: 0, end: 39 }); expect(Actions.zoneShuffled(1, 2, data)).toEqual({ type: Types.ZONE_SHUFFLED, gameId: 1, playerId: 2, data }); }); it('dieRolled', () => { - const data = { sides: 6, value: 4, values: [4] }; + const data = create(Event_RollDieSchema, { sides: 6, value: 4, values: [4] }); expect(Actions.dieRolled(1, 2, data)).toEqual({ type: Types.DIE_ROLLED, gameId: 1, playerId: 2, data }); }); @@ -155,12 +176,12 @@ describe('Actions', () => { }); it('zoneDumped', () => { - const data = { zoneOwnerId: 1, zoneName: 'hand', numberCards: 3, isReversed: false }; + const data = create(Event_DumpZoneSchema, { zoneOwnerId: 1, zoneName: 'hand', numberCards: 3, isReversed: false }); expect(Actions.zoneDumped(1, 2, data)).toEqual({ type: Types.ZONE_DUMPED, gameId: 1, playerId: 2, data }); }); it('zonePropertiesChanged', () => { - const data = { zoneName: 'deck', alwaysRevealTopCard: true, alwaysLookAtTopCard: false }; + const data = create(Event_ChangeZonePropertiesSchema, { zoneName: 'deck', alwaysRevealTopCard: true, alwaysLookAtTopCard: false }); expect(Actions.zonePropertiesChanged(1, 2, data)).toEqual({ type: Types.ZONE_PROPERTIES_CHANGED, gameId: 1, diff --git a/webclient/src/store/game/game.actions.ts b/webclient/src/store/game/game.actions.ts index 3ae9950a6..977207205 100644 --- a/webclient/src/store/game/game.actions.ts +++ b/webclient/src/store/game/game.actions.ts @@ -232,3 +232,5 @@ export const Actions = { message, }), }; + +export type GameAction = ReturnType; diff --git a/webclient/src/store/game/game.dispatch.spec.ts b/webclient/src/store/game/game.dispatch.spec.ts index 4c5ca0701..77c6e0227 100644 --- a/webclient/src/store/game/game.dispatch.spec.ts +++ b/webclient/src/store/game/game.dispatch.spec.ts @@ -1,5 +1,6 @@ vi.mock('store/store', () => ({ store: { dispatch: vi.fn() } })); +import { create } from '@bufbuild/protobuf'; import { store } from 'store/store'; import { Actions } from './game.actions'; import { Dispatch } from './game.dispatch'; @@ -10,6 +11,25 @@ import { makeGameEntry, makePlayerProperties, } from './__mocks__/fixtures'; +import { Event_GameStateChangedSchema } from 'generated/proto/event_game_state_changed_pb'; +import { Event_MoveCardSchema } from 'generated/proto/event_move_card_pb'; +import { Event_FlipCardSchema } from 'generated/proto/event_flip_card_pb'; +import { Event_DestroyCardSchema } from 'generated/proto/event_destroy_card_pb'; +import { Event_AttachCardSchema } from 'generated/proto/event_attach_card_pb'; +import { Event_CreateTokenSchema } from 'generated/proto/event_create_token_pb'; +import { Event_SetCardAttrSchema } from 'generated/proto/event_set_card_attr_pb'; +import { Event_SetCardCounterSchema } from 'generated/proto/event_set_card_counter_pb'; +import { Event_CreateArrowSchema } from 'generated/proto/event_create_arrow_pb'; +import { Event_DeleteArrowSchema } from 'generated/proto/event_delete_arrow_pb'; +import { Event_CreateCounterSchema } from 'generated/proto/event_create_counter_pb'; +import { Event_SetCounterSchema } from 'generated/proto/event_set_counter_pb'; +import { Event_DelCounterSchema } from 'generated/proto/event_del_counter_pb'; +import { Event_DrawCardsSchema } from 'generated/proto/event_draw_cards_pb'; +import { Event_RevealCardsSchema } from 'generated/proto/event_reveal_cards_pb'; +import { Event_ShuffleSchema } from 'generated/proto/event_shuffle_pb'; +import { Event_RollDieSchema } from 'generated/proto/event_roll_die_pb'; +import { Event_DumpZoneSchema } from 'generated/proto/event_dump_zone_pb'; +import { Event_ChangeZonePropertiesSchema } from 'generated/proto/event_change_zone_properties_pb'; beforeEach(() => vi.clearAllMocks()); @@ -41,7 +61,9 @@ describe('Dispatch', () => { }); it('gameStateChanged dispatches Actions.gameStateChanged()', () => { - const data = { playerList: [], gameStarted: false, activePlayerId: 0, activePhase: 0, secondsElapsed: 0 }; + const data = create(Event_GameStateChangedSchema, { + playerList: [], gameStarted: false, activePlayerId: 0, activePhase: 0, secondsElapsed: 0 + }); Dispatch.gameStateChanged(1, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.gameStateChanged(1, data)); }); @@ -69,97 +91,97 @@ describe('Dispatch', () => { }); it('cardMoved dispatches Actions.cardMoved()', () => { - const data = { cardId: 1 } as any; + const data = create(Event_MoveCardSchema, { cardId: 1 }); Dispatch.cardMoved(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.cardMoved(1, 2, data)); }); it('cardFlipped dispatches Actions.cardFlipped()', () => { - const data = { cardId: 1 } as any; + const data = create(Event_FlipCardSchema, { cardId: 1 }); Dispatch.cardFlipped(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.cardFlipped(1, 2, data)); }); it('cardDestroyed dispatches Actions.cardDestroyed()', () => { - const data = { cardId: 1 } as any; + const data = create(Event_DestroyCardSchema, { cardId: 1 }); Dispatch.cardDestroyed(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.cardDestroyed(1, 2, data)); }); it('cardAttached dispatches Actions.cardAttached()', () => { - const data = { cardId: 1 } as any; + const data = create(Event_AttachCardSchema, { cardId: 1 }); Dispatch.cardAttached(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.cardAttached(1, 2, data)); }); it('tokenCreated dispatches Actions.tokenCreated()', () => { - const data = { cardId: 1 } as any; + const data = create(Event_CreateTokenSchema, { cardId: 1 }); Dispatch.tokenCreated(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.tokenCreated(1, 2, data)); }); it('cardAttrChanged dispatches Actions.cardAttrChanged()', () => { - const data = { cardId: 1 } as any; + const data = create(Event_SetCardAttrSchema, { cardId: 1 }); Dispatch.cardAttrChanged(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.cardAttrChanged(1, 2, data)); }); it('cardCounterChanged dispatches Actions.cardCounterChanged()', () => { - const data = { cardId: 1 } as any; + const data = create(Event_SetCardCounterSchema, { cardId: 1 }); Dispatch.cardCounterChanged(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.cardCounterChanged(1, 2, data)); }); it('arrowCreated dispatches Actions.arrowCreated()', () => { - const data = { arrowInfo: makeArrow() }; + const data = create(Event_CreateArrowSchema, { arrowInfo: makeArrow() }); Dispatch.arrowCreated(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.arrowCreated(1, 2, data)); }); it('arrowDeleted dispatches Actions.arrowDeleted()', () => { - const data = { arrowId: 3 }; + const data = create(Event_DeleteArrowSchema, { arrowId: 3 }); Dispatch.arrowDeleted(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.arrowDeleted(1, 2, data)); }); it('counterCreated dispatches Actions.counterCreated()', () => { - const data = { counterInfo: makeCounter() }; + const data = create(Event_CreateCounterSchema, { counterInfo: makeCounter() }); Dispatch.counterCreated(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.counterCreated(1, 2, data)); }); it('counterSet dispatches Actions.counterSet()', () => { - const data = { counterId: 1, value: 10 }; + const data = create(Event_SetCounterSchema, { counterId: 1, value: 10 }); Dispatch.counterSet(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.counterSet(1, 2, data)); }); it('counterDeleted dispatches Actions.counterDeleted()', () => { - const data = { counterId: 1 }; + const data = create(Event_DelCounterSchema, { counterId: 1 }); Dispatch.counterDeleted(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.counterDeleted(1, 2, data)); }); it('cardsDrawn dispatches Actions.cardsDrawn()', () => { - const data = { number: 2, cards: [makeCard()] }; + const data = create(Event_DrawCardsSchema, { number: 2, cards: [makeCard()] }); Dispatch.cardsDrawn(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.cardsDrawn(1, 2, data)); }); it('cardsRevealed dispatches Actions.cardsRevealed()', () => { - const data = { zoneName: 'hand', cards: [] } as any; + const data = create(Event_RevealCardsSchema, { zoneName: 'hand', cards: [] }); Dispatch.cardsRevealed(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.cardsRevealed(1, 2, data)); }); it('zoneShuffled dispatches Actions.zoneShuffled()', () => { - const data = { zoneName: 'deck', start: 0, end: 39 }; + const data = create(Event_ShuffleSchema, { zoneName: 'deck', start: 0, end: 39 }); Dispatch.zoneShuffled(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.zoneShuffled(1, 2, data)); }); it('dieRolled dispatches Actions.dieRolled()', () => { - const data = { sides: 6, value: 4, values: [4] }; + const data = create(Event_RollDieSchema, { sides: 6, value: 4, values: [4] }); Dispatch.dieRolled(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.dieRolled(1, 2, data)); }); @@ -180,13 +202,13 @@ describe('Dispatch', () => { }); it('zoneDumped dispatches Actions.zoneDumped()', () => { - const data = { zoneOwnerId: 1, zoneName: 'hand', numberCards: 3, isReversed: false }; + const data = create(Event_DumpZoneSchema, { zoneOwnerId: 1, zoneName: 'hand', numberCards: 3, isReversed: false }); Dispatch.zoneDumped(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.zoneDumped(1, 2, data)); }); it('zonePropertiesChanged dispatches Actions.zonePropertiesChanged()', () => { - const data = { zoneName: 'deck', alwaysRevealTopCard: true, alwaysLookAtTopCard: false }; + const data = create(Event_ChangeZonePropertiesSchema, { zoneName: 'deck', alwaysRevealTopCard: true, alwaysLookAtTopCard: false }); Dispatch.zonePropertiesChanged(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.zonePropertiesChanged(1, 2, data)); }); diff --git a/webclient/src/store/game/game.reducer.spec.ts b/webclient/src/store/game/game.reducer.spec.ts index 82ea025d6..6435e7bd1 100644 --- a/webclient/src/store/game/game.reducer.spec.ts +++ b/webclient/src/store/game/game.reducer.spec.ts @@ -1,3 +1,4 @@ +import { create } from '@bufbuild/protobuf'; import { CardAttribute, PlayerInfo } from 'types'; import { gamesReducer } from './game.reducer'; import { Types } from './game.types'; @@ -11,6 +12,7 @@ import { makeState, makeZoneEntry, } from './__mocks__/fixtures'; +import { ServerInfo_PlayerSchema } from 'generated/proto/serverinfo_player_pb'; // ── 2A: Initialisation & lifecycle ─────────────────────────────────────────── @@ -67,7 +69,7 @@ describe('2B: Game state & player management', () => { const counter = makeCounter({ id: 2 }); const arrow = makeArrow({ id: 3 }); const playerList: PlayerInfo[] = [ - { + create(ServerInfo_PlayerSchema, { properties: makePlayerProperties({ playerId: 7 }), deckList: 'some deck', zoneList: [ @@ -83,7 +85,7 @@ describe('2B: Game state & player management', () => { ], counterList: [counter], arrowList: [arrow], - }, + }), ]; const result = gamesReducer(state, { @@ -620,7 +622,7 @@ describe('2F: CARD_COUNTER_CHANGED', () => { playerId: 1, data: { zoneName: 'table', cardId: 4, counterId: 1, counterValue: 3 }, }); - expect(result.games[1].players[1].zones['table'].cards[0].counterList).toEqual([{ id: 1, value: 3 }]); + expect(result.games[1].players[1].zones['table'].cards[0].counterList).toEqual([expect.objectContaining({ id: 1, value: 3 })]); }); it('updates existing counter value when counterId matches', () => { @@ -631,7 +633,7 @@ describe('2F: CARD_COUNTER_CHANGED', () => { playerId: 1, data: { zoneName: 'table', cardId: 4, counterId: 1, counterValue: 7 }, }); - expect(result.games[1].players[1].zones['table'].cards[0].counterList).toEqual([{ id: 1, value: 7 }]); + expect(result.games[1].players[1].zones['table'].cards[0].counterList).toEqual([expect.objectContaining({ id: 1, value: 7 })]); }); it('removes counter from counterList when counterValue ≤ 0', () => { diff --git a/webclient/src/store/game/game.reducer.ts b/webclient/src/store/game/game.reducer.ts index b6148fb4f..1f164210c 100644 --- a/webclient/src/store/game/game.reducer.ts +++ b/webclient/src/store/game/game.reducer.ts @@ -7,6 +7,10 @@ import { PlayerInfo, PlayerProperties, } from 'types'; +import { create } from '@bufbuild/protobuf'; +import { ServerInfo_CardSchema } from 'generated/proto/serverinfo_card_pb'; +import { ServerInfo_CardCounterSchema } from 'generated/proto/serverinfo_cardcounter_pb'; +import { GameAction } from './game.actions'; import { GameEntry, GameMessage, GamesState, PlayerEntry, ZoneEntry } from './game.interfaces'; import { Types } from './game.types'; @@ -120,7 +124,7 @@ function buildEmptyCard( faceDown: boolean, providerId: string ): CardInfo { - return { + return create(ServerInfo_CardSchema, { id, name, x, @@ -138,7 +142,7 @@ function buildEmptyCard( attachZone: '', attachCardId: -1, providerId, - }; + }); } // ── Initial state ───────────────────────────────────────────────────────────── @@ -149,7 +153,7 @@ const initialState: GamesState = { // ── Reducer ─────────────────────────────────────────────────────────────────── -export const gamesReducer = (state: GamesState = initialState, action: any): GamesState => { +export const gamesReducer = (state: GamesState = initialState, action: GameAction): GamesState => { switch (action.type) { case Types.CLEAR_STORE: { return initialState; @@ -422,7 +426,7 @@ export const gamesReducer = (state: GamesState = initialState, action: any): Gam return state; } - const newCard: CardInfo = { + const newCard: CardInfo = create(ServerInfo_CardSchema, { id: cardId, name: cardName, x, @@ -440,7 +444,7 @@ export const gamesReducer = (state: GamesState = initialState, action: any): Gam attachZone: '', attachCardId: -1, providerId: cardProviderId, - }; + }); return updateZone(state, gameId, playerId, zoneName, { cards: [...zone.cards, newCard], cardCount: zone.cardCount + 1, @@ -514,7 +518,7 @@ export const gamesReducer = (state: GamesState = initialState, action: any): Gam newCounterList = existing >= 0 ? card.counterList.map(c => (c.id === counterId ? { ...c, value: counterValue } : c)) - : [...card.counterList, { id: counterId, value: counterValue }]; + : [...card.counterList, create(ServerInfo_CardCounterSchema, { id: counterId, value: counterValue })]; } const updatedCards = [...zone.cards]; diff --git a/webclient/src/store/game/game.selectors.spec.ts b/webclient/src/store/game/game.selectors.spec.ts index de38ef580..4bb064dff 100644 --- a/webclient/src/store/game/game.selectors.spec.ts +++ b/webclient/src/store/game/game.selectors.spec.ts @@ -1,6 +1,5 @@ import { Selectors } from './game.selectors'; -import { - makeGameEntry, makePlayerEntry, makePlayerProperties, makeState, +import { makeGameEntry, makePlayerEntry, makeState, makeZoneEntry, makeCard, makeCounter, makeArrow, } from './__mocks__/fixtures'; import { GamesState } from './game.interfaces'; diff --git a/webclient/src/store/game/game.selectors.ts b/webclient/src/store/game/game.selectors.ts index 2de0cb1d1..1e9b55e70 100644 --- a/webclient/src/store/game/game.selectors.ts +++ b/webclient/src/store/game/game.selectors.ts @@ -1,9 +1,14 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { CardInfo } from 'types'; import { GamesState, GameEntry, PlayerEntry, ZoneEntry } from './game.interfaces'; interface State { games: GamesState; } +const EMPTY_ARRAY: CardInfo[] = []; +const EMPTY_OBJECT = {} as Record; + export const Selectors = { getGames: ({ games }: State): { [gameId: number]: GameEntry } => games.games, @@ -41,13 +46,13 @@ export const Selectors = { ): ZoneEntry | undefined => games.games[gameId]?.players[playerId]?.zones[zoneName], getCards: ({ games }: State, gameId: number, playerId: number, zoneName: string) => - games.games[gameId]?.players[playerId]?.zones[zoneName]?.cards ?? [], + games.games[gameId]?.players[playerId]?.zones[zoneName]?.cards ?? EMPTY_ARRAY, getCounters: ({ games }: State, gameId: number, playerId: number) => - games.games[gameId]?.players[playerId]?.counters ?? {}, + games.games[gameId]?.players[playerId]?.counters ?? EMPTY_OBJECT, getArrows: ({ games }: State, gameId: number, playerId: number) => - games.games[gameId]?.players[playerId]?.arrows ?? {}, + games.games[gameId]?.players[playerId]?.arrows ?? EMPTY_OBJECT, getActivePlayerId: ({ games }: State, gameId: number): number | undefined => games.games[gameId]?.activePlayerId, @@ -65,8 +70,10 @@ export const Selectors = { games.games[gameId]?.reversed ?? false, getMessages: ({ games }: State, gameId: number) => - games.games[gameId]?.messages ?? [], + games.games[gameId]?.messages ?? EMPTY_ARRAY, - getActiveGameIds: ({ games }: State): number[] => - Object.keys(games.games).map(Number), + getActiveGameIds: createSelector( + [({ games }: State) => games.games], + (games) => Object.keys(games).map(Number) + ), }; diff --git a/webclient/src/store/game/game.types.ts b/webclient/src/store/game/game.types.ts index 19ed016b7..7b3a8d39d 100644 --- a/webclient/src/store/game/game.types.ts +++ b/webclient/src/store/game/game.types.ts @@ -31,4 +31,4 @@ export const Types = { ZONE_DUMPED: '[Games] Zone Dumped', ZONE_PROPERTIES_CHANGED: '[Games] Zone Properties Changed', GAME_SAY: '[Games] Game Say', -}; +} as const; diff --git a/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts b/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts index 27bc5a391..d0dc23bab 100644 --- a/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts +++ b/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts @@ -1,52 +1,63 @@ import { Game, GameSortField, + Message, + ProtoInit, Room, SortDirection, User, UserSortField, } from 'types'; -import { Message, RoomsState } from '../rooms.interfaces'; +import { create } from '@bufbuild/protobuf'; +import { ServerInfo_UserSchema } from 'generated/proto/serverinfo_user_pb'; +import { ServerInfo_GameSchema } from 'generated/proto/serverinfo_game_pb'; +import { ServerInfo_RoomSchema } from 'generated/proto/serverinfo_room_pb'; +import { RoomsState } from '../rooms.interfaces'; -export function makeUser(overrides: Partial = {}): User { - return { +export function makeUser(overrides: ProtoInit = {}): User { + return create(ServerInfo_UserSchema, { name: 'TestUser', accountageSecs: 0n, privlevel: '', userLevel: 0, ...overrides, + }); +} + +export function makeRoom(overrides: ProtoInit = {}): Room { + const { gametypeMap = {}, order = 0, gameList = [], ...protoOverrides } = overrides; + return { + ...create(ServerInfo_RoomSchema, { + roomId: 1, + name: 'Test Room', + description: '', + gameCount: 0, + gameList: [], + gametypeList: [], + autoJoin: false, + playerCount: 0, + userList: [], + ...protoOverrides, + }), + gameList, + gametypeMap, + order, }; } -export function makeRoom(overrides: Partial = {}): Room { +export function makeGame(overrides: ProtoInit = {}): Game & { startTime: number } { + const { gameType = '', startTime = 0, ...protoOverrides } = overrides; return { - roomId: 1, - name: 'Test Room', - description: '', - gameCount: 0, - gameList: [], - gametypeList: [], - gametypeMap: {}, - autoJoin: false, - permissionlevel: 0 as any, - playerCount: 0, - privilegelevel: 0 as any, - userList: [], - order: 0, - ...overrides, - }; -} - -export function makeGame(overrides: Partial = {}): Game & { startTime: number } { - return { - gameId: 1, - roomId: 1, - description: 'Test Game', - gameType: '', - gameTypes: [], - started: false, - startTime: 0, - ...overrides, + ...create(ServerInfo_GameSchema, { + gameId: 1, + roomId: 1, + description: 'Test Game', + gameTypes: [], + started: false, + ...protoOverrides, + }), + gameType, + startTime, }; } diff --git a/webclient/src/store/rooms/rooms.actions.tsx b/webclient/src/store/rooms/rooms.actions.tsx index 98108fe8b..18106ffd7 100644 --- a/webclient/src/store/rooms/rooms.actions.tsx +++ b/webclient/src/store/rooms/rooms.actions.tsx @@ -1,71 +1,77 @@ +import { GameSortField, Message, SortDirection, User } from 'types'; +import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; +import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; + import { Types } from './rooms.types'; export const Actions = { clearStore: () => ({ - type: Types.CLEAR_STORE + type: Types.CLEAR_STORE, }), - updateRooms: rooms => ({ + updateRooms: (rooms: ServerInfo_Room[]) => ({ type: Types.UPDATE_ROOMS, - rooms + rooms, }), - joinRoom: roomInfo => ({ + joinRoom: (roomInfo: ServerInfo_Room) => ({ type: Types.JOIN_ROOM, - roomInfo + roomInfo, }), - leaveRoom: roomId => ({ + leaveRoom: (roomId: number) => ({ type: Types.LEAVE_ROOM, - roomId + roomId, }), - addMessage: (roomId, message) => ({ + addMessage: (roomId: number, message: Message) => ({ type: Types.ADD_MESSAGE, roomId, - message + message, }), - updateGames: (roomId, games) => ({ + updateGames: (roomId: number, games: ServerInfo_Game[]) => ({ type: Types.UPDATE_GAMES, roomId, - games + games, }), - userJoined: (roomId, user) => ({ + userJoined: (roomId: number, user: User) => ({ type: Types.USER_JOINED, roomId, - user + user, }), - userLeft: (roomId, name) => ({ + userLeft: (roomId: number, name: string) => ({ type: Types.USER_LEFT, roomId, - name + name, }), - sortGames: (roomId, field, order) => ({ + sortGames: (roomId: number, field: GameSortField, order: SortDirection) => ({ type: Types.SORT_GAMES, roomId, field, - order + order, }), - removeMessages: (roomId, name, amount) => ({ + removeMessages: (roomId: number, name: string, amount: number) => ({ type: Types.REMOVE_MESSAGES, roomId, name, - amount + amount, }), - gameCreated: (roomId) => ({ + gameCreated: (roomId: number) => ({ type: Types.GAME_CREATED, - roomId + roomId, }), - joinedGame: (roomId, gameId) => ({ + joinedGame: (roomId: number, gameId: number) => ({ type: Types.JOINED_GAME, roomId, - gameId + gameId, }), } + +export type RoomsAction = ReturnType; diff --git a/webclient/src/store/rooms/rooms.dispatch.spec.ts b/webclient/src/store/rooms/rooms.dispatch.spec.ts index 82ac93f99..ff090b36c 100644 --- a/webclient/src/store/rooms/rooms.dispatch.spec.ts +++ b/webclient/src/store/rooms/rooms.dispatch.spec.ts @@ -1,10 +1,6 @@ -vi.mock('store/store', () => ({ store: { dispatch: vi.fn() } })); -vi.mock('redux-form', () => ({ - reset: vi.fn((form) => ({ type: '@@redux-form/RESET', meta: { form } })), -})); +vi.mock('store', () => ({ store: { dispatch: vi.fn() } })); -import { store } from 'store/store'; -import { reset } from 'redux-form'; +import { store } from 'store'; import { Actions } from './rooms.actions'; import { Dispatch } from './rooms.dispatch'; import { makeGame, makeMessage, makeRoom, makeUser } from './__mocks__/rooms-fixtures'; @@ -42,11 +38,11 @@ describe('Dispatch', () => { expect(store.dispatch).toHaveBeenCalledWith(Actions.addMessage(1, message)); }); - it('addMessage with message.name truthy → dispatches reset("sayMessage") then Actions.addMessage()', () => { + it('addMessage with message.name truthy → dispatches Actions.addMessage()', () => { const message = { ...makeMessage(), name: 'Alice' }; Dispatch.addMessage(1, message); - expect(store.dispatch).toHaveBeenNthCalledWith(1, (reset as vi.Mock)('sayMessage')); - expect(store.dispatch).toHaveBeenNthCalledWith(2, Actions.addMessage(1, message)); + expect(store.dispatch).toHaveBeenCalledTimes(1); + expect(store.dispatch).toHaveBeenCalledWith(Actions.addMessage(1, message)); }); it('updateGames dispatches Actions.updateGames()', () => { diff --git a/webclient/src/store/rooms/rooms.dispatch.tsx b/webclient/src/store/rooms/rooms.dispatch.tsx index c89b5ebc4..969aeaff9 100644 --- a/webclient/src/store/rooms/rooms.dispatch.tsx +++ b/webclient/src/store/rooms/rooms.dispatch.tsx @@ -1,4 +1,7 @@ -import { reset } from 'redux-form'; +import { GameSortField, Message, SortDirection, User } from 'types'; +import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; +import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; + import { Actions } from './rooms.actions'; import { store } from 'store'; @@ -7,52 +10,48 @@ export const Dispatch = { store.dispatch(Actions.clearStore()); }, - updateRooms: rooms => { + updateRooms: (rooms: ServerInfo_Room[]) => { store.dispatch(Actions.updateRooms(rooms)); }, - joinRoom: roomInfo => { + joinRoom: (roomInfo: ServerInfo_Room) => { store.dispatch(Actions.joinRoom(roomInfo)); }, - leaveRoom: roomId => { + leaveRoom: (roomId: number) => { store.dispatch(Actions.leaveRoom(roomId)); }, - addMessage: (roomId, message) => { - if (message.name) { - store.dispatch(reset('sayMessage')); - } - + addMessage: (roomId: number, message: Message) => { store.dispatch(Actions.addMessage(roomId, message)); }, - updateGames: (roomId, games) => { + updateGames: (roomId: number, games: ServerInfo_Game[]) => { store.dispatch(Actions.updateGames(roomId, games)); }, - userJoined: (roomId, user) => { + userJoined: (roomId: number, user: User) => { store.dispatch(Actions.userJoined(roomId, user)); }, - userLeft: (roomId, name) => { + userLeft: (roomId: number, name: string) => { store.dispatch(Actions.userLeft(roomId, name)); }, - sortGames: (roomId, field, order) => { + sortGames: (roomId: number, field: GameSortField, order: SortDirection) => { store.dispatch(Actions.sortGames(roomId, field, order)); }, - removeMessages: (roomId, name, amount) => { + removeMessages: (roomId: number, name: string, amount: number) => { store.dispatch(Actions.removeMessages(roomId, name, amount)); }, - gameCreated: (roomId) => { + gameCreated: (roomId: number) => { store.dispatch(Actions.gameCreated(roomId)); }, - joinedGame: (roomId, gameId) => { + joinedGame: (roomId: number, gameId: number) => { store.dispatch(Actions.joinedGame(roomId, gameId)); } } diff --git a/webclient/src/store/rooms/rooms.interfaces.tsx b/webclient/src/store/rooms/rooms.interfaces.tsx index c7b90ac84..8e9cf1943 100644 --- a/webclient/src/store/rooms/rooms.interfaces.tsx +++ b/webclient/src/store/rooms/rooms.interfaces.tsx @@ -1,4 +1,4 @@ -import { GameSortField, Room, Game, SortBy, UserSortField } from 'types'; +import { GameSortField, Message, Room, Game, SortBy, UserSortField } from 'types'; export interface RoomsState { rooms: RoomsStateRooms; @@ -41,10 +41,3 @@ export interface RoomsStateSortGamesBy extends SortBy { export interface RoomsStateSortUsersBy extends SortBy { field: UserSortField } - -export interface Message { - message: string; - messageType: number; - timeReceived: number; - timeOf?: number; -} diff --git a/webclient/src/store/rooms/rooms.reducer.spec.ts b/webclient/src/store/rooms/rooms.reducer.spec.ts index 2555a12f2..df0ef2836 100644 --- a/webclient/src/store/rooms/rooms.reducer.spec.ts +++ b/webclient/src/store/rooms/rooms.reducer.spec.ts @@ -126,6 +126,20 @@ describe('ADD_MESSAGE', () => { expect(result.messages[1][0].message).not.toBe('first'); expect(result.messages[1][MAX_ROOM_MESSAGES - 1].message).toBe('new'); }); + + it('prepends "name: " to message when name is present', () => { + const state = makeRoomsState({ messages: { 1: [] } }); + const message = makeMessage({ name: 'Alice', message: 'hello' }); + const result = roomsReducer(state, { type: Types.ADD_MESSAGE, roomId: 1, message }); + expect(result.messages[1][0].message).toBe('Alice: hello'); + }); + + it('does not prepend when name is empty', () => { + const state = makeRoomsState({ messages: { 1: [] } }); + const message = makeMessage({ name: '', message: 'system msg' }); + const result = roomsReducer(state, { type: Types.ADD_MESSAGE, roomId: 1, message }); + expect(result.messages[1][0].message).toBe('system msg'); + }); }); // ── UPDATE_GAMES ────────────────────────────────────────────────────────────── @@ -267,6 +281,16 @@ describe('REMOVE_MESSAGES', () => { }); }); +// ── GAME_CREATED ────────────────────────────────────────────────────────────── + +describe('GAME_CREATED', () => { + it('returns state unchanged', () => { + const state = makeRoomsState(); + const result = roomsReducer(state, { type: Types.GAME_CREATED, roomId: 1 }); + expect(result).toBe(state); + }); +}); + // ── JOINED_GAME ─────────────────────────────────────────────────────────────── describe('JOINED_GAME', () => { diff --git a/webclient/src/store/rooms/rooms.reducer.tsx b/webclient/src/store/rooms/rooms.reducer.tsx index 5898c7150..bbf5a1ed5 100644 --- a/webclient/src/store/rooms/rooms.reducer.tsx +++ b/webclient/src/store/rooms/rooms.reducer.tsx @@ -1,9 +1,10 @@ import * as _ from 'lodash'; -import { GameSortField, UserSortField, SortDirection } from 'types'; +import { GameSortField, Room, UserSortField, SortDirection } from 'types'; -import { SortUtil } from '../common'; +import { normalizeGameObject, normalizeGametypeMap, normalizeRoomInfo, normalizeUserMessage, SortUtil } from '../common'; +import { RoomsAction } from './rooms.actions'; import { RoomsState } from './rooms.interfaces' import { MAX_ROOM_MESSAGES, Types } from './rooms.types'; @@ -23,7 +24,7 @@ const initialState: RoomsState = { } }; -export const roomsReducer = (state = initialState, action: any) => { +export const roomsReducer = (state = initialState, action: RoomsAction) => { switch (action.type) { case Types.CLEAR_STORE: { return { @@ -36,20 +37,21 @@ export const roomsReducer = (state = initialState, action: any) => { ...state.rooms }; - // Server does not send everything on updates - _.each(action.rooms, (room, order) => { - const { roomId } = room; + // Server does not send everything on updates — preserve existing gameList/userList + _.each(action.rooms, (rawRoom, order) => { + const { gameList: _g, gametypeList, userList: _u, ...roomMeta } = rawRoom; + const { roomId } = roomMeta; const existing = rooms[roomId] || {}; - const update = { ...room }; - delete update.gameList; - delete update.gametypeList; - delete update.userList; + const gametypeMap = normalizeGametypeMap(gametypeList); rooms[roomId] = { - ...existing, - ...update, - order + ...(existing as Room), + ...roomMeta, + gametypeMap, + gameList: (existing as Room).gameList, + userList: (existing as Room).userList, + order, }; }); @@ -57,9 +59,10 @@ export const roomsReducer = (state = initialState, action: any) => { } case Types.JOIN_ROOM: { - const { roomInfo } = action; + const { roomInfo: rawRoomInfo } = action; const { joinedRoomIds, rooms, sortGamesBy, sortUsersBy } = state; + const roomInfo = normalizeRoomInfo(rawRoomInfo); const { roomId } = roomInfo; const gameList = [ @@ -125,8 +128,8 @@ export const roomsReducer = (state = initialState, action: any) => { roomMessages.shift(); } - message.timeReceived = new Date().getTime(); - roomMessages.push(message); + const normalized = normalizeUserMessage({ ...message, timeReceived: Date.now() }); + roomMessages.push(normalized); return { ...state, @@ -150,8 +153,12 @@ export const roomsReducer = (state = initialState, action: any) => { return { ...state }; } + // Normalize incoming raw proto games using the room's gametypeMap + const gametypeMap = room.gametypeMap ?? {}; + const normalizedGames = games.map(g => normalizeGameObject(g, gametypeMap)); + // Create map of games with update objects - const toUpdate = games.reduce((map, game) => { + const toUpdate = normalizedGames.reduce((map, game) => { map[game.gameId] = game; return map; }, {}); @@ -320,6 +327,10 @@ export const roomsReducer = (state = initialState, action: any) => { } } + // Signal-only — no state mutation needed; explicit for discriminated-union exhaustiveness + case Types.GAME_CREATED: + return state; + default: return state; } diff --git a/webclient/src/store/rooms/rooms.selectors.spec.ts b/webclient/src/store/rooms/rooms.selectors.spec.ts index f8c6ef4cc..72b52f4e7 100644 --- a/webclient/src/store/rooms/rooms.selectors.spec.ts +++ b/webclient/src/store/rooms/rooms.selectors.spec.ts @@ -92,16 +92,14 @@ describe('Selectors', () => { }); it('getRoomGames → returns gameList for roomId', () => { - const games = [makeGame()]; - const room = makeRoom({ roomId: 1, gameList: games }); + const room = makeRoom({ roomId: 1, gameList: [makeGame()] }); const state = makeRoomsState({ rooms: { 1: room } }); - expect(Selectors.getRoomGames(rootState(state), 1)).toBe(games); + expect(Selectors.getRoomGames(rootState(state), 1)).toBe(room.gameList); }); it('getRoomUsers → returns userList for roomId', () => { - const users = [makeUser()]; - const room = makeRoom({ roomId: 1, userList: users }); + const room = makeRoom({ roomId: 1, userList: [makeUser()] }); const state = makeRoomsState({ rooms: { 1: room } }); - expect(Selectors.getRoomUsers(rootState(state), 1)).toBe(users); + expect(Selectors.getRoomUsers(rootState(state), 1)).toBe(room.userList); }); }); diff --git a/webclient/src/store/rooms/rooms.selectors.tsx b/webclient/src/store/rooms/rooms.selectors.tsx index e1e7ec818..8a8feb4a3 100644 --- a/webclient/src/store/rooms/rooms.selectors.tsx +++ b/webclient/src/store/rooms/rooms.selectors.tsx @@ -1,4 +1,5 @@ import * as _ from 'lodash'; +import { createSelector } from '@reduxjs/toolkit'; import { RoomsState } from './rooms.interfaces'; interface State { @@ -16,15 +17,15 @@ export const Selectors = { getSortGamesBy: ({ rooms: { sortGamesBy } }: State) => sortGamesBy, getSortUsersBy: ({ rooms: { sortUsersBy } }: State) => sortUsersBy, - getJoinedRooms: (state: State) => { - const joined = Selectors.getJoinedRoomIds(state); - return _.filter(Selectors.getRooms(state), room => joined[room.roomId]); - }, + getJoinedRooms: createSelector( + [(state: State) => state.rooms.rooms, (state: State) => state.rooms.joinedRoomIds], + (rooms, joined) => _.filter(rooms, room => joined[room.roomId]) + ), - getJoinedGames: (state: State, roomId: number) => { - const joined = Selectors.getJoinedGameIds(state)[roomId]; - return _.filter(Selectors.getGames(state)[roomId], game => joined[game.gameId]); - }, + getJoinedGames: createSelector( + [(state: State, roomId: number) => state.rooms.games[roomId], (state: State, roomId: number) => state.rooms.joinedGameIds[roomId]], + (games, joined) => _.filter(games, game => joined[game.gameId]) + ), getRoomMessages: (state: State, roomId: number) => Selectors.getMessages(state)[roomId], getRoomGames: (state: State, roomId: number) => Selectors.getRooms(state)[roomId].gameList, diff --git a/webclient/src/store/rooms/rooms.types.tsx b/webclient/src/store/rooms/rooms.types.tsx index 0b07eadd1..ef2c9607a 100644 --- a/webclient/src/store/rooms/rooms.types.tsx +++ b/webclient/src/store/rooms/rooms.types.tsx @@ -11,6 +11,6 @@ export const Types = { REMOVE_MESSAGES: '[Rooms] Remove Messages', GAME_CREATED: '[Rooms] Game Created', JOINED_GAME: '[Rooms] Joined Game', -}; +} as const; export const MAX_ROOM_MESSAGES = 1000; diff --git a/webclient/src/store/rootReducer.ts b/webclient/src/store/rootReducer.ts index 8da693fce..0a0ac94d4 100644 --- a/webclient/src/store/rootReducer.ts +++ b/webclient/src/store/rootReducer.ts @@ -1,16 +1,13 @@ -import { combineReducers } from 'redux'; +import { combineReducers } from '@reduxjs/toolkit'; import { gamesReducer } from './game'; import { roomsReducer } from './rooms'; import { serverReducer } from './server'; -import { reducer as formReducer } from 'redux-form' -import { actionReducer } from './actions' +import { actionReducer } from './actions'; export default combineReducers({ games: gamesReducer, rooms: roomsReducer, server: serverReducer, - - form: formReducer, action: actionReducer }); diff --git a/webclient/src/store/server/__mocks__/server-fixtures.ts b/webclient/src/store/server/__mocks__/server-fixtures.ts index fdaa997db..05c2f9747 100644 --- a/webclient/src/store/server/__mocks__/server-fixtures.ts +++ b/webclient/src/store/server/__mocks__/server-fixtures.ts @@ -2,7 +2,9 @@ import { BanHistoryItem, DeckList, DeckStorageTreeItem, + Game, LogItem, + ProtoInit, ReplayMatch, SortDirection, StatusEnum, @@ -12,20 +14,30 @@ import { WarnHistoryItem, WarnListItem, } from 'types'; +import { create } from '@bufbuild/protobuf'; +import { ServerInfo_GameSchema } from 'generated/proto/serverinfo_game_pb'; +import { ServerInfo_UserSchema } from 'generated/proto/serverinfo_user_pb'; +import { ServerInfo_ReplayMatchSchema } from 'generated/proto/serverinfo_replay_match_pb'; +import { ServerInfo_ChatMessageSchema } from 'generated/proto/serverinfo_chat_message_pb'; +import { ServerInfo_BanSchema } from 'generated/proto/serverinfo_ban_pb'; +import { ServerInfo_WarningSchema } from 'generated/proto/serverinfo_warning_pb'; +import { Response_WarnListSchema } from 'generated/proto/response_warn_list_pb'; +import { ServerInfo_DeckStorage_TreeItemSchema, ServerInfo_DeckStorage_FolderSchema } from 'generated/proto/serverinfo_deckstorage_pb'; +import { Response_DeckListSchema } from 'generated/proto/response_deck_list_pb'; import { ServerState } from '../server.interfaces'; -export function makeUser(overrides: Partial = {}): User { - return { +export function makeUser(overrides: ProtoInit = {}): User { + return create(ServerInfo_UserSchema, { name: 'TestUser', accountageSecs: 0n, privlevel: '', userLevel: 0, ...overrides, - }; + }); } -export function makeLogItem(overrides: Partial = {}): LogItem { - return { +export function makeLogItem(overrides: ProtoInit = {}): LogItem { + return create(ServerInfo_ChatMessageSchema, { message: '', senderId: '', senderIp: '', @@ -35,11 +47,11 @@ export function makeLogItem(overrides: Partial = {}): LogItem { targetType: '', time: '', ...overrides, - }; + }); } -export function makeBanHistoryItem(overrides: Partial = {}): BanHistoryItem { - return { +export function makeBanHistoryItem(overrides: ProtoInit = {}): BanHistoryItem { + return create(ServerInfo_BanSchema, { adminId: '', adminName: '', banTime: '', @@ -47,47 +59,45 @@ export function makeBanHistoryItem(overrides: Partial = {}): Ban banReason: '', visibleReason: '', ...overrides, - }; + }); } -export function makeWarnHistoryItem(overrides: Partial = {}): WarnHistoryItem { - return { +export function makeWarnHistoryItem(overrides: ProtoInit = {}): WarnHistoryItem { + return create(ServerInfo_WarningSchema, { userName: '', adminName: '', reason: '', timeOf: '', ...overrides, - }; + }); } -export function makeWarnListItem(overrides: Partial = {}): WarnListItem { - return { - warning: '', +export function makeWarnListItem(overrides: ProtoInit = {}): WarnListItem { + return create(Response_WarnListSchema, { + warning: [], userName: '', userClientid: '', ...overrides, - }; + }); } -export function makeDeckTreeItem(overrides: Partial = {}): DeckStorageTreeItem { - return { +export function makeDeckTreeItem(overrides: ProtoInit = {}): DeckStorageTreeItem { + return create(ServerInfo_DeckStorage_TreeItemSchema, { id: 1, name: 'item', - file: { creationTime: 0 }, - folder: null, ...overrides, - }; + }); } -export function makeDeckList(overrides: Partial = {}): DeckList { - return { - root: { items: [] }, +export function makeDeckList(overrides: ProtoInit = {}): DeckList { + return create(Response_DeckListSchema, { + root: create(ServerInfo_DeckStorage_FolderSchema, { items: [] }), ...overrides, - }; + }); } -export function makeReplayMatch(overrides: Partial = {}): ReplayMatch { - return { +export function makeReplayMatch(overrides: ProtoInit = {}): ReplayMatch { + return create(ServerInfo_ReplayMatchSchema, { gameId: 1, roomName: 'Test Room', timeStarted: 0, @@ -97,7 +107,11 @@ export function makeReplayMatch(overrides: Partial = {}): ReplayMat doNotHide: false, replayList: [], ...overrides, - }; + }); +} + +export function makeGame(overrides: Partial = {}): Game { + return { ...create(ServerInfo_GameSchema, { description: '' }), gameType: '', ...overrides }; } export function makeConnectOptions(overrides: Partial = {}): WebSocketConnectOptions { @@ -148,6 +162,7 @@ export function makeServerState(overrides: Partial = {}): ServerSta replays: [], backendDecks: null, gamesOfUser: {}, + registrationError: null, ...overrides, }; } diff --git a/webclient/src/store/server/server.actions.spec.ts b/webclient/src/store/server/server.actions.spec.ts index 4d4220854..f6817d93f 100644 --- a/webclient/src/store/server/server.actions.spec.ts +++ b/webclient/src/store/server/server.actions.spec.ts @@ -1,11 +1,16 @@ import { Actions } from './server.actions'; import { Types } from './server.types'; +import { create } from '@bufbuild/protobuf'; +import { Event_NotifyUserSchema } from 'generated/proto/event_notify_user_pb'; +import { Event_ServerShutdownSchema } from 'generated/proto/event_server_shutdown_pb'; +import { Event_UserMessageSchema } from 'generated/proto/event_user_message_pb'; import { makeBanHistoryItem, makeConnectOptions, makeDeckList, makeDeckTreeItem, makeReplayMatch, + makeGame, makeUser, makeWarnHistoryItem, makeWarnListItem, @@ -107,7 +112,7 @@ describe('Actions', () => { }); it('viewLogs', () => { - const logs = { room: [], game: [], chat: [] }; + const logs = [{ targetType: 'room' }] as any[]; expect(Actions.viewLogs(logs)).toEqual({ type: Types.VIEW_LOGS, logs }); }); @@ -124,7 +129,11 @@ describe('Actions', () => { }); it('registrationFailed', () => { - expect(Actions.registrationFailed('err')).toEqual({ type: Types.REGISTRATION_FAILED, error: 'err' }); + expect(Actions.registrationFailed('err', 999)).toEqual({ type: Types.REGISTRATION_FAILED, reason: 'err', endTime: 999 }); + }); + + it('registrationFailed without endTime', () => { + expect(Actions.registrationFailed('err')).toEqual({ type: Types.REGISTRATION_FAILED, reason: 'err', endTime: undefined }); }); it('registrationEmailError', () => { @@ -209,17 +218,17 @@ describe('Actions', () => { }); it('notifyUser', () => { - const notification = { type: 1, warningReason: '', customTitle: '', customContent: '' }; + const notification = create(Event_NotifyUserSchema, { type: 1, warningReason: '', customTitle: '', customContent: '' }); expect(Actions.notifyUser(notification)).toEqual({ type: Types.NOTIFY_USER, notification }); }); it('serverShutdown', () => { - const data = { reason: 'maintenance', minutes: 5 }; + const data = create(Event_ServerShutdownSchema, { reason: 'maintenance', minutes: 5 }); expect(Actions.serverShutdown(data)).toEqual({ type: Types.SERVER_SHUTDOWN, data }); }); it('userMessage', () => { - const messageData = { senderName: 'Alice', receiverName: 'Bob', message: 'hey' }; + const messageData = create(Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'hey' }); expect(Actions.userMessage(messageData)).toEqual({ type: Types.USER_MESSAGE, messageData }); }); @@ -347,7 +356,8 @@ describe('Actions', () => { }); it('gamesOfUser', () => { - const games = [{ gameId: 1 }] as any; - expect(Actions.gamesOfUser('alice', games)).toEqual({ type: Types.GAMES_OF_USER, userName: 'alice', games }); + const games = [makeGame({ gameId: 1 })]; + const gametypeMap = { 1: 'Standard' }; + expect(Actions.gamesOfUser('alice', games, gametypeMap)).toEqual({ type: Types.GAMES_OF_USER, userName: 'alice', games, gametypeMap }); }); }); diff --git a/webclient/src/store/server/server.actions.ts b/webclient/src/store/server/server.actions.ts index e72021889..f0c20b2fc 100644 --- a/webclient/src/store/server/server.actions.ts +++ b/webclient/src/store/server/server.actions.ts @@ -1,4 +1,10 @@ -import { DeckList, DeckStorageTreeItem, Game, ReplayMatch, WebSocketConnectOptions } from 'types'; +import { + BanHistoryItem, DeckList, DeckStorageTreeItem, GametypeMap, LogItem, ReplayMatch, + User, WebSocketConnectOptions, WarnHistoryItem, WarnListItem +} from 'types'; +import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; +import { NotifyUserData, ServerShutdownData, UserMessageData } from 'websocket/events/session/interfaces'; +import { ServerStateStatus } from './server.interfaces'; import { Types } from './server.types'; export const Actions = { @@ -15,7 +21,7 @@ export const Actions = { loginFailed: () => ({ type: Types.LOGIN_FAILED, }), - connectionClosed: reason => ({ + connectionClosed: (reason: number) => ({ type: Types.CONNECTION_CLOSED, reason }), @@ -28,59 +34,59 @@ export const Actions = { testConnectionFailed: () => ({ type: Types.TEST_CONNECTION_FAILED, }), - serverMessage: message => ({ + serverMessage: (message: string) => ({ type: Types.SERVER_MESSAGE, message }), - updateBuddyList: buddyList => ({ + updateBuddyList: (buddyList: User[]) => ({ type: Types.UPDATE_BUDDY_LIST, buddyList }), - addToBuddyList: user => ({ + addToBuddyList: (user: User) => ({ type: Types.ADD_TO_BUDDY_LIST, user }), - removeFromBuddyList: userName => ({ + removeFromBuddyList: (userName: string) => ({ type: Types.REMOVE_FROM_BUDDY_LIST, userName }), - updateIgnoreList: ignoreList => ({ + updateIgnoreList: (ignoreList: User[]) => ({ type: Types.UPDATE_IGNORE_LIST, ignoreList }), - addToIgnoreList: user => ({ + addToIgnoreList: (user: User) => ({ type: Types.ADD_TO_IGNORE_LIST, user }), - removeFromIgnoreList: userName => ({ + removeFromIgnoreList: (userName: string) => ({ type: Types.REMOVE_FROM_IGNORE_LIST, userName }), - updateInfo: info => ({ + updateInfo: (info: { name: string; version: string }) => ({ type: Types.UPDATE_INFO, info }), - updateStatus: status => ({ + updateStatus: (status: ServerStateStatus) => ({ type: Types.UPDATE_STATUS, status }), - updateUser: user => ({ + updateUser: (user: User) => ({ type: Types.UPDATE_USER, user }), - updateUsers: users => ({ + updateUsers: (users: User[]) => ({ type: Types.UPDATE_USERS, users }), - userJoined: user => ({ + userJoined: (user: User) => ({ type: Types.USER_JOINED, user }), - userLeft: name => ({ + userLeft: (name: string) => ({ type: Types.USER_LEFT, name }), - viewLogs: logs => ({ + viewLogs: (logs: LogItem[]) => ({ type: Types.VIEW_LOGS, logs }), @@ -93,22 +99,26 @@ export const Actions = { registrationSuccess: () => ({ type: Types.REGISTRATION_SUCCESS, }), - registrationFailed: (error) => ({ + registrationFailed: (reason: string, endTime?: number) => ({ type: Types.REGISTRATION_FAILED, - error + reason, + endTime, }), - registrationEmailError: (error) => ({ + registrationEmailError: (error: string) => ({ type: Types.REGISTRATION_EMAIL_ERROR, error }), - registrationPasswordError: (error) => ({ + registrationPasswordError: (error: string) => ({ type: Types.REGISTRATION_PASSWORD_ERROR, error }), - registrationUserNameError: (error) => ({ + registrationUserNameError: (error: string) => ({ type: Types.REGISTRATION_USERNAME_ERROR, error }), + clearRegistrationErrors: () => ({ + type: Types.CLEAR_REGISTRATION_ERRORS, + }), accountAwaitingActivation: (options: WebSocketConnectOptions) => ({ type: Types.ACCOUNT_AWAITING_ACTIVATION, options @@ -131,7 +141,7 @@ export const Actions = { resetPasswordSuccess: () => ({ type: Types.RESET_PASSWORD_SUCCESS, }), - adjustMod: (userName, shouldBeMod, shouldBeJudge) => ({ + adjustMod: (userName: string, shouldBeMod: boolean, shouldBeJudge: boolean) => ({ type: Types.ADJUST_MOD, userName, shouldBeMod, @@ -149,59 +159,59 @@ export const Actions = { accountPasswordChange: () => ({ type: Types.ACCOUNT_PASSWORD_CHANGE, }), - accountEditChanged: (user) => ({ + accountEditChanged: (user: Partial) => ({ type: Types.ACCOUNT_EDIT_CHANGED, user, }), - accountImageChanged: (user) => ({ + accountImageChanged: (user: Partial) => ({ type: Types.ACCOUNT_IMAGE_CHANGED, user, }), - getUserInfo: (userInfo) => ({ + getUserInfo: (userInfo: User) => ({ type: Types.GET_USER_INFO, userInfo, }), - notifyUser: (notification) => ({ + notifyUser: (notification: NotifyUserData) => ({ type: Types.NOTIFY_USER, notification, }), - serverShutdown: (data) => ({ + serverShutdown: (data: ServerShutdownData) => ({ type: Types.SERVER_SHUTDOWN, data, }), - userMessage: (messageData) => ({ + userMessage: (messageData: UserMessageData) => ({ type: Types.USER_MESSAGE, messageData, }), - addToList: (list, userName) => ({ + addToList: (list: string, userName: string) => ({ type: Types.ADD_TO_LIST, list, userName, }), - removeFromList: (list, userName) => ({ + removeFromList: (list: string, userName: string) => ({ type: Types.REMOVE_FROM_LIST, list, userName, }), - banFromServer: (userName) => ({ + banFromServer: (userName: string) => ({ type: Types.BAN_FROM_SERVER, userName, }), - banHistory: (userName, banHistory) => ({ + banHistory: (userName: string, banHistory: BanHistoryItem[]) => ({ type: Types.BAN_HISTORY, userName, banHistory, }), - warnHistory: (userName, warnHistory) => ({ + warnHistory: (userName: string, warnHistory: WarnHistoryItem[]) => ({ type: Types.WARN_HISTORY, userName, warnHistory, }), - warnListOptions: (warnList) => ({ + warnListOptions: (warnList: WarnListItem[]) => ({ type: Types.WARN_LIST_OPTIONS, warnList, }), - warnUser: (userName) => ({ + warnUser: (userName: string) => ({ type: Types.WARN_USER, userName, }), @@ -234,5 +244,8 @@ export const Actions = { deckDelDir: (path: string) => ({ type: Types.DECK_DEL_DIR, path }), deckUpload: (path: string, treeItem: DeckStorageTreeItem) => ({ type: Types.DECK_UPLOAD, path, treeItem }), deckDelete: (deckId: number) => ({ type: Types.DECK_DELETE, deckId }), - gamesOfUser: (userName: string, games: Game[]) => ({ type: Types.GAMES_OF_USER, userName, games }), + gamesOfUser: (userName: string, games: ServerInfo_Game[], gametypeMap: GametypeMap) => + ({ type: Types.GAMES_OF_USER, userName, games, gametypeMap }), } + +export type ServerAction = ReturnType; diff --git a/webclient/src/store/server/server.dispatch.spec.ts b/webclient/src/store/server/server.dispatch.spec.ts index ca2352ff0..5a4198d2f 100644 --- a/webclient/src/store/server/server.dispatch.spec.ts +++ b/webclient/src/store/server/server.dispatch.spec.ts @@ -1,17 +1,18 @@ -vi.mock('store/store', () => ({ store: { dispatch: vi.fn() } })); -vi.mock('redux-form', () => ({ - reset: vi.fn((form) => ({ type: '@@redux-form/RESET', meta: { form } })), -})); +vi.mock('store', () => ({ store: { dispatch: vi.fn() } })); -import { store } from 'store/store'; -import { reset } from 'redux-form'; +import { store } from 'store'; import { Actions } from './server.actions'; import { Dispatch } from './server.dispatch'; +import { create } from '@bufbuild/protobuf'; +import { Event_NotifyUserSchema } from 'generated/proto/event_notify_user_pb'; +import { Event_ServerShutdownSchema } from 'generated/proto/event_server_shutdown_pb'; +import { Event_UserMessageSchema } from 'generated/proto/event_user_message_pb'; import { makeBanHistoryItem, makeConnectOptions, makeDeckList, makeDeckTreeItem, + makeGame, makeReplayMatch, makeUser, makeWarnHistoryItem, @@ -68,11 +69,10 @@ describe('Dispatch', () => { expect(store.dispatch).toHaveBeenCalledWith(Actions.updateBuddyList(list)); }); - it('addToBuddyList dispatches reset("addToBuddies") then Actions.addToBuddyList()', () => { + it('addToBuddyList dispatches Actions.addToBuddyList()', () => { const user = makeUser(); Dispatch.addToBuddyList(user); - expect(store.dispatch).toHaveBeenNthCalledWith(1, (reset as vi.Mock)('addToBuddies')); - expect(store.dispatch).toHaveBeenNthCalledWith(2, Actions.addToBuddyList(user)); + expect(store.dispatch).toHaveBeenCalledWith(Actions.addToBuddyList(user)); }); it('removeFromBuddyList dispatches Actions.removeFromBuddyList()', () => { @@ -86,11 +86,10 @@ describe('Dispatch', () => { expect(store.dispatch).toHaveBeenCalledWith(Actions.updateIgnoreList(list)); }); - it('addToIgnoreList dispatches reset("addToIgnore") then Actions.addToIgnoreList()', () => { + it('addToIgnoreList dispatches Actions.addToIgnoreList()', () => { const user = makeUser(); Dispatch.addToIgnoreList(user); - expect(store.dispatch).toHaveBeenNthCalledWith(1, (reset as vi.Mock)('addToIgnore')); - expect(store.dispatch).toHaveBeenNthCalledWith(2, Actions.addToIgnoreList(user)); + expect(store.dispatch).toHaveBeenCalledWith(Actions.addToIgnoreList(user)); }); it('removeFromIgnoreList dispatches Actions.removeFromIgnoreList()', () => { @@ -132,7 +131,7 @@ describe('Dispatch', () => { }); it('viewLogs dispatches Actions.viewLogs()', () => { - const logs = { room: [], game: [], chat: [] }; + const logs = [{ targetType: 'room' }] as any[]; Dispatch.viewLogs(logs); expect(store.dispatch).toHaveBeenCalledWith(Actions.viewLogs(logs)); }); @@ -157,9 +156,14 @@ describe('Dispatch', () => { expect(store.dispatch).toHaveBeenCalledWith(Actions.registrationSuccess()); }); - it('registrationFailed dispatches correctly', () => { - Dispatch.registrationFailed('err'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.registrationFailed('err')); + it('registrationFailed passes reason and endTime to action', () => { + Dispatch.registrationFailed('reason', 999); + expect(store.dispatch).toHaveBeenCalledWith(Actions.registrationFailed('reason', 999)); + }); + + it('registrationFailed passes reason only when no endTime', () => { + Dispatch.registrationFailed('plain reason'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.registrationFailed('plain reason', undefined)); }); it('registrationEmailError dispatches correctly', () => { @@ -257,19 +261,19 @@ describe('Dispatch', () => { }); it('notifyUser dispatches correctly', () => { - const notification = { type: 1, warningReason: '', customTitle: '', customContent: '' }; + const notification = create(Event_NotifyUserSchema, { type: 1, warningReason: '', customTitle: '', customContent: '' }); Dispatch.notifyUser(notification); expect(store.dispatch).toHaveBeenCalledWith(Actions.notifyUser(notification)); }); it('serverShutdown dispatches correctly', () => { - const data = { reason: 'maintenance', minutes: 5 }; + const data = create(Event_ServerShutdownSchema, { reason: 'maintenance', minutes: 5 }); Dispatch.serverShutdown(data); expect(store.dispatch).toHaveBeenCalledWith(Actions.serverShutdown(data)); }); it('userMessage dispatches correctly', () => { - const messageData = { senderName: 'Alice', receiverName: 'Bob', message: 'hey' }; + const messageData = create(Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'hey' }); Dispatch.userMessage(messageData); expect(store.dispatch).toHaveBeenCalledWith(Actions.userMessage(messageData)); }); @@ -382,8 +386,9 @@ describe('Dispatch', () => { }); it('gamesOfUser dispatches correctly', () => { - const games = [{ gameId: 1 }] as any; - Dispatch.gamesOfUser('alice', games); - expect(store.dispatch).toHaveBeenCalledWith(Actions.gamesOfUser('alice', games)); + const games = [makeGame({ gameId: 1 })]; + const gametypeMap = { 1: 'Standard' }; + Dispatch.gamesOfUser('alice', games, gametypeMap); + expect(store.dispatch).toHaveBeenCalledWith(Actions.gamesOfUser('alice', games, gametypeMap)); }); }); diff --git a/webclient/src/store/server/server.dispatch.ts b/webclient/src/store/server/server.dispatch.ts index ac747f165..6f0177a7f 100644 --- a/webclient/src/store/server/server.dispatch.ts +++ b/webclient/src/store/server/server.dispatch.ts @@ -1,7 +1,11 @@ -import { reset } from 'redux-form'; import { Actions } from './server.actions'; import { store } from 'store'; -import { DeckList, DeckStorageTreeItem, Game, ReplayMatch, WebSocketConnectOptions } from 'types'; +import { + BanHistoryItem, DeckList, DeckStorageTreeItem, GametypeMap, LogItem, ReplayMatch, + User, WarnHistoryItem, WarnListItem, WebSocketConnectOptions +} from 'types'; +import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; +import { NotifyUserData, ServerShutdownData, UserMessageData } from 'websocket/events/session/interfaces'; export const Dispatch = { initialized: () => { @@ -10,13 +14,13 @@ export const Dispatch = { clearStore: () => { store.dispatch(Actions.clearStore()); }, - loginSuccessful: options => { + loginSuccessful: (options: WebSocketConnectOptions) => { store.dispatch(Actions.loginSuccessful(options)); }, loginFailed: () => { store.dispatch(Actions.loginFailed()); }, - connectionClosed: reason => { + connectionClosed: (reason: number) => { store.dispatch(Actions.connectionClosed(reason)); }, connectionFailed: () => { @@ -28,57 +32,55 @@ export const Dispatch = { testConnectionFailed: () => { store.dispatch(Actions.testConnectionFailed()); }, - updateBuddyList: buddyList => { + updateBuddyList: (buddyList: User[]) => { store.dispatch(Actions.updateBuddyList(buddyList)); }, - addToBuddyList: user => { - store.dispatch(reset('addToBuddies')); + addToBuddyList: (user: User) => { store.dispatch(Actions.addToBuddyList(user)); }, - removeFromBuddyList: userName => { + removeFromBuddyList: (userName: string) => { store.dispatch(Actions.removeFromBuddyList(userName)); }, - updateIgnoreList: ignoreList => { + updateIgnoreList: (ignoreList: User[]) => { store.dispatch(Actions.updateIgnoreList(ignoreList)); }, - addToIgnoreList: user => { - store.dispatch(reset('addToIgnore')); + addToIgnoreList: (user: User) => { store.dispatch(Actions.addToIgnoreList(user)); }, - removeFromIgnoreList: userName => { + removeFromIgnoreList: (userName: string) => { store.dispatch(Actions.removeFromIgnoreList(userName)); }, - updateInfo: (name, version) => { + updateInfo: (name: string, version: string) => { store.dispatch(Actions.updateInfo({ name, version })); }, - updateStatus: (state, description) => { + updateStatus: (state: number, description: string) => { store.dispatch(Actions.updateStatus({ state, description })); }, - updateUser: user => { + updateUser: (user: User) => { store.dispatch(Actions.updateUser(user)); }, - updateUsers: users => { + updateUsers: (users: User[]) => { store.dispatch(Actions.updateUsers(users)); }, - userJoined: user => { + userJoined: (user: User) => { store.dispatch(Actions.userJoined(user)); }, - userLeft: name => { + userLeft: (name: string) => { store.dispatch(Actions.userLeft(name)); }, - viewLogs: name => { - store.dispatch(Actions.viewLogs(name)); + viewLogs: (logs: LogItem[]) => { + store.dispatch(Actions.viewLogs(logs)); }, clearLogs: () => { store.dispatch(Actions.clearLogs()); }, - serverMessage: message => { + serverMessage: (message: string) => { store.dispatch(Actions.serverMessage(message)); }, registrationRequiresEmail: () => { @@ -87,16 +89,19 @@ export const Dispatch = { registrationSuccess: () => { store.dispatch(Actions.registrationSuccess()) }, - registrationFailed: (error) => { - store.dispatch(Actions.registrationFailed(error)); + registrationFailed: (reason: string, endTime?: number) => { + store.dispatch(Actions.registrationFailed(reason, endTime)); }, - registrationEmailError: (error) => { + clearRegistrationErrors: () => { + store.dispatch(Actions.clearRegistrationErrors()); + }, + registrationEmailError: (error: string) => { store.dispatch(Actions.registrationEmailError(error)); }, - registrationPasswordError: (error) => { + registrationPasswordError: (error: string) => { store.dispatch(Actions.registrationPasswordError(error)); }, - registrationUserNameError: (error) => { + registrationUserNameError: (error: string) => { store.dispatch(Actions.registrationUserNameError(error)); }, accountAwaitingActivation: (options: WebSocketConnectOptions) => { @@ -120,7 +125,7 @@ export const Dispatch = { resetPasswordSuccess: () => { store.dispatch(Actions.resetPasswordSuccess()); }, - adjustMod: (userName, shouldBeMod, shouldBeJudge) => { + adjustMod: (userName: string, shouldBeMod: boolean, shouldBeJudge: boolean) => { store.dispatch(Actions.adjustMod(userName, shouldBeMod, shouldBeJudge)); }, reloadConfig: () => { @@ -135,43 +140,43 @@ export const Dispatch = { accountPasswordChange: () => { store.dispatch(Actions.accountPasswordChange()); }, - accountEditChanged: (user) => { + accountEditChanged: (user: Partial) => { store.dispatch(Actions.accountEditChanged(user)); }, - accountImageChanged: (user) => { + accountImageChanged: (user: Partial) => { store.dispatch(Actions.accountImageChanged(user)); }, - getUserInfo: (userInfo) => { + getUserInfo: (userInfo: User) => { store.dispatch(Actions.getUserInfo(userInfo)); }, - notifyUser: (notification) => { + notifyUser: (notification: NotifyUserData) => { store.dispatch(Actions.notifyUser(notification)) }, - serverShutdown: (data) => { + serverShutdown: (data: ServerShutdownData) => { store.dispatch(Actions.serverShutdown(data)) }, - userMessage: (messageData) => { + userMessage: (messageData: UserMessageData) => { store.dispatch(Actions.userMessage(messageData)) }, - addToList: (list, userName) => { + addToList: (list: string, userName: string) => { store.dispatch(Actions.addToList(list, userName)) }, - removeFromList: (list, userName) => { + removeFromList: (list: string, userName: string) => { store.dispatch(Actions.removeFromList(list, userName)) }, - banFromServer: (userName) => { + banFromServer: (userName: string) => { store.dispatch(Actions.banFromServer(userName)); }, - banHistory: (userName, banHistory) => { + banHistory: (userName: string, banHistory: BanHistoryItem[]) => { store.dispatch(Actions.banHistory(userName, banHistory)) }, - warnHistory: (userName, warnHistory) => { + warnHistory: (userName: string, warnHistory: WarnHistoryItem[]) => { store.dispatch(Actions.warnHistory(userName, warnHistory)) }, - warnListOptions: (warnList) => { + warnListOptions: (warnList: WarnListItem[]) => { store.dispatch(Actions.warnListOptions(warnList)) }, - warnUser: (userName) => { + warnUser: (userName: string) => { store.dispatch(Actions.warnUser(userName)) }, grantReplayAccess: (replayId: number, moderatorName: string) => { @@ -213,7 +218,7 @@ export const Dispatch = { deckDelete: (deckId: number) => { store.dispatch(Actions.deckDelete(deckId)); }, - gamesOfUser: (userName: string, games: Game[]) => { - store.dispatch(Actions.gamesOfUser(userName, games)); + gamesOfUser: (userName: string, games: ServerInfo_Game[], gametypeMap: GametypeMap) => { + store.dispatch(Actions.gamesOfUser(userName, games, gametypeMap)); }, } diff --git a/webclient/src/store/server/server.interfaces.ts b/webclient/src/store/server/server.interfaces.ts index 4a9d63509..6ee8f29fe 100644 --- a/webclient/src/store/server/server.interfaces.ts +++ b/webclient/src/store/server/server.interfaces.ts @@ -72,6 +72,7 @@ export interface ServerState { replays: ReplayMatch[]; backendDecks: DeckList | null; gamesOfUser: { [userName: string]: Game[] }; + registrationError: string | null; } export interface ServerStateStatus { diff --git a/webclient/src/store/server/server.reducer.spec.ts b/webclient/src/store/server/server.reducer.spec.ts index c501fbe0a..aa961dfe0 100644 --- a/webclient/src/store/server/server.reducer.spec.ts +++ b/webclient/src/store/server/server.reducer.spec.ts @@ -1,4 +1,7 @@ import { StatusEnum, UserLevelFlag } from 'types'; +import { create } from '@bufbuild/protobuf'; +import { Event_UserMessageSchema } from 'generated/proto/event_user_message_pb'; +import { ServerInfo_DeckStorage_FolderSchema, ServerInfo_DeckStorage_TreeItemSchema } from 'generated/proto/serverinfo_deckstorage_pb'; import { serverReducer } from './server.reducer'; import { Types } from './server.types'; import { @@ -6,6 +9,7 @@ import { makeConnectOptions, makeDeckList, makeDeckTreeItem, + makeGame, makeLogItem, makeReplayMatch, makeServerState, @@ -71,6 +75,35 @@ describe('Account & Connection', () => { }); }); +// ── Registration ────────────────────────────────────────────────────────────── + +describe('Registration', () => { + it('REGISTRATION_FAILED → stores normalized error (plain reason)', () => { + const state = makeServerState({ registrationError: null }); + const result = serverReducer(state, { type: Types.REGISTRATION_FAILED, reason: 'Server is disabled', endTime: undefined }); + expect(result.registrationError).toBe('Server is disabled'); + }); + + it('REGISTRATION_FAILED → normalizes banned error when endTime is given', () => { + const state = makeServerState({ registrationError: null }); + const result = serverReducer(state, { type: Types.REGISTRATION_FAILED, reason: 'bad actor', endTime: Date.now() + 100_000 }); + expect(result.registrationError).toContain('banned'); + expect(result.registrationError).toContain('bad actor'); + }); + + it('CLEAR_REGISTRATION_ERRORS → sets registrationError to null', () => { + const state = makeServerState({ registrationError: 'some error' }); + const result = serverReducer(state, { type: Types.CLEAR_REGISTRATION_ERRORS }); + expect(result.registrationError).toBeNull(); + }); + + it('CLEAR_STORE → resets registrationError to null', () => { + const state = makeServerState({ registrationError: 'stale error' }); + const result = serverReducer(state, { type: Types.CLEAR_STORE }); + expect(result.registrationError).toBeNull(); + }); +}); + // ── Server Info & Status ────────────────────────────────────────────────────── describe('Server Info & Status', () => { @@ -205,11 +238,11 @@ describe('Ignore List', () => { // ── Logs ───────────────────────────────────────────────────────────────────── describe('Logs', () => { - it('VIEW_LOGS → replaces logs entirely', () => { - const logs = { room: [makeLogItem()], game: [], chat: [] }; + it('VIEW_LOGS → groups LogItem[] into room/game/chat buckets', () => { + const log = makeLogItem({ targetType: 'room' }); const state = makeServerState(); - const result = serverReducer(state, { type: Types.VIEW_LOGS, logs }); - expect(result.logs).toEqual(logs); + const result = serverReducer(state, { type: Types.VIEW_LOGS, logs: [log] }); + expect(result.logs.room).toEqual([log]); }); it('CLEAR_LOGS → resets logs to empty arrays', () => { @@ -241,12 +274,12 @@ describe('Messaging', () => { }); it('USER_MESSAGE → appends to existing messages for that user', () => { - const existingMsg = { senderName: 'Alice', receiverName: 'Bob', message: 'first' }; + const existingMsg = create(Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'first' }); const state = makeServerState({ user: makeUser({ name: 'Bob' }), messages: { Alice: [existingMsg] }, }); - const newMsg = { senderName: 'Alice', receiverName: 'Bob', message: 'second' }; + const newMsg = create(Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'second' }); const result = serverReducer(state, { type: Types.USER_MESSAGE, messageData: newMsg }); expect(result.messages['Alice']).toHaveLength(2); }); @@ -442,8 +475,12 @@ describe('Deck Storage', () => { }); it('DECK_UPLOAD with nested path → inserts into matching subfolder', () => { - const subfolder = { id: 0, name: 'myDecks', file: null, folder: { items: [] } }; - const state = makeServerState({ backendDecks: { root: { items: [subfolder] } } }); + const subfolder = create(ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: 'myDecks', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] }) + }); + const state = makeServerState({ + backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) }) + }); const item = makeDeckTreeItem({ name: 'new.cod' }); const result = serverReducer(state, { type: Types.DECK_UPLOAD, path: 'myDecks', treeItem: item }); const folder = result.backendDecks.root.items.find(i => i.name === 'myDecks'); @@ -468,15 +505,19 @@ describe('Deck Storage', () => { it('DECK_DELETE → removes item by id from tree', () => { const item = makeDeckTreeItem({ id: 7 }); - const state = makeServerState({ backendDecks: { root: { items: [item] } } }); + const state = makeServerState({ backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [item] }) }) }); const result = serverReducer(state, { type: Types.DECK_DELETE, deckId: 7 }); expect(result.backendDecks.root.items).toHaveLength(0); }); it('DECK_DELETE → recursively removes item nested inside a subfolder', () => { const nested = makeDeckTreeItem({ id: 9, name: 'nested.cod' }); - const subfolder = { id: 0, name: 'sub', file: null, folder: { items: [nested] } }; - const state = makeServerState({ backendDecks: { root: { items: [subfolder] } } }); + const subfolder = create(ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: 'sub', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [nested] }) + }); + const state = makeServerState({ + backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) }) + }); const result = serverReducer(state, { type: Types.DECK_DELETE, deckId: 9 }); expect(result.backendDecks.root.items[0].folder.items).toHaveLength(0); }); @@ -492,12 +533,16 @@ describe('Deck Storage', () => { const result = serverReducer(state, { type: Types.DECK_NEW_DIR, path: '', dirName: 'myDir' }); expect(result.backendDecks.root.items).toHaveLength(1); expect(result.backendDecks.root.items[0].name).toBe('myDir'); - expect(result.backendDecks.root.items[0].folder).toEqual({ items: [] }); + expect(result.backendDecks.root.items[0].folder.items).toEqual([]); }); it('DECK_NEW_DIR nested → inserts folder inside matching subfolder', () => { - const subfolder = { id: 0, name: 'parent', file: null, folder: { items: [] } }; - const state = makeServerState({ backendDecks: { root: { items: [subfolder] } } }); + const subfolder = create(ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: 'parent', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] }) + }); + const state = makeServerState({ + backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) }) + }); const result = serverReducer(state, { type: Types.DECK_NEW_DIR, path: 'parent', dirName: 'child' }); const parent = result.backendDecks.root.items.find(i => i.name === 'parent'); expect(parent.folder.items).toHaveLength(1); @@ -511,23 +556,37 @@ describe('Deck Storage', () => { }); it('DECK_DEL_DIR → removes folder from root by name', () => { - const subfolder = { id: 0, name: 'myDir', file: null, folder: { items: [] } }; - const state = makeServerState({ backendDecks: { root: { items: [subfolder] } } }); + const subfolder = create(ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: 'myDir', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] }) + }); + const state = makeServerState({ + backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) }) + }); const result = serverReducer(state, { type: Types.DECK_DEL_DIR, path: 'myDir' }); expect(result.backendDecks.root.items).toHaveLength(0); }); it('DECK_DEL_DIR → returns deck tree unchanged when path is empty', () => { - const subfolder = { id: 0, name: 'keep', file: null, folder: { items: [] } }; - const state = makeServerState({ backendDecks: { root: { items: [subfolder] } } }); + const subfolder = create(ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: 'keep', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] }) + }); + const state = makeServerState({ + backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) }) + }); const result = serverReducer(state, { type: Types.DECK_DEL_DIR, path: '' }); expect(result.backendDecks.root.items).toHaveLength(1); }); it('DECK_DEL_DIR → recursively removes nested subfolder via multi-segment path', () => { - const child = { id: 0, name: 'child', file: null, folder: { items: [] } }; - const parent = { id: 0, name: 'parent', file: null, folder: { items: [child] } }; - const state = makeServerState({ backendDecks: { root: { items: [parent] } } }); + const child = create(ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: 'child', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] }) + }); + const parent = create(ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: 'parent', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [child] }) + }); + const state = makeServerState({ + backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [parent] }) }) + }); const result = serverReducer(state, { type: Types.DECK_DEL_DIR, path: 'parent/child' }); expect(result.backendDecks.root.items[0].folder.items).toHaveLength(0); }); @@ -536,25 +595,25 @@ describe('Deck Storage', () => { // ── GAMES_OF_USER ───────────────────────────────────────────────────────────── describe('GAMES_OF_USER', () => { - it('stores games keyed by userName', () => { - const games = [{ gameId: 5, roomId: 1 }] as any; + it('stores normalized games keyed by userName', () => { + const games = [makeGame({ gameId: 5 })]; const state = makeServerState(); - const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', games }); - expect(result.gamesOfUser['alice']).toBe(games); + const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', games, gametypeMap: {} }); + expect(result.gamesOfUser['alice']).toEqual(games); }); it('overwrites previous games for same user', () => { - const old = [{ gameId: 1 }] as any; - const fresh = [{ gameId: 2 }] as any; + const old = [makeGame({ gameId: 1 })]; + const fresh = [makeGame({ gameId: 2 })]; const state = makeServerState({ gamesOfUser: { alice: old } }); - const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', games: fresh }); - expect(result.gamesOfUser['alice']).toBe(fresh); + const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', games: fresh, gametypeMap: {} }); + expect(result.gamesOfUser['alice']).toEqual(fresh); }); it('does not affect other users\' entries', () => { - const bobGames = [{ gameId: 3 }] as any; + const bobGames = [makeGame({ gameId: 3 })]; const state = makeServerState({ gamesOfUser: { bob: bobGames } }); - const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', games: [] }); + const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', games: [], gametypeMap: {} }); expect(result.gamesOfUser['bob']).toBe(bobGames); }); }); diff --git a/webclient/src/store/server/server.reducer.ts b/webclient/src/store/server/server.reducer.ts index f5ad57e7c..859b0a168 100644 --- a/webclient/src/store/server/server.reducer.ts +++ b/webclient/src/store/server/server.reducer.ts @@ -1,7 +1,11 @@ import { DeckStorageFolder, DeckStorageTreeItem, SortDirection, StatusEnum, UserLevelFlag, UserSortField } from 'types'; +import { create } from '@bufbuild/protobuf'; +import { Response_DeckListSchema } from 'generated/proto/response_deck_list_pb'; +import { ServerInfo_DeckStorage_FolderSchema, ServerInfo_DeckStorage_TreeItemSchema } from 'generated/proto/serverinfo_deckstorage_pb'; -import { SortUtil } from '../common'; +import { normalizeBannedUserError, normalizeGameObject, normalizeLogs, SortUtil } from '../common'; +import { ServerAction } from './server.actions'; import { ServerState } from './server.interfaces' import { Types } from './server.types'; @@ -11,31 +15,33 @@ function splitPath(path: string): string[] { function insertAtPath(folder: DeckStorageFolder, pathSegments: string[], item: DeckStorageTreeItem): DeckStorageFolder { if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === '')) { - return { items: [...folder.items, item] }; + return create(ServerInfo_DeckStorage_FolderSchema, { items: [...folder.items, item] }); } const [head, ...tail] = pathSegments; const match = folder.items.find(child => child.name === head && child.folder); if (match) { - return { + return create(ServerInfo_DeckStorage_FolderSchema, { items: folder.items.map(child => child === match ? { ...child, folder: insertAtPath(child.folder!, tail, item) } : child ), - }; + }); } - const created: DeckStorageTreeItem = { id: 0, name: head, file: null, folder: insertAtPath({ items: [] }, tail, item) }; - return { items: [...folder.items, created] }; + const created: DeckStorageTreeItem = create(ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: head, folder: insertAtPath(create(ServerInfo_DeckStorage_FolderSchema, { items: [] }), tail, item) + }); + return create(ServerInfo_DeckStorage_FolderSchema, { items: [...folder.items, created] }); } function removeById(folder: DeckStorageFolder, id: number): DeckStorageFolder { - return { + return create(ServerInfo_DeckStorage_FolderSchema, { items: folder.items .filter(item => item.id !== id) .map(item => item.folder ? { ...item, folder: removeById(item.folder, id) } : item ), - }; + }); } function removeByPath(folder: DeckStorageFolder, pathSegments: string[]): DeckStorageFolder { @@ -44,15 +50,17 @@ function removeByPath(folder: DeckStorageFolder, pathSegments: string[]): DeckSt } const [head, ...tail] = pathSegments; if (tail.length === 0) { - return { items: folder.items.filter(item => !(item.name === head && item.folder !== null)) }; + return create(ServerInfo_DeckStorage_FolderSchema, { + items: folder.items.filter(item => !(item.name === head && item.folder != null)) + }); } - return { + return create(ServerInfo_DeckStorage_FolderSchema, { items: folder.items.map(item => item.name === head && item.folder ? { ...item, folder: removeByPath(item.folder, tail) } : item ), - }; + }); } const initialState: ServerState = { @@ -93,9 +101,10 @@ const initialState: ServerState = { replays: [], backendDecks: null, gamesOfUser: {}, + registrationError: null, }; -export const serverReducer = (state = initialState, action: any) => { +export const serverReducer = (state = initialState, action: ServerAction) => { switch (action.type) { case Types.INITIALIZED: { return { @@ -271,7 +280,7 @@ export const serverReducer = (state = initialState, action: any) => { return { ...state, logs: { - ...logs + ...normalizeLogs(logs) } }; } @@ -424,60 +433,96 @@ export const serverReducer = (state = initialState, action: any) => { return { ...state, backendDecks: action.deckList }; } case Types.DECK_UPLOAD: { - if (!state.backendDecks) { + if (!state.backendDecks?.root) { return state; } return { ...state, - backendDecks: { + backendDecks: create(Response_DeckListSchema, { root: insertAtPath(state.backendDecks.root, splitPath(action.path), action.treeItem), - }, + }), }; } case Types.DECK_DELETE: { - if (!state.backendDecks) { + if (!state.backendDecks?.root) { return state; } return { ...state, - backendDecks: { + backendDecks: create(Response_DeckListSchema, { root: removeById(state.backendDecks.root, action.deckId), - }, + }), }; } case Types.DECK_NEW_DIR: { - if (!state.backendDecks) { + if (!state.backendDecks?.root) { return state; } - const newFolder: DeckStorageTreeItem = { id: 0, name: action.dirName, file: null, folder: { items: [] } }; + const newFolder: DeckStorageTreeItem = create(ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: action.dirName, folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] }) + }); return { ...state, - backendDecks: { + backendDecks: create(Response_DeckListSchema, { root: insertAtPath(state.backendDecks.root, splitPath(action.path), newFolder), - }, + }), }; } case Types.DECK_DEL_DIR: { - if (!state.backendDecks) { + if (!state.backendDecks?.root) { return state; } return { ...state, - backendDecks: { + backendDecks: create(Response_DeckListSchema, { root: removeByPath(state.backendDecks.root, splitPath(action.path)), - }, + }), }; } case Types.GAMES_OF_USER: { - const { userName, games } = action; + const { userName, games, gametypeMap } = action; + const normalizedGames = games.map(g => normalizeGameObject(g, gametypeMap)); return { ...state, gamesOfUser: { ...state.gamesOfUser, - [userName]: games, + [userName]: normalizedGames, }, }; } + case Types.REGISTRATION_FAILED: { + const error = action.endTime + ? normalizeBannedUserError(action.reason, action.endTime) + : action.reason; + return { ...state, registrationError: error }; + } + case Types.CLEAR_REGISTRATION_ERRORS: + return { ...state, registrationError: null }; + // Signal-only action types — no state mutation, explicit for discriminated-union exhaustiveness + case Types.LOGIN_SUCCESSFUL: + case Types.LOGIN_FAILED: + case Types.CONNECTION_CLOSED: + case Types.CONNECTION_FAILED: + case Types.TEST_CONNECTION_SUCCESSFUL: + case Types.TEST_CONNECTION_FAILED: + case Types.REGISTRATION_REQUIRES_EMAIL: + case Types.REGISTRATION_SUCCESS: + case Types.REGISTRATION_EMAIL_ERROR: + case Types.REGISTRATION_PASSWORD_ERROR: + case Types.REGISTRATION_USERNAME_ERROR: + case Types.RESET_PASSWORD_REQUESTED: + case Types.RESET_PASSWORD_FAILED: + case Types.RESET_PASSWORD_CHALLENGE: + case Types.RESET_PASSWORD_SUCCESS: + case Types.RELOAD_CONFIG: + case Types.SHUTDOWN_SERVER: + case Types.UPDATE_SERVER_MESSAGE: + case Types.ACCOUNT_PASSWORD_CHANGE: + case Types.ADD_TO_LIST: + case Types.REMOVE_FROM_LIST: + case Types.GRANT_REPLAY_ACCESS: + case Types.FORCE_ACTIVATE_USER: + return state; default: return state; } diff --git a/webclient/src/store/server/server.selectors.ts b/webclient/src/store/server/server.selectors.ts index 4506699cf..260757ab9 100644 --- a/webclient/src/store/server/server.selectors.ts +++ b/webclient/src/store/server/server.selectors.ts @@ -18,4 +18,5 @@ export const Selectors = { getIgnoreList: ({ server }: State) => server.ignoreList, getReplays: ({ server }: State) => server.replays, getBackendDecks: ({ server }: State) => server.backendDecks, + getRegistrationError: ({ server }: State) => server.registrationError, } diff --git a/webclient/src/store/server/server.types.ts b/webclient/src/store/server/server.types.ts index ab1dd3f1d..159e33094 100644 --- a/webclient/src/store/server/server.types.ts +++ b/webclient/src/store/server/server.types.ts @@ -28,6 +28,7 @@ export const Types = { REGISTRATION_EMAIL_ERROR: '[Server] Registration Email Error', REGISTRATION_PASSWORD_ERROR: '[Server] Registration Password Error', REGISTRATION_USERNAME_ERROR: '[Server] Registration Username Error', + CLEAR_REGISTRATION_ERRORS: '[Server] Clear Registration Errors', ACCOUNT_AWAITING_ACTIVATION: '[Server] Account Awaiting Activation', ACCOUNT_ACTIVATION_SUCCESS: '[Server] Account Activation Success', ACCOUNT_ACTIVATION_FAILED: '[Server] Account Activation Failed', @@ -70,4 +71,4 @@ export const Types = { DECK_DELETE: '[Server] Deck Delete', // User games GAMES_OF_USER: '[Server] Games Of User', -}; +} as const; diff --git a/webclient/src/store/store.ts b/webclient/src/store/store.ts index e6ef5df3a..997d97661 100644 --- a/webclient/src/store/store.ts +++ b/webclient/src/store/store.ts @@ -1,9 +1,27 @@ -import { createStore, applyMiddleware } from 'redux'; -import thunk from 'redux-thunk'; +import { configureStore, isPlain } from '@reduxjs/toolkit'; +import { isMessage } from '@bufbuild/protobuf'; +import { useDispatch, useSelector } from 'react-redux'; import rootReducer from './rootReducer'; -const initialState = {}; +// Protobuf-es v2 messages are already plain objects (no class prototype, unlike v1). +// They carry $typeName (string, identifies the message) and $unknown (binary unknown +// fields) — both are serializable and harmless in Redux state. No conversion needed. +// Fields may include Uint8Array (bytes) and BigInt (int64/uint64), which fail Redux +// Toolkit’s default serializable check, so we extend it to accept these types. +function isSerializable(value: unknown): boolean { + return isPlain(value) || isMessage(value) || value instanceof Uint8Array || typeof value === 'bigint'; +} -const middleware: any = [thunk]; +export const store = configureStore({ + reducer: rootReducer, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + immutableCheck: { warnAfter: 128 }, + serializableCheck: { isSerializable, warnAfter: 128 }, + }), +}); +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; -export const store = createStore(rootReducer, initialState, applyMiddleware(...middleware)); +export const useAppDispatch = useDispatch.withTypes(); +export const useAppSelector = useSelector.withTypes(); diff --git a/webclient/src/types/constants.spec.ts b/webclient/src/types/constants.spec.ts index f95a16eaa..d324867dc 100644 --- a/webclient/src/types/constants.spec.ts +++ b/webclient/src/types/constants.spec.ts @@ -2,8 +2,6 @@ import { URL_REGEX, MESSAGE_SENDER_REGEX, MENTION_REGEX, - CARD_CALLOUT_REGEX, - CALLOUT_BOUNDARY_REGEX, } from './constants'; describe('RegEx', () => { diff --git a/webclient/src/types/deckList.ts b/webclient/src/types/deckList.ts index 9d7d792a6..63ef28f13 100644 --- a/webclient/src/types/deckList.ts +++ b/webclient/src/types/deckList.ts @@ -1,18 +1,9 @@ -export interface DeckList { - root: DeckStorageFolder; -} +import type { + ServerInfo_DeckStorage_File, ServerInfo_DeckStorage_Folder, ServerInfo_DeckStorage_TreeItem +} from 'generated/proto/serverinfo_deckstorage_pb'; +import type { Response_DeckList } from 'generated/proto/response_deck_list_pb'; -export interface DeckStorageFolder { - items: DeckStorageTreeItem[]; -} - -export interface DeckStorageFile { - creationTime: number; -} - -export interface DeckStorageTreeItem { - id: number; - name: string; - file: DeckStorageFile | null; - folder: DeckStorageFolder | null; -} +export type DeckList = Response_DeckList; +export type DeckStorageFolder = ServerInfo_DeckStorage_Folder; +export type DeckStorageFile = ServerInfo_DeckStorage_File; +export type DeckStorageTreeItem = ServerInfo_DeckStorage_TreeItem; diff --git a/webclient/src/types/forms.ts b/webclient/src/types/forms.ts deleted file mode 100644 index 421bc2615..000000000 --- a/webclient/src/types/forms.ts +++ /dev/null @@ -1,11 +0,0 @@ -export enum FormKey { - ADD_TO_BUDDIES = 'ADD_TO_BUDDIES', - ADD_TO_IGNORE = 'ADD_TO_IGNORE', - CARD_IMPORT = 'CARD_IMPORT', - CONNECT = 'CONNECT', - LOGIN = 'LOGIN', - RESET_PASSWORD_REQUEST = 'RESET_PASSWORD_REQUEST', - RESET_PASSWORD = 'RESET_PASSWORD', - REGISTER = 'REGISTER', - SEARCH_LOGS = 'SEARCH_LOGS', -} diff --git a/webclient/src/types/game.ts b/webclient/src/types/game.ts index f15f62118..120073f58 100644 --- a/webclient/src/types/game.ts +++ b/webclient/src/types/game.ts @@ -67,6 +67,17 @@ import type { ServerInfo_Player } from 'generated/proto/serverinfo_player_pb'; export { CardAttribute } from 'generated/proto/card_attributes_pb'; export { ServerInfo_Zone_ZoneType as ZoneType } from 'generated/proto/serverinfo_zone_pb'; +// ── Proto utility types ─────────────────────────────────────────────────────── + +/** + * Init shape for constructing protobuf messages via create(). + * Strips $typeName and $unknown branding, making all fields optional. + * Use for function parameters that feed into create(). + */ +export type ProtoInit = { + [K in keyof T as K extends '$typeName' | '$unknown' ? never : K]?: T[K]; +}; + // ── UI types (not proto mirrors) ────────────────────────────────────────────── export type Game = ServerInfo_Game & { @@ -165,34 +176,36 @@ export interface GameEventMeta { forcedByJudge: number; } -// ── Type aliases for generated command param types ──────────────────────────── +// ── Type aliases for generated command param types (init shapes) ────────────── +// These use ProtoInit<> because callers construct plain objects; +// the command functions internally call create(Schema, params). export type { CardToMove }; -export type MoveCardParams = Command_MoveCard; -export type DrawCardsParams = Command_DrawCards; -export type RollDieParams = Command_RollDie; -export type ShuffleParams = Command_Shuffle; -export type FlipCardParams = Command_FlipCard; -export type AttachCardParams = Command_AttachCard; -export type CreateTokenParams = Command_CreateToken; -export type SetCardAttrParams = Command_SetCardAttr; -export type SetCardCounterParams = Command_SetCardCounter; -export type IncCardCounterParams = Command_IncCardCounter; -export type RevealCardsParams = Command_RevealCards; -export type DumpZoneParams = Command_DumpZone; -export type ChangeZonePropertiesParams = Command_ChangeZoneProperties; -export type CreateArrowParams = Command_CreateArrow; -export type DeleteArrowParams = Command_DeleteArrow; -export type CreateCounterParams = Command_CreateCounter; -export type SetCounterParams = Command_SetCounter; -export type IncCounterParams = Command_IncCounter; -export type DelCounterParams = Command_DelCounter; -export type KickFromGameParams = Command_KickFromGame; -export type ReadyStartParams = Command_ReadyStart; -export type MulliganParams = Command_Mulligan; -export type DeckSelectParams = Command_DeckSelect; +export type MoveCardParams = ProtoInit; +export type DrawCardsParams = ProtoInit; +export type RollDieParams = ProtoInit; +export type ShuffleParams = ProtoInit; +export type FlipCardParams = ProtoInit; +export type AttachCardParams = ProtoInit; +export type CreateTokenParams = ProtoInit; +export type SetCardAttrParams = ProtoInit; +export type SetCardCounterParams = ProtoInit; +export type IncCardCounterParams = ProtoInit; +export type RevealCardsParams = ProtoInit; +export type DumpZoneParams = ProtoInit; +export type ChangeZonePropertiesParams = ProtoInit; +export type CreateArrowParams = ProtoInit; +export type DeleteArrowParams = ProtoInit; +export type CreateCounterParams = ProtoInit; +export type SetCounterParams = ProtoInit; +export type IncCounterParams = ProtoInit; +export type DelCounterParams = ProtoInit; +export type KickFromGameParams = ProtoInit; +export type ReadyStartParams = ProtoInit; +export type MulliganParams = ProtoInit; +export type DeckSelectParams = ProtoInit; export type MoveCardToZone = MoveCard_ToZone; -export type SetSideboardPlanParams = Command_SetSideboardPlan; -export type SetSideboardLockParams = Command_SetSideboardLock; -export type SetActivePhaseParams = Command_SetActivePhase; -export type GameSayParams = Command_GameSay; +export type SetSideboardPlanParams = ProtoInit; +export type SetSideboardLockParams = ProtoInit; +export type SetActivePhaseParams = ProtoInit; +export type GameSayParams = ProtoInit; diff --git a/webclient/src/types/index.ts b/webclient/src/types/index.ts index ded9962e5..c23171132 100644 --- a/webclient/src/types/index.ts +++ b/webclient/src/types/index.ts @@ -7,8 +7,6 @@ export * from './server'; export * from './sort'; export * from './user'; export * from './routes'; -export * from './sort'; -export * from './forms'; export * from './message'; export * from './settings'; export * from './languages'; diff --git a/webclient/src/types/moderator.ts b/webclient/src/types/moderator.ts index 5991ec6c1..0b6009b84 100644 --- a/webclient/src/types/moderator.ts +++ b/webclient/src/types/moderator.ts @@ -1,21 +1,7 @@ -export interface BanHistoryItem { - adminId: string; - adminName: string; - banTime: string; - banLength: string; - banReason: string; - visibleReason: string; -} +import type { ServerInfo_Ban } from 'generated/proto/serverinfo_ban_pb'; +import type { ServerInfo_Warning } from 'generated/proto/serverinfo_warning_pb'; +import type { Response_WarnList } from 'generated/proto/response_warn_list_pb'; -export interface WarnHistoryItem { - userName: string; - adminName: string; - reason: string; - timeOf: string; -} - -export interface WarnListItem { - warning: string; - userName: string; - userClientid: string; -} +export type BanHistoryItem = ServerInfo_Ban; +export type WarnHistoryItem = ServerInfo_Warning; +export type WarnListItem = Response_WarnList; diff --git a/webclient/src/types/room.ts b/webclient/src/types/room.ts index 9f18d2d4c..f4441f697 100644 --- a/webclient/src/types/room.ts +++ b/webclient/src/types/room.ts @@ -1,8 +1,10 @@ import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; +import type { Game } from './game'; export interface GametypeMap { [index: number]: string } export type Room = ServerInfo_Room & { gametypeMap: GametypeMap; + gameList: Game[]; order: number; }; diff --git a/webclient/src/types/server.ts b/webclient/src/types/server.ts index 305a5a810..4b74c75c1 100644 --- a/webclient/src/types/server.ts +++ b/webclient/src/types/server.ts @@ -1,3 +1,5 @@ +import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb'; + export interface ServerStatus { status: StatusEnum; description: string; @@ -109,16 +111,7 @@ export const KnownHosts = { [KnownHost.TETRARCH]: { port: 443, host: 'mtg.tetrarch.co/servatrice' }, } -export interface LogItem { - message: string; - senderId: string; - senderIp: string; - senderName: string; - targetId: string; - targetName: string; - targetType: string; - time: string; -} +export type LogItem = ServerInfo_ChatMessage; export interface LogGroups { room: LogItem[]; diff --git a/webclient/src/websocket/WebClient.spec.ts b/webclient/src/websocket/WebClient.spec.ts index b279132ea..4f27d5580 100644 --- a/webclient/src/websocket/WebClient.spec.ts +++ b/webclient/src/websocket/WebClient.spec.ts @@ -1,18 +1,22 @@ vi.mock('./services/WebSocketService', () => ({ - WebSocketService: vi.fn().mockImplementation(() => ({ - message$: { subscribe: vi.fn() }, - connect: vi.fn(), - testConnect: vi.fn(), - disconnect: vi.fn(), - })), + WebSocketService: vi.fn().mockImplementation(function WebSocketServiceImpl() { + return { + message$: { subscribe: vi.fn() }, + connect: vi.fn(), + testConnect: vi.fn(), + disconnect: vi.fn(), + }; + }), })); vi.mock('./services/ProtobufService', () => ({ - ProtobufService: vi.fn().mockImplementation(() => ({ - handleMessageEvent: vi.fn(), - sendKeepAliveCommand: vi.fn(), - resetCommands: vi.fn(), - })), + ProtobufService: vi.fn().mockImplementation(function ProtobufServiceImpl() { + return { + handleMessageEvent: vi.fn(), + sendKeepAliveCommand: vi.fn(), + resetCommands: vi.fn(), + }; + }), })); vi.mock('./persistence', () => ({ @@ -20,12 +24,17 @@ vi.mock('./persistence', () => ({ SessionPersistence: { clearStore: vi.fn(), initialized: vi.fn() }, })); +vi.mock('store', () => ({ + GameDispatch: { clearStore: vi.fn() }, +})); + import { WebClient } from './WebClient'; import { WebSocketService } from './services/WebSocketService'; import { ProtobufService } from './services/ProtobufService'; import { RoomPersistence, SessionPersistence } from './persistence'; import { StatusEnum } from 'types'; import { Subject } from 'rxjs'; +import { Mock } from 'vitest'; describe('WebClient', () => { let client: WebClient; @@ -33,18 +42,22 @@ describe('WebClient', () => { beforeEach(() => { vi.clearAllMocks(); - (ProtobufService as vi.Mock).mockImplementation(() => ({ - handleMessageEvent: vi.fn(), - sendKeepAliveCommand: vi.fn(), - resetCommands: vi.fn(), - })); + (ProtobufService as Mock).mockImplementation(function ProtobufServiceImpl() { + return { + handleMessageEvent: vi.fn(), + sendKeepAliveCommand: vi.fn(), + resetCommands: vi.fn(), + }; + }); messageSubject = new Subject(); - (WebSocketService as vi.Mock).mockImplementation(() => ({ - message$: messageSubject, - connect: vi.fn(), - testConnect: vi.fn(), - disconnect: vi.fn(), - })); + (WebSocketService as Mock).mockImplementation(function WebSocketServiceImpl() { + return { + message$: messageSubject, + connect: vi.fn(), + testConnect: vi.fn(), + disconnect: vi.fn(), + }; + }); // suppress console.log from constructor in non-test-env check vi.spyOn(console, 'log').mockImplementation(() => {}); client = new WebClient(); diff --git a/webclient/src/websocket/WebClient.ts b/webclient/src/websocket/WebClient.ts index e1a0fad56..302a0da3f 100644 --- a/webclient/src/websocket/WebClient.ts +++ b/webclient/src/websocket/WebClient.ts @@ -77,7 +77,7 @@ export class WebClient { } } - public keepAlive(pingReceived: Function) { + public keepAlive(pingReceived: () => void) { this.protobuf.sendKeepAliveCommand(pingReceived); } diff --git a/webclient/src/websocket/__mocks__/callbackHelpers.ts b/webclient/src/websocket/__mocks__/callbackHelpers.ts index 0aaa61184..cee6ffb87 100644 --- a/webclient/src/websocket/__mocks__/callbackHelpers.ts +++ b/webclient/src/websocket/__mocks__/callbackHelpers.ts @@ -7,7 +7,9 @@ * Defaults to 2 (ext, value, options). * Use 3 for sendRoomCommand (roomId, ext, value, options). */ -export function makeCallbackHelpers(mockFn: vi.Mock, optsArgIndex = 2) { +import { Mock } from 'vitest'; + +export function makeCallbackHelpers(mockFn: Mock, optsArgIndex = 2) { function getLastSendOpts() { const calls = mockFn.mock.calls; return calls[calls.length - 1]?.[optsArgIndex]; diff --git a/webclient/src/websocket/__mocks__/helpers.ts b/webclient/src/websocket/__mocks__/helpers.ts index c9d6d9973..ec05d87d2 100644 --- a/webclient/src/websocket/__mocks__/helpers.ts +++ b/webclient/src/websocket/__mocks__/helpers.ts @@ -9,7 +9,7 @@ export function makeMockWebSocketInstance() { return { send: vi.fn(), close: vi.fn(), - readyState: WebSocket.OPEN, + readyState: WebSocket.OPEN as number, binaryType: '' as BinaryType, onopen: null as any, onclose: null as any, @@ -18,12 +18,17 @@ export function makeMockWebSocketInstance() { }; } -/** Installs a mock WebSocket constructor on global. Returns the mock instance. */ +/** Installs a mock WebSocket constructor on global. Returns the mock instance and a cleanup function. */ export function installMockWebSocket() { + const originalWebSocket = (globalThis as any).WebSocket; const mockInstance = makeMockWebSocketInstance(); - const MockWS = vi.fn(() => mockInstance) as any; + const MockWS = vi.fn(function MockWebSocket() { + return mockInstance; + }) as any; MockWS.OPEN = 1; MockWS.CLOSED = 3; - (global as any).WebSocket = MockWS; - return { MockWS, mockInstance }; + (globalThis as any).WebSocket = MockWS; + return { MockWS, mockInstance, restore: () => { + (globalThis as any).WebSocket = originalWebSocket; + } }; } diff --git a/webclient/src/websocket/commands/admin/adminCommands.spec.ts b/webclient/src/websocket/commands/admin/adminCommands.spec.ts index 098db2597..fd1778e9f 100644 --- a/webclient/src/websocket/commands/admin/adminCommands.spec.ts +++ b/webclient/src/websocket/commands/admin/adminCommands.spec.ts @@ -21,8 +21,10 @@ import { reloadConfig } from './reloadConfig'; import { shutdownServer } from './shutdownServer'; import { updateServerMessage } from './updateServerMessage'; -const { getLastSendOpts, invokeOnSuccess } = makeCallbackHelpers( - BackendService.sendAdminCommand as vi.Mock, +import { Mock } from 'vitest'; + +const { invokeOnSuccess } = makeCallbackHelpers( + BackendService.sendAdminCommand as Mock, 2 ); diff --git a/webclient/src/websocket/commands/game/gameCommands.spec.ts b/webclient/src/websocket/commands/game/gameCommands.spec.ts index 5ac94fc9b..1daf3a7dc 100644 --- a/webclient/src/websocket/commands/game/gameCommands.spec.ts +++ b/webclient/src/websocket/commands/game/gameCommands.spec.ts @@ -72,8 +72,10 @@ vi.mock('../../services/BackendService', () => ({ const gameId = 1; +import { Mock } from 'vitest'; + beforeEach(() => { - (BackendService.sendGameCommand as vi.Mock).mockClear(); + (BackendService.sendGameCommand as Mock).mockClear(); }); describe('Game commands — delegate to BackendService.sendGameCommand', () => { diff --git a/webclient/src/websocket/commands/moderator/getWarnList.ts b/webclient/src/websocket/commands/moderator/getWarnList.ts index 3f7e178ed..49282811e 100644 --- a/webclient/src/websocket/commands/moderator/getWarnList.ts +++ b/webclient/src/websocket/commands/moderator/getWarnList.ts @@ -8,7 +8,7 @@ export function getWarnList(modName: string, userName: string, userClientid: str BackendService.sendModeratorCommand(Command_GetWarnList_ext, create(Command_GetWarnListSchema, { modName, userName, userClientid }), { responseExt: Response_WarnList_ext, onSuccess: (response) => { - ModeratorPersistence.warnListOptions(response.warning); + ModeratorPersistence.warnListOptions([response]); }, }); } diff --git a/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts b/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts index 576983942..2ca5eea7b 100644 --- a/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts +++ b/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts @@ -50,8 +50,10 @@ import { updateAdminNotes } from './updateAdminNotes'; import { viewLogHistory } from './viewLogHistory'; import { warnUser } from './warnUser'; -const { getLastSendOpts, invokeOnSuccess } = makeCallbackHelpers( - BackendService.sendModeratorCommand as vi.Mock, +import { Mock } from 'vitest'; + +const { invokeOnSuccess } = makeCallbackHelpers( + BackendService.sendModeratorCommand as Mock, 2 ); @@ -175,11 +177,11 @@ describe('getWarnList', () => { ); }); - it('onSuccess calls ModeratorPersistence.warnListOptions with warning', () => { + it('onSuccess calls ModeratorPersistence.warnListOptions with the response', () => { getWarnList('mod1', 'alice', 'US'); - const resp = { warning: ['w1', 'w2'] }; + const resp = { warning: ['w1', 'w2'], userName: 'alice', userClientid: 'US' }; invokeOnSuccess(resp, { responseCode: 0 }); - expect(ModeratorPersistence.warnListOptions).toHaveBeenCalledWith(['w1', 'w2']); + expect(ModeratorPersistence.warnListOptions).toHaveBeenCalledWith([resp]); }); }); diff --git a/webclient/src/websocket/commands/room/roomCommands.spec.ts b/webclient/src/websocket/commands/room/roomCommands.spec.ts index cf5693407..691455c56 100644 --- a/webclient/src/websocket/commands/room/roomCommands.spec.ts +++ b/webclient/src/websocket/commands/room/roomCommands.spec.ts @@ -21,8 +21,10 @@ import { joinGame } from './joinGame'; import { leaveRoom } from './leaveRoom'; import { roomSay } from './roomSay'; -const { getLastSendOpts, invokeOnSuccess } = makeCallbackHelpers( - BackendService.sendRoomCommand as vi.Mock, +import { Mock } from 'vitest'; + +const { invokeOnSuccess } = makeCallbackHelpers( + BackendService.sendRoomCommand as Mock, // sendRoomCommand(roomId, ext, value, options) — options at index 3 3 ); diff --git a/webclient/src/websocket/commands/session/deckList.ts b/webclient/src/websocket/commands/session/deckList.ts index dc526d5be..f1260dcfc 100644 --- a/webclient/src/websocket/commands/session/deckList.ts +++ b/webclient/src/websocket/commands/session/deckList.ts @@ -8,7 +8,9 @@ export function deckList(): void { BackendService.sendSessionCommand(Command_DeckList_ext, create(Command_DeckListSchema), { responseExt: Response_DeckList_ext, onSuccess: (response) => { - SessionPersistence.updateServerDecks(response); + if (response.root) { + SessionPersistence.updateServerDecks(response); + } }, }); } diff --git a/webclient/src/websocket/commands/session/deckUpload.ts b/webclient/src/websocket/commands/session/deckUpload.ts index fce574525..d3c3576b2 100644 --- a/webclient/src/websocket/commands/session/deckUpload.ts +++ b/webclient/src/websocket/commands/session/deckUpload.ts @@ -8,7 +8,9 @@ export function deckUpload(path: string, deckId: number, deckList: string): void BackendService.sendSessionCommand(Command_DeckUpload_ext, create(Command_DeckUploadSchema, { path, deckId, deckList }), { responseExt: Response_DeckUpload_ext, onSuccess: (response) => { - SessionPersistence.uploadServerDeck(path, response.newFile); + if (response.newFile) { + SessionPersistence.uploadServerDeck(path, response.newFile); + } }, }); } diff --git a/webclient/src/websocket/commands/session/joinRoom.ts b/webclient/src/websocket/commands/session/joinRoom.ts index 657acd3c7..371f98c16 100644 --- a/webclient/src/websocket/commands/session/joinRoom.ts +++ b/webclient/src/websocket/commands/session/joinRoom.ts @@ -8,7 +8,9 @@ export function joinRoom(roomId: number): void { BackendService.sendSessionCommand(Command_JoinRoom_ext, create(Command_JoinRoomSchema, { roomId }), { responseExt: Response_JoinRoom_ext, onSuccess: (response) => { - RoomPersistence.joinRoom(response.roomInfo); + if (response.roomInfo) { + RoomPersistence.joinRoom(response.roomInfo); + } }, }); } diff --git a/webclient/src/websocket/commands/session/ping.ts b/webclient/src/websocket/commands/session/ping.ts index 11a7ff4fc..a8bbc7b94 100644 --- a/webclient/src/websocket/commands/session/ping.ts +++ b/webclient/src/websocket/commands/session/ping.ts @@ -2,8 +2,8 @@ import { create } from '@bufbuild/protobuf'; import { BackendService } from '../../services/BackendService'; import { Command_Ping_ext, Command_PingSchema } from 'generated/proto/session_commands_pb'; -export function ping(pingReceived: Function): void { +export function ping(pingReceived: () => void): void { BackendService.sendSessionCommand(Command_Ping_ext, create(Command_PingSchema), { - onResponse: (raw) => pingReceived(raw), + onResponse: () => pingReceived(), }); } diff --git a/webclient/src/websocket/commands/session/register.ts b/webclient/src/websocket/commands/session/register.ts index 22d80d923..cb2020102 100644 --- a/webclient/src/websocket/commands/session/register.ts +++ b/webclient/src/websocket/commands/session/register.ts @@ -1,7 +1,7 @@ import { ServerRegisterParams } from 'store'; import { StatusEnum, WebSocketConnectOptions } from 'types'; -import { create } from '@bufbuild/protobuf'; +import { create, getExtension } from '@bufbuild/protobuf'; import type { MessageInitShape } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; import { BackendService } from '../../services/BackendService'; @@ -9,6 +9,7 @@ import { Command_Register_ext, Command_RegisterSchema } from 'generated/proto/se import { SessionPersistence } from '../../persistence'; import { hashPassword } from '../../utils'; import { Response_ResponseCode } from 'generated/proto/response_pb'; +import { Response_Register_ext } from 'generated/proto/response_register_pb'; import { login, disconnect, updateStatus } from './'; @@ -65,9 +66,12 @@ export function register(options: WebSocketConnectOptions, password?: string, pa [Response_ResponseCode.RespRegistrationDisabled]: () => onRegistrationError( () => SessionPersistence.registrationFailed('Registration is currently disabled') ), - [Response_ResponseCode.RespUserIsBanned]: (raw) => onRegistrationError( - () => SessionPersistence.registrationFailed(raw.reasonStr, raw.endTime) - ), + [Response_ResponseCode.RespUserIsBanned]: (raw) => { + const register = getExtension(raw, Response_Register_ext); + onRegistrationError( + () => SessionPersistence.registrationFailed(register.deniedReasonStr, Number(register.deniedEndTime)) + ); + }, }, onError: () => onRegistrationError( () => SessionPersistence.registrationFailed('Registration failed due to a server issue') diff --git a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts index fe4227c4a..d2ba2bd50 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts @@ -31,6 +31,7 @@ vi.mock('./', async () => { return makeSessionBarrelMock(); }); +import { Mock } from 'vitest'; import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; import { BackendService } from '../../services/BackendService'; import { SessionPersistence } from '../../persistence'; @@ -51,6 +52,9 @@ import { import { Response_ForgotPasswordRequest_ext } from 'generated/proto/response_forgotpasswordrequest_pb'; import { Response_Login_ext } from 'generated/proto/response_login_pb'; import { Response_PasswordSalt_ext } from 'generated/proto/response_password_salt_pb'; +import { Response_Register_ext, Response_RegisterSchema } from 'generated/proto/response_register_pb'; +import { create, setExtension } from '@bufbuild/protobuf'; +import { ResponseSchema } from 'generated/proto/response_pb'; import { connect } from './connect'; import { updateStatus } from './updateStatus'; import { login } from './login'; @@ -61,16 +65,16 @@ import { forgotPasswordRequest } from './forgotPasswordRequest'; import { forgotPasswordReset } from './forgotPasswordReset'; import { requestPasswordSalt } from './requestPasswordSalt'; -const { getLastSendOpts, invokeOnSuccess, invokeResponseCode, invokeOnError } = makeCallbackHelpers( - BackendService.sendSessionCommand as vi.Mock, +const { invokeOnSuccess, invokeResponseCode, invokeOnError } = makeCallbackHelpers( + BackendService.sendSessionCommand as Mock, 2 ); beforeEach(() => { vi.clearAllMocks(); - (hashPassword as vi.Mock).mockReturnValue('hashed_pw'); - (generateSalt as vi.Mock).mockReturnValue('randSalt'); - (passwordSaltSupported as vi.Mock).mockReturnValue(0); + (hashPassword as Mock).mockReturnValue('hashed_pw'); + (generateSalt as Mock).mockReturnValue('randSalt'); + (passwordSaltSupported as Mock).mockReturnValue(0); }); // ---------------------------------------------------------------- @@ -182,7 +186,7 @@ describe('login', () => { login({ userName: 'alice' } as any, 'secret'); const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } }; invokeOnSuccess(loginResp, { responseCode: 0 }); - const calledWith = (SessionPersistence.loginSuccessful as vi.Mock).mock.calls[0][0]; + const calledWith = (SessionPersistence.loginSuccessful as Mock).mock.calls[0][0]; expect(calledWith).not.toHaveProperty('password'); }); @@ -190,7 +194,7 @@ describe('login', () => { login({ userName: 'alice' } as any, 'pw', 'salt'); const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } }; invokeOnSuccess(loginResp, { responseCode: 0 }); - const calledWith = (SessionPersistence.loginSuccessful as vi.Mock).mock.calls[0][0]; + const calledWith = (SessionPersistence.loginSuccessful as Mock).mock.calls[0][0]; expect(calledWith).toHaveProperty('hashedPassword', 'hashed_pw'); }); @@ -347,9 +351,11 @@ describe('register', () => { expect(SessionPersistence.registrationFailed).toHaveBeenCalled(); }); - it('RespUserIsBanned calls registrationFailed with raw.reasonStr and raw.endTime', () => { + it('RespUserIsBanned calls registrationFailed with deniedReasonStr and deniedEndTime', () => { register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespUserIsBanned, { reasonStr: 'bad user', endTime: 9999 }); + const raw = create(ResponseSchema, { responseCode: Response_ResponseCode.RespUserIsBanned }); + setExtension(raw, Response_Register_ext, create(Response_RegisterSchema, { deniedReasonStr: 'bad user', deniedEndTime: 9999n })); + invokeResponseCode(Response_ResponseCode.RespUserIsBanned, raw); expect(SessionPersistence.registrationFailed).toHaveBeenCalledWith('bad user', 9999); }); diff --git a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts index 4cd8bdce4..d6b1e3744 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts @@ -31,12 +31,12 @@ vi.mock('./', async () => { return { ...(actual as any), ...makeSessionBarrelMock() }; }); +import { Mock } from 'vitest'; import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; import { BackendService } from '../../services/BackendService'; import { SessionPersistence } from '../../persistence'; import { RoomPersistence } from '../../persistence'; import webClient from '../../WebClient'; -import * as SessionCommands from './'; import { hashPassword, generateSalt, passwordSaltSupported } from '../../utils'; import { Command_AccountEdit_ext, @@ -95,15 +95,15 @@ import { replayGetCode } from './replayGetCode'; import { replaySubmitCode } from './replaySubmitCode'; const { invokeOnSuccess, invokeCallback } = makeCallbackHelpers( - BackendService.sendSessionCommand as vi.Mock, + BackendService.sendSessionCommand as Mock, 2 ); beforeEach(() => { vi.clearAllMocks(); - (hashPassword as vi.Mock).mockReturnValue('hashed_pw'); - (generateSalt as vi.Mock).mockReturnValue('randSalt'); - (passwordSaltSupported as vi.Mock).mockReturnValue(0); + (hashPassword as Mock).mockReturnValue('hashed_pw'); + (generateSalt as Mock).mockReturnValue('randSalt'); + (passwordSaltSupported as Mock).mockReturnValue(0); }); // ---------------------------------------------------------------- @@ -215,9 +215,9 @@ describe('deckList', () => { it('calls updateServerDecks on success', () => { deckList(); - const resp = { folders: [] }; - invokeOnSuccess(resp, { responseCode: 0 }); - expect(SessionPersistence.updateServerDecks).toHaveBeenCalledWith(resp); + const root = { items: [] }; + invokeOnSuccess({ root }, { responseCode: 0 }); + expect(SessionPersistence.updateServerDecks).toHaveBeenCalledWith({ root }); }); }); @@ -380,9 +380,8 @@ describe('ping', () => { it('calls pingReceived via onResponse', () => { const pingReceived = vi.fn(); ping(pingReceived); - const raw = {}; - invokeCallback('onResponse', raw); - expect(pingReceived).toHaveBeenCalledWith(raw); + invokeCallback('onResponse', {}); + expect(pingReceived).toHaveBeenCalled(); }); }); diff --git a/webclient/src/websocket/events/common/index.ts b/webclient/src/websocket/events/common/index.ts index 93d40e0e6..f0be28944 100644 --- a/webclient/src/websocket/events/common/index.ts +++ b/webclient/src/websocket/events/common/index.ts @@ -1,3 +1,3 @@ -import { ExtensionRegistry } from '../../services/ProtobufService'; +import { SessionExtensionRegistry } from '../../services/ProtobufService'; -export const CommonEvents: ExtensionRegistry = []; +export const CommonEvents: SessionExtensionRegistry = []; diff --git a/webclient/src/websocket/events/game/index.ts b/webclient/src/websocket/events/game/index.ts index 64c26b153..8c7963481 100644 --- a/webclient/src/websocket/events/game/index.ts +++ b/webclient/src/websocket/events/game/index.ts @@ -1,4 +1,4 @@ -import { ExtensionRegistry } from '../../services/ProtobufService'; +import { GameExtensionRegistry, makeGameEntry } from '../../services/ProtobufService'; import { attachCard } from './attachCard'; import { changeZoneProperties } from './changeZoneProperties'; import { createArrow } from './createArrow'; @@ -59,35 +59,35 @@ import { Event_DumpZone_ext } from 'generated/proto/event_dump_zone_pb'; import { Event_ChangeZoneProperties_ext } from 'generated/proto/event_change_zone_properties_pb'; import { Event_ReverseTurn_ext } from 'generated/proto/event_reverse_turn_pb'; -export const GameEvents: ExtensionRegistry = [ - [Event_Join_ext, joinGame], - [Event_Leave_ext, leaveGame], - [Event_GameClosed_ext, gameClosed], - [Event_GameHostChanged_ext, gameHostChanged], - [Event_Kicked_ext, kicked], - [Event_GameStateChanged_ext, gameStateChanged], - [Event_PlayerPropertiesChanged_ext, playerPropertiesChanged], - [Event_GameSay_ext, gameSay], - [Event_CreateArrow_ext, createArrow], - [Event_DeleteArrow_ext, deleteArrow], - [Event_CreateCounter_ext, createCounter], - [Event_SetCounter_ext, setCounter], - [Event_DelCounter_ext, delCounter], - [Event_DrawCards_ext, drawCards], - [Event_RevealCards_ext, revealCards], - [Event_Shuffle_ext, shuffle], - [Event_RollDie_ext, rollDie], - [Event_MoveCard_ext, moveCard], - [Event_FlipCard_ext, flipCard], - [Event_DestroyCard_ext, destroyCard], - [Event_AttachCard_ext, attachCard], - [Event_CreateToken_ext, createToken], - [Event_SetCardAttr_ext, setCardAttr], - [Event_SetCardCounter_ext, setCardCounter], - [Event_SetActivePlayer_ext, setActivePlayer], - [Event_SetActivePhase_ext, setActivePhase], - [Event_DumpZone_ext, dumpZone], - [Event_ChangeZoneProperties_ext, changeZoneProperties], - [Event_ReverseTurn_ext, reverseTurn], +export const GameEvents: GameExtensionRegistry = [ + makeGameEntry(Event_Join_ext, joinGame), + makeGameEntry(Event_Leave_ext, leaveGame), + makeGameEntry(Event_GameClosed_ext, gameClosed), + makeGameEntry(Event_GameHostChanged_ext, gameHostChanged), + makeGameEntry(Event_Kicked_ext, kicked), + makeGameEntry(Event_GameStateChanged_ext, gameStateChanged), + makeGameEntry(Event_PlayerPropertiesChanged_ext, playerPropertiesChanged), + makeGameEntry(Event_GameSay_ext, gameSay), + makeGameEntry(Event_CreateArrow_ext, createArrow), + makeGameEntry(Event_DeleteArrow_ext, deleteArrow), + makeGameEntry(Event_CreateCounter_ext, createCounter), + makeGameEntry(Event_SetCounter_ext, setCounter), + makeGameEntry(Event_DelCounter_ext, delCounter), + makeGameEntry(Event_DrawCards_ext, drawCards), + makeGameEntry(Event_RevealCards_ext, revealCards), + makeGameEntry(Event_Shuffle_ext, shuffle), + makeGameEntry(Event_RollDie_ext, rollDie), + makeGameEntry(Event_MoveCard_ext, moveCard), + makeGameEntry(Event_FlipCard_ext, flipCard), + makeGameEntry(Event_DestroyCard_ext, destroyCard), + makeGameEntry(Event_AttachCard_ext, attachCard), + makeGameEntry(Event_CreateToken_ext, createToken), + makeGameEntry(Event_SetCardAttr_ext, setCardAttr), + makeGameEntry(Event_SetCardCounter_ext, setCardCounter), + makeGameEntry(Event_SetActivePlayer_ext, setActivePlayer), + makeGameEntry(Event_SetActivePhase_ext, setActivePhase), + makeGameEntry(Event_DumpZone_ext, dumpZone), + makeGameEntry(Event_ChangeZoneProperties_ext, changeZoneProperties), + makeGameEntry(Event_ReverseTurn_ext, reverseTurn), ]; diff --git a/webclient/src/websocket/events/room/index.ts b/webclient/src/websocket/events/room/index.ts index 366d68476..799986dec 100644 --- a/webclient/src/websocket/events/room/index.ts +++ b/webclient/src/websocket/events/room/index.ts @@ -1,4 +1,4 @@ -import { ExtensionRegistry } from '../../services/ProtobufService'; +import { RoomExtensionRegistry, makeRoomEntry } from '../../services/ProtobufService'; import { joinRoom } from './joinRoom'; import { leaveRoom } from './leaveRoom'; @@ -12,11 +12,11 @@ import { Event_ListGames_ext } from 'generated/proto/event_list_games_pb'; import { Event_RemoveMessages_ext } from 'generated/proto/event_remove_messages_pb'; import { Event_RoomSay_ext } from 'generated/proto/event_room_say_pb'; -export const RoomEvents: ExtensionRegistry = [ - [Event_JoinRoom_ext, joinRoom], - [Event_LeaveRoom_ext, leaveRoom], - [Event_ListGames_ext, listGames], - [Event_RemoveMessages_ext, removeMessages], - [Event_RoomSay_ext, roomSay], +export const RoomEvents: RoomExtensionRegistry = [ + makeRoomEntry(Event_JoinRoom_ext, joinRoom), + makeRoomEntry(Event_LeaveRoom_ext, leaveRoom), + makeRoomEntry(Event_ListGames_ext, listGames), + makeRoomEntry(Event_RemoveMessages_ext, removeMessages), + makeRoomEntry(Event_RoomSay_ext, roomSay), ]; diff --git a/webclient/src/websocket/events/room/interfaces.ts b/webclient/src/websocket/events/room/interfaces.ts index cb7086555..6e8a61b01 100644 --- a/webclient/src/websocket/events/room/interfaces.ts +++ b/webclient/src/websocket/events/room/interfaces.ts @@ -2,10 +2,12 @@ import type { Event_JoinRoom } from 'generated/proto/event_join_room_pb'; import type { Event_LeaveRoom } from 'generated/proto/event_leave_room_pb'; import type { Event_ListGames } from 'generated/proto/event_list_games_pb'; import type { Event_RemoveMessages } from 'generated/proto/event_remove_messages_pb'; +import type { Event_RoomSay } from 'generated/proto/event_room_say_pb'; import type { RoomEvent as GeneratedRoomEvent } from 'generated/proto/room_event_pb'; export type JoinRoomData = Event_JoinRoom; export type LeaveRoomData = Event_LeaveRoom; export type ListGamesData = Event_ListGames; export type RemoveMessagesData = Event_RemoveMessages; +export type RoomSayData = Event_RoomSay; export type RoomEvent = GeneratedRoomEvent; diff --git a/webclient/src/websocket/events/room/roomEvents.spec.ts b/webclient/src/websocket/events/room/roomEvents.spec.ts index f2e4eee8f..60690d487 100644 --- a/webclient/src/websocket/events/room/roomEvents.spec.ts +++ b/webclient/src/websocket/events/room/roomEvents.spec.ts @@ -8,30 +8,37 @@ vi.mock('../../persistence', () => ({ }, })); +import { create } from '@bufbuild/protobuf'; import { RoomPersistence } from '../../persistence'; import { joinRoom } from './joinRoom'; import { leaveRoom } from './leaveRoom'; import { listGames } from './listGames'; import { removeMessages } from './removeMessages'; import { roomSay } from './roomSay'; +import { Event_JoinRoomSchema } from 'generated/proto/event_join_room_pb'; +import { Event_LeaveRoomSchema } from 'generated/proto/event_leave_room_pb'; +import { Event_ListGamesSchema } from 'generated/proto/event_list_games_pb'; +import { Event_RemoveMessagesSchema } from 'generated/proto/event_remove_messages_pb'; +import { Event_RoomSaySchema } from 'generated/proto/event_room_say_pb'; +import { RoomEventSchema } from 'generated/proto/room_event_pb'; -const makeRoomEvent = (roomId: number) => ({ roomId }) as any; +const makeRoomEvent = (roomId: number) => create(RoomEventSchema, { roomId }); beforeEach(() => vi.clearAllMocks()); describe('joinRoom room event', () => { it('calls RoomPersistence.userJoined with roomId and userInfo', () => { - const userInfo = { name: 'alice' } as any; - joinRoom({ userInfo }, makeRoomEvent(3)); - expect(RoomPersistence.userJoined).toHaveBeenCalledWith(3, userInfo); + const data = create(Event_JoinRoomSchema, { userInfo: { name: 'alice' } }); + joinRoom(data, makeRoomEvent(3)); + expect(RoomPersistence.userJoined).toHaveBeenCalledWith(3, data.userInfo); }); }); describe('leaveRoom room event', () => { it('calls RoomPersistence.userLeft with roomId and name', () => { - leaveRoom({ name: 'alice' }, makeRoomEvent(4)); + leaveRoom(create(Event_LeaveRoomSchema, { name: 'alice' }), makeRoomEvent(4)); expect(RoomPersistence.userLeft).toHaveBeenCalledWith(4, 'alice'); }); }); @@ -39,25 +46,29 @@ describe('leaveRoom room event', () => { describe('listGames room event', () => { it('calls RoomPersistence.updateGames with roomId and gameList', () => { - const gameList = [{ gameId: 1 }] as any; - listGames({ gameList }, makeRoomEvent(5)); - expect(RoomPersistence.updateGames).toHaveBeenCalledWith(5, gameList); + const data = create(Event_ListGamesSchema, { gameList: [{ gameId: 1 }] }); + listGames(data, makeRoomEvent(5)); + expect(RoomPersistence.updateGames).toHaveBeenCalledWith(5, data.gameList); }); }); describe('removeMessages room event', () => { it('calls RoomPersistence.removeMessages with roomId, name, amount', () => { - removeMessages({ name: 'bob', amount: 10 }, makeRoomEvent(6)); + removeMessages(create(Event_RemoveMessagesSchema, { name: 'bob', amount: 10 }), makeRoomEvent(6)); expect(RoomPersistence.removeMessages).toHaveBeenCalledWith(6, 'bob', 10); }); }); describe('roomSay room event', () => { + beforeEach(() => { + vi.useFakeTimers(); vi.setSystemTime(0); + }); + afterEach(() => vi.useRealTimers()); it('calls RoomPersistence.addMessage with roomId and message', () => { - const msg = { text: 'hello' } as any; - roomSay(msg, makeRoomEvent(7)); - expect(RoomPersistence.addMessage).toHaveBeenCalledWith(7, msg); + const data = create(Event_RoomSaySchema, { message: 'hello' }); + roomSay(data, makeRoomEvent(7)); + expect(RoomPersistence.addMessage).toHaveBeenCalledWith(7, { ...data, timeReceived: 0 }); }); }); diff --git a/webclient/src/websocket/events/room/roomSay.ts b/webclient/src/websocket/events/room/roomSay.ts index 9522248da..f6accd8a9 100644 --- a/webclient/src/websocket/events/room/roomSay.ts +++ b/webclient/src/websocket/events/room/roomSay.ts @@ -1,8 +1,9 @@ import { Message } from 'types'; import { RoomPersistence } from '../../persistence'; -import { RoomEvent } from './interfaces'; +import { RoomSayData, RoomEvent } from './interfaces'; -export function roomSay(message: Message, { roomId }: RoomEvent): void { +export function roomSay(data: RoomSayData, { roomId }: RoomEvent): void { + const message: Message = { ...data, timeReceived: Date.now() }; RoomPersistence.addMessage(roomId, message); } diff --git a/webclient/src/websocket/events/session/index.ts b/webclient/src/websocket/events/session/index.ts index 6c34f0dfd..84dc7ba7f 100644 --- a/webclient/src/websocket/events/session/index.ts +++ b/webclient/src/websocket/events/session/index.ts @@ -1,4 +1,4 @@ -import { ExtensionRegistry } from '../../services/ProtobufService'; +import { SessionExtensionRegistry, makeSessionEntry } from '../../services/ProtobufService'; import { addToList } from './addToList'; import { connectionClosed } from './connectionClosed'; import { listRooms } from './listRooms'; @@ -29,20 +29,20 @@ import { Event_UserJoined_ext } from 'generated/proto/event_user_joined_pb'; import { Event_UserLeft_ext } from 'generated/proto/event_user_left_pb'; import { Event_UserMessage_ext } from 'generated/proto/event_user_message_pb'; -export const SessionEvents: ExtensionRegistry = [ - [Event_AddToList_ext, addToList], - [Event_ConnectionClosed_ext, connectionClosed], - [Event_GameJoined_ext, gameJoined], - [Event_ListRooms_ext, listRooms], - [Event_NotifyUser_ext, notifyUser], - [Event_RemoveFromList_ext, removeFromList], - [Event_ReplayAdded_ext, replayAdded], - [Event_ServerCompleteList_ext, serverCompleteList], - [Event_ServerIdentification_ext, serverIdentification], - [Event_ServerMessage_ext, serverMessage], - [Event_ServerShutdown_ext, serverShutdown], - [Event_UserJoined_ext, userJoined], - [Event_UserLeft_ext, userLeft], - [Event_UserMessage_ext, userMessage], +export const SessionEvents: SessionExtensionRegistry = [ + makeSessionEntry(Event_AddToList_ext, addToList), + makeSessionEntry(Event_ConnectionClosed_ext, connectionClosed), + makeSessionEntry(Event_GameJoined_ext, gameJoined), + makeSessionEntry(Event_ListRooms_ext, listRooms), + makeSessionEntry(Event_NotifyUser_ext, notifyUser), + makeSessionEntry(Event_RemoveFromList_ext, removeFromList), + makeSessionEntry(Event_ReplayAdded_ext, replayAdded), + makeSessionEntry(Event_ServerCompleteList_ext, serverCompleteList), + makeSessionEntry(Event_ServerIdentification_ext, serverIdentification), + makeSessionEntry(Event_ServerMessage_ext, serverMessage), + makeSessionEntry(Event_ServerShutdown_ext, serverShutdown), + makeSessionEntry(Event_UserJoined_ext, userJoined), + makeSessionEntry(Event_UserLeft_ext, userLeft), + makeSessionEntry(Event_UserMessage_ext, userMessage), ]; diff --git a/webclient/src/websocket/events/session/sessionEvents.spec.ts b/webclient/src/websocket/events/session/sessionEvents.spec.ts index fae54fc4b..9f662cde7 100644 --- a/webclient/src/websocket/events/session/sessionEvents.spec.ts +++ b/webclient/src/websocket/events/session/sessionEvents.spec.ts @@ -52,7 +52,21 @@ vi.mock('../../utils', () => ({ })); import { WebSocketConnectReason } from 'types'; -import { Event_ConnectionClosed_CloseReason } from 'generated/proto/event_connection_closed_pb'; +import { create } from '@bufbuild/protobuf'; +import { Event_ConnectionClosed_CloseReason, Event_ConnectionClosedSchema } from 'generated/proto/event_connection_closed_pb'; +import { Event_GameJoinedSchema } from 'generated/proto/event_game_joined_pb'; +import { Event_NotifyUserSchema } from 'generated/proto/event_notify_user_pb'; +import { Event_ReplayAddedSchema } from 'generated/proto/event_replay_added_pb'; +import { Event_ServerCompleteListSchema } from 'generated/proto/event_server_complete_list_pb'; +import { Event_ServerMessageSchema } from 'generated/proto/event_server_message_pb'; +import { Event_ServerShutdownSchema } from 'generated/proto/event_server_shutdown_pb'; +import { Event_UserJoinedSchema } from 'generated/proto/event_user_joined_pb'; +import { Event_UserLeftSchema } from 'generated/proto/event_user_left_pb'; +import { Event_UserMessageSchema } from 'generated/proto/event_user_message_pb'; +import { Event_AddToListSchema } from 'generated/proto/event_add_to_list_pb'; +import { Event_RemoveFromListSchema } from 'generated/proto/event_remove_from_list_pb'; +import { Event_ListRoomsSchema } from 'generated/proto/event_list_rooms_pb'; +import { Event_ServerIdentificationSchema } from 'generated/proto/event_server_identification_pb'; import { SessionPersistence, RoomPersistence } from '../../persistence'; import webClient from '../../WebClient'; @@ -72,11 +86,12 @@ import { removeFromList } from './removeFromList'; import { listRooms } from './listRooms'; import { connectionClosed } from './connectionClosed'; import { serverIdentification } from './serverIdentification'; +import { Mock } from 'vitest'; beforeEach(() => { vi.clearAllMocks(); - (Utils.generateSalt as vi.Mock).mockReturnValue('newSalt'); - (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(0); + (Utils.generateSalt as Mock).mockReturnValue('newSalt'); + (Utils.passwordSaltSupported as Mock).mockReturnValue(0); }); // ---------------------------------------------------------------- @@ -85,7 +100,7 @@ beforeEach(() => { describe('gameJoined', () => { it('calls SessionPersistence.gameJoined', () => { - const data = { gameId: 1 } as any; + const data = create(Event_GameJoinedSchema, { playerId: 1 }); gameJoined(data); expect(SessionPersistence.gameJoined).toHaveBeenCalledWith(data); }); @@ -97,7 +112,7 @@ describe('gameJoined', () => { describe('notifyUser', () => { it('calls SessionPersistence.notifyUser', () => { - const data = { message: 'yo' } as any; + const data = create(Event_NotifyUserSchema, { warningReason: 'yo' }); notifyUser(data); expect(SessionPersistence.notifyUser).toHaveBeenCalledWith(data); }); @@ -109,8 +124,10 @@ describe('notifyUser', () => { describe('replayAdded', () => { it('calls SessionPersistence.replayAdded with matchInfo', () => { - replayAdded({ matchInfo: { id: 42 } } as any); - expect(SessionPersistence.replayAdded).toHaveBeenCalledWith({ id: 42 }); + const data = create(Event_ReplayAddedSchema); + data.matchInfo = { gameId: 42 } as any; + replayAdded(data); + expect(SessionPersistence.replayAdded).toHaveBeenCalledWith(data.matchInfo); }); }); @@ -120,9 +137,10 @@ describe('replayAdded', () => { describe('serverCompleteList', () => { it('calls SessionPersistence.updateUsers and RoomPersistence.updateRooms', () => { - serverCompleteList({ userList: ['u'], roomList: ['r'] } as any); - expect(SessionPersistence.updateUsers).toHaveBeenCalledWith(['u']); - expect(RoomPersistence.updateRooms).toHaveBeenCalledWith(['r']); + const data = create(Event_ServerCompleteListSchema, { userList: [], roomList: [] }); + serverCompleteList(data); + expect(SessionPersistence.updateUsers).toHaveBeenCalledWith(data.userList); + expect(RoomPersistence.updateRooms).toHaveBeenCalledWith(data.roomList); }); }); @@ -132,7 +150,7 @@ describe('serverCompleteList', () => { describe('serverMessage', () => { it('calls SessionPersistence.serverMessage with message', () => { - serverMessage({ message: 'hello server' }); + serverMessage(create(Event_ServerMessageSchema, { message: 'hello server' })); expect(SessionPersistence.serverMessage).toHaveBeenCalledWith('hello server'); }); }); @@ -143,7 +161,7 @@ describe('serverMessage', () => { describe('serverShutdown', () => { it('calls SessionPersistence.serverShutdown', () => { - const payload = { reason: 'maintenance' } as any; + const payload = create(Event_ServerShutdownSchema, { reason: 'maintenance' }); serverShutdown(payload); expect(SessionPersistence.serverShutdown).toHaveBeenCalledWith(payload); }); @@ -155,8 +173,10 @@ describe('serverShutdown', () => { describe('userJoined', () => { it('calls SessionPersistence.userJoined with userInfo', () => { - userJoined({ userInfo: { name: 'alice' } } as any); - expect(SessionPersistence.userJoined).toHaveBeenCalledWith({ name: 'alice' }); + const data = create(Event_UserJoinedSchema); + data.userInfo = { name: 'alice' } as any; + userJoined(data); + expect(SessionPersistence.userJoined).toHaveBeenCalledWith(data.userInfo); }); }); @@ -166,7 +186,7 @@ describe('userJoined', () => { describe('userLeft', () => { it('calls SessionPersistence.userLeft with name', () => { - userLeft({ name: 'bob' }); + userLeft(create(Event_UserLeftSchema, { name: 'bob' })); expect(SessionPersistence.userLeft).toHaveBeenCalledWith('bob'); }); }); @@ -177,7 +197,7 @@ describe('userLeft', () => { describe('userMessage', () => { it('calls SessionPersistence.userMessage', () => { - const payload = { userName: 'alice', message: 'hi' } as any; + const payload = create(Event_UserMessageSchema, { senderName: 'alice', message: 'hi' }); userMessage(payload); expect(SessionPersistence.userMessage).toHaveBeenCalledWith(payload); }); @@ -187,21 +207,27 @@ describe('userMessage', () => { // addToList // ---------------------------------------------------------------- describe('addToList', () => { - const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); - afterAll(() => logSpy.mockRestore()); + let logSpy: ReturnType; + beforeEach(() => { + logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + }); it('buddy list → addToBuddyList', () => { - addToList({ listName: 'buddy', userInfo: { name: 'alice' } } as any); - expect(SessionPersistence.addToBuddyList).toHaveBeenCalledWith({ name: 'alice' }); + const data = create(Event_AddToListSchema, { listName: 'buddy' }); + data.userInfo = { name: 'alice' } as any; + addToList(data); + expect(SessionPersistence.addToBuddyList).toHaveBeenCalledWith(data.userInfo); }); it('ignore list → addToIgnoreList', () => { - addToList({ listName: 'ignore', userInfo: { name: 'bob' } } as any); - expect(SessionPersistence.addToIgnoreList).toHaveBeenCalledWith({ name: 'bob' }); + const data = create(Event_AddToListSchema, { listName: 'ignore' }); + data.userInfo = { name: 'bob' } as any; + addToList(data); + expect(SessionPersistence.addToIgnoreList).toHaveBeenCalledWith(data.userInfo); }); it('unknown list → console.log', () => { - addToList({ listName: 'unknown', userInfo: {} } as any); + addToList(create(Event_AddToListSchema, { listName: 'unknown' })); expect(logSpy).toHaveBeenCalled(); }); }); @@ -212,18 +238,18 @@ describe('addToList', () => { describe('removeFromList', () => { it('buddy list → removeFromBuddyList', () => { - removeFromList({ listName: 'buddy', userName: 'alice' } as any); + removeFromList(create(Event_RemoveFromListSchema, { listName: 'buddy', userName: 'alice' })); expect(SessionPersistence.removeFromBuddyList).toHaveBeenCalledWith('alice'); }); it('ignore list → removeFromIgnoreList', () => { - removeFromList({ listName: 'ignore', userName: 'bob' } as any); + removeFromList(create(Event_RemoveFromListSchema, { listName: 'ignore', userName: 'bob' })); expect(SessionPersistence.removeFromIgnoreList).toHaveBeenCalledWith('bob'); }); it('unknown list → console.log', () => { const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); - removeFromList({ listName: 'other', userName: 'x' } as any); + removeFromList(create(Event_RemoveFromListSchema, { listName: 'other', userName: 'x' })); expect(logSpy).toHaveBeenCalled(); logSpy.mockRestore(); }); @@ -235,19 +261,19 @@ describe('removeFromList', () => { describe('listRooms', () => { it('calls RoomPersistence.updateRooms', () => { - listRooms({ roomList: [] }); + listRooms(create(Event_ListRoomsSchema, { roomList: [] })); expect(RoomPersistence.updateRooms).toHaveBeenCalledWith([]); }); it('does not call joinRoom when autojoinrooms is false', () => { (webClient as any).clientOptions = { autojoinrooms: false }; - listRooms({ roomList: [{ autoJoin: true, roomId: 1 }] } as any); + listRooms(create(Event_ListRoomsSchema, { roomList: [{ autoJoin: true, roomId: 1 }] as any[] })); expect(SessionCmds.joinRoom).not.toHaveBeenCalled(); }); it('calls joinRoom for autoJoin rooms when autojoinrooms is true', () => { (webClient as any).clientOptions = { autojoinrooms: true }; - listRooms({ roomList: [{ autoJoin: true, roomId: 2 }, { autoJoin: false, roomId: 3 }] } as any); + listRooms(create(Event_ListRoomsSchema, { roomList: [{ autoJoin: true, roomId: 2 }, { autoJoin: false, roomId: 3 }] as any[] })); expect(SessionCmds.joinRoom).toHaveBeenCalledTimes(1); expect(SessionCmds.joinRoom).toHaveBeenCalledWith(2); }); @@ -259,12 +285,12 @@ describe('listRooms', () => { describe('connectionClosed', () => { it('uses reasonStr when provided', () => { - connectionClosed({ reason: 0, reasonStr: 'custom' } as any); + connectionClosed(create(Event_ConnectionClosedSchema, { reason: 0, reasonStr: 'custom' })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'custom'); }); it('USER_LIMIT_REACHED → specific message', () => { - connectionClosed({ reason: Event_ConnectionClosed_CloseReason.USER_LIMIT_REACHED } as any); + connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.USER_LIMIT_REACHED })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith( expect.anything(), expect.stringContaining('maximum user capacity') @@ -272,42 +298,42 @@ describe('connectionClosed', () => { }); it('TOO_MANY_CONNECTIONS → specific message', () => { - connectionClosed({ reason: Event_ConnectionClosed_CloseReason.TOO_MANY_CONNECTIONS } as any); + connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.TOO_MANY_CONNECTIONS })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('too many concurrent')); }); it('BANNED → specific message', () => { - connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED } as any); + connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); }); it('DEMOTED → specific message', () => { - connectionClosed({ reason: Event_ConnectionClosed_CloseReason.DEMOTED } as any); + connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.DEMOTED })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('demoted')); }); it('SERVER_SHUTDOWN → specific message', () => { - connectionClosed({ reason: Event_ConnectionClosed_CloseReason.SERVER_SHUTDOWN } as any); + connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.SERVER_SHUTDOWN })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('shutdown')); }); it('USERNAMEINVALID → specific message', () => { - connectionClosed({ reason: Event_ConnectionClosed_CloseReason.USERNAMEINVALID } as any); + connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.USERNAMEINVALID })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('username')); }); it('LOGGEDINELSEWERE → specific message', () => { - connectionClosed({ reason: Event_ConnectionClosed_CloseReason.LOGGEDINELSEWERE } as any); + connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.LOGGEDINELSEWERE })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('logged out')); }); it('OTHER → "Unknown reason"', () => { - connectionClosed({ reason: Event_ConnectionClosed_CloseReason.OTHER } as any); + connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.OTHER })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'Unknown reason'); }); it('BANNED with valid positive endTime → shows formatted date', () => { - connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 1700000000 } as any); + connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 1700000000 })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith( expect.anything(), expect.stringContaining('You are banned until') @@ -315,27 +341,28 @@ describe('connectionClosed', () => { }); it('BANNED with endTime = 0 → shows generic banned message', () => { - connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 0 } as any); + connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 0 })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); }); it('BANNED with endTime = -1 → shows generic banned message', () => { - connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: -1 } as any); + connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: -1 })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); }); it('BANNED with endTime = NaN → shows generic banned message', () => { - connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: NaN } as any); + connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: NaN })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); }); it('BANNED with endTime = Infinity → shows generic banned message', () => { - connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: Infinity } as any); + connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: Infinity })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); }); it('BANNED with reasonStr → uses reasonStr regardless of endTime', () => { - connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 0, reasonStr: 'custom ban reason' } as any); + connectionClosed(create(Event_ConnectionClosedSchema, + { reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 0, reasonStr: 'custom ban reason' })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'custom ban reason'); }); }); @@ -351,15 +378,17 @@ describe('serverIdentification', () => { }); it('disconnects when protocolVersion mismatches', () => { - serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 99, serverOptions: 0 } as any); + serverIdentification(create(Event_ServerIdentificationSchema, + { serverName: 's', serverVersion: '1', protocolVersion: 99, serverOptions: 0 })); expect(SessionCmds.updateStatus).toHaveBeenCalled(); expect(SessionCmds.disconnect).toHaveBeenCalled(); }); it('LOGIN reason without salt → calls login with password as separate param', () => { (webClient as any).options = { reason: WebSocketConnectReason.LOGIN, password: 'secret' }; - (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(0); - serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); + (Utils.passwordSaltSupported as Mock).mockReturnValue(0); + serverIdentification(create(Event_ServerIdentificationSchema, + { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 })); expect(SessionCmds.login).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), 'secret' @@ -368,8 +397,9 @@ describe('serverIdentification', () => { it('LOGIN reason with salt → calls requestPasswordSalt with password as separate param', () => { (webClient as any).options = { reason: WebSocketConnectReason.LOGIN, password: 'secret' }; - (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(1); - serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any); + (Utils.passwordSaltSupported as Mock).mockReturnValue(1); + serverIdentification(create(Event_ServerIdentificationSchema, + { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 })); expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), 'secret' @@ -378,8 +408,9 @@ describe('serverIdentification', () => { it('REGISTER reason without salt → calls register with password and null salt', () => { (webClient as any).options = { reason: WebSocketConnectReason.REGISTER, password: 'secret' }; - (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(0); - serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); + (Utils.passwordSaltSupported as Mock).mockReturnValue(0); + serverIdentification(create(Event_ServerIdentificationSchema, + { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 })); expect(SessionCmds.register).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), 'secret', @@ -389,8 +420,9 @@ describe('serverIdentification', () => { it('REGISTER reason with salt → calls register with password and generated salt', () => { (webClient as any).options = { reason: WebSocketConnectReason.REGISTER, password: 'secret' }; - (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(1); - serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any); + (Utils.passwordSaltSupported as Mock).mockReturnValue(1); + serverIdentification(create(Event_ServerIdentificationSchema, + { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 })); expect(SessionCmds.register).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), 'secret', @@ -400,8 +432,9 @@ describe('serverIdentification', () => { it('ACTIVATE_ACCOUNT reason without salt → calls activate with password as separate param', () => { (webClient as any).options = { reason: WebSocketConnectReason.ACTIVATE_ACCOUNT, password: 'secret' }; - (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(0); - serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); + (Utils.passwordSaltSupported as Mock).mockReturnValue(0); + serverIdentification(create(Event_ServerIdentificationSchema, + { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 })); expect(SessionCmds.activate).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), 'secret' @@ -410,8 +443,9 @@ describe('serverIdentification', () => { it('ACTIVATE_ACCOUNT reason with salt → calls requestPasswordSalt with password as separate param', () => { (webClient as any).options = { reason: WebSocketConnectReason.ACTIVATE_ACCOUNT, password: 'secret' }; - (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(1); - serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any); + (Utils.passwordSaltSupported as Mock).mockReturnValue(1); + serverIdentification(create(Event_ServerIdentificationSchema, + { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 })); expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), 'secret' @@ -420,20 +454,23 @@ describe('serverIdentification', () => { it('PASSWORD_RESET_REQUEST reason → calls forgotPasswordRequest', () => { (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET_REQUEST }; - serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); + serverIdentification(create(Event_ServerIdentificationSchema, + { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 })); expect(SessionCmds.forgotPasswordRequest).toHaveBeenCalled(); }); it('PASSWORD_RESET_CHALLENGE reason → calls forgotPasswordChallenge', () => { (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET_CHALLENGE }; - serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); + serverIdentification(create(Event_ServerIdentificationSchema, + { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 })); expect(SessionCmds.forgotPasswordChallenge).toHaveBeenCalled(); }); it('PASSWORD_RESET reason without salt → calls forgotPasswordReset with newPassword as separate param', () => { (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET, newPassword: 'newpw' }; - (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(0); - serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); + (Utils.passwordSaltSupported as Mock).mockReturnValue(0); + serverIdentification(create(Event_ServerIdentificationSchema, + { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 })); expect(SessionCmds.forgotPasswordReset).toHaveBeenCalledWith( expect.not.objectContaining({ newPassword: expect.anything() }), 'newpw' @@ -442,8 +479,9 @@ describe('serverIdentification', () => { it('PASSWORD_RESET reason with salt → calls requestPasswordSalt with newPassword as separate param', () => { (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET, newPassword: 'newpw' }; - (Utils.passwordSaltSupported as vi.Mock).mockReturnValue(1); - serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any); + (Utils.passwordSaltSupported as Mock).mockReturnValue(1); + serverIdentification(create(Event_ServerIdentificationSchema, + { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 })); expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith( expect.not.objectContaining({ newPassword: expect.anything() }), undefined, @@ -453,14 +491,16 @@ describe('serverIdentification', () => { it('unknown reason → updateStatus DISCONNECTED and disconnect', () => { (webClient as any).options = { reason: 999 }; - serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any); + serverIdentification(create(Event_ServerIdentificationSchema, + { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 })); expect(SessionCmds.updateStatus).toHaveBeenCalled(); expect(SessionCmds.disconnect).toHaveBeenCalled(); }); it('updates webClient.options to empty and calls SessionPersistence.updateInfo', () => { (webClient as any).options = { reason: WebSocketConnectReason.LOGIN }; - serverIdentification({ serverName: 'myServer', serverVersion: '2.0', protocolVersion: 14, serverOptions: 0 } as any); + serverIdentification(create(Event_ServerIdentificationSchema, + { serverName: 'myServer', serverVersion: '2.0', protocolVersion: 14, serverOptions: 0 })); expect(SessionPersistence.updateInfo).toHaveBeenCalledWith('myServer', '2.0'); expect((webClient as any).options).toEqual({}); }); diff --git a/webclient/src/websocket/persistence/GamePersistence.spec.ts b/webclient/src/websocket/persistence/GamePersistence.spec.ts index d6983e5eb..50d8c6539 100644 --- a/webclient/src/websocket/persistence/GamePersistence.spec.ts +++ b/webclient/src/websocket/persistence/GamePersistence.spec.ts @@ -1,3 +1,4 @@ +import { create } from '@bufbuild/protobuf'; import { GamePersistence } from './GamePersistence'; vi.mock('store', () => ({ @@ -34,19 +35,40 @@ vi.mock('store', () => ({ }, })); +import { Event_GameStateChangedSchema } from 'generated/proto/event_game_state_changed_pb'; +import { Event_MoveCardSchema } from 'generated/proto/event_move_card_pb'; +import { Event_FlipCardSchema } from 'generated/proto/event_flip_card_pb'; +import { Event_DestroyCardSchema } from 'generated/proto/event_destroy_card_pb'; +import { Event_AttachCardSchema } from 'generated/proto/event_attach_card_pb'; +import { Event_CreateTokenSchema } from 'generated/proto/event_create_token_pb'; +import { Event_SetCardAttrSchema } from 'generated/proto/event_set_card_attr_pb'; +import { Event_SetCardCounterSchema } from 'generated/proto/event_set_card_counter_pb'; +import { Event_CreateArrowSchema } from 'generated/proto/event_create_arrow_pb'; +import { Event_DeleteArrowSchema } from 'generated/proto/event_delete_arrow_pb'; +import { Event_CreateCounterSchema } from 'generated/proto/event_create_counter_pb'; +import { Event_SetCounterSchema } from 'generated/proto/event_set_counter_pb'; +import { Event_DelCounterSchema } from 'generated/proto/event_del_counter_pb'; +import { Event_DrawCardsSchema } from 'generated/proto/event_draw_cards_pb'; +import { Event_RevealCardsSchema } from 'generated/proto/event_reveal_cards_pb'; +import { Event_ShuffleSchema } from 'generated/proto/event_shuffle_pb'; +import { Event_RollDieSchema } from 'generated/proto/event_roll_die_pb'; +import { Event_DumpZoneSchema } from 'generated/proto/event_dump_zone_pb'; +import { Event_ChangeZonePropertiesSchema } from 'generated/proto/event_change_zone_properties_pb'; +import { ServerInfo_PlayerPropertiesSchema } from 'generated/proto/serverinfo_playerproperties_pb'; + import { GameDispatch } from 'store'; beforeEach(() => vi.clearAllMocks()); describe('GamePersistence', () => { it('gameStateChanged dispatches via GameDispatch', () => { - const data = { playerList: [] } as any; + const data = create(Event_GameStateChangedSchema, { playerList: [] }); GamePersistence.gameStateChanged(5, data); expect(GameDispatch.gameStateChanged).toHaveBeenCalledWith(5, data); }); it('playerJoined dispatches via GameDispatch', () => { - const data = { playerId: 1 } as any; + const data = create(ServerInfo_PlayerPropertiesSchema, { playerId: 1 }); GamePersistence.playerJoined(5, data); expect(GameDispatch.playerJoined).toHaveBeenCalledWith(5, data); }); @@ -57,7 +79,7 @@ describe('GamePersistence', () => { }); it('playerPropertiesChanged dispatches via GameDispatch', () => { - const props = { playerId: 2 } as any; + const props = create(ServerInfo_PlayerPropertiesSchema, { playerId: 2 }); GamePersistence.playerPropertiesChanged(5, 2, props); expect(GameDispatch.playerPropertiesChanged).toHaveBeenCalledWith(5, 2, props); }); @@ -83,97 +105,97 @@ describe('GamePersistence', () => { }); it('cardMoved dispatches via GameDispatch', () => { - const data = { cardId: 3 } as any; + const data = create(Event_MoveCardSchema, { cardId: 3 }); GamePersistence.cardMoved(5, 1, data); expect(GameDispatch.cardMoved).toHaveBeenCalledWith(5, 1, data); }); it('cardFlipped dispatches via GameDispatch', () => { - const data = { cardId: 3 } as any; + const data = create(Event_FlipCardSchema, { cardId: 3 }); GamePersistence.cardFlipped(5, 1, data); expect(GameDispatch.cardFlipped).toHaveBeenCalledWith(5, 1, data); }); it('cardDestroyed dispatches via GameDispatch', () => { - const data = { cardId: 3 } as any; + const data = create(Event_DestroyCardSchema, { cardId: 3 }); GamePersistence.cardDestroyed(5, 1, data); expect(GameDispatch.cardDestroyed).toHaveBeenCalledWith(5, 1, data); }); it('cardAttached dispatches via GameDispatch', () => { - const data = { cardId: 3 } as any; + const data = create(Event_AttachCardSchema, { cardId: 3 }); GamePersistence.cardAttached(5, 1, data); expect(GameDispatch.cardAttached).toHaveBeenCalledWith(5, 1, data); }); it('tokenCreated dispatches via GameDispatch', () => { - const data = { cardId: 3 } as any; + const data = create(Event_CreateTokenSchema, { cardId: 3 }); GamePersistence.tokenCreated(5, 1, data); expect(GameDispatch.tokenCreated).toHaveBeenCalledWith(5, 1, data); }); it('cardAttrChanged dispatches via GameDispatch', () => { - const data = { cardId: 3 } as any; + const data = create(Event_SetCardAttrSchema, { cardId: 3 }); GamePersistence.cardAttrChanged(5, 1, data); expect(GameDispatch.cardAttrChanged).toHaveBeenCalledWith(5, 1, data); }); it('cardCounterChanged dispatches via GameDispatch', () => { - const data = { cardId: 3 } as any; + const data = create(Event_SetCardCounterSchema, { cardId: 3 }); GamePersistence.cardCounterChanged(5, 1, data); expect(GameDispatch.cardCounterChanged).toHaveBeenCalledWith(5, 1, data); }); it('arrowCreated dispatches via GameDispatch', () => { - const data = { arrowInfo: {} } as any; + const data = create(Event_CreateArrowSchema, {}); GamePersistence.arrowCreated(5, 1, data); expect(GameDispatch.arrowCreated).toHaveBeenCalledWith(5, 1, data); }); it('arrowDeleted dispatches via GameDispatch', () => { - const data = { arrowId: 9 }; + const data = create(Event_DeleteArrowSchema, { arrowId: 9 }); GamePersistence.arrowDeleted(5, 1, data); expect(GameDispatch.arrowDeleted).toHaveBeenCalledWith(5, 1, data); }); it('counterCreated dispatches via GameDispatch', () => { - const data = { counterInfo: {} } as any; + const data = create(Event_CreateCounterSchema, {}); GamePersistence.counterCreated(5, 1, data); expect(GameDispatch.counterCreated).toHaveBeenCalledWith(5, 1, data); }); it('counterSet dispatches via GameDispatch', () => { - const data = { counterId: 1, value: 20 }; + const data = create(Event_SetCounterSchema, { counterId: 1, value: 20 }); GamePersistence.counterSet(5, 1, data); expect(GameDispatch.counterSet).toHaveBeenCalledWith(5, 1, data); }); it('counterDeleted dispatches via GameDispatch', () => { - const data = { counterId: 1 }; + const data = create(Event_DelCounterSchema, { counterId: 1 }); GamePersistence.counterDeleted(5, 1, data); expect(GameDispatch.counterDeleted).toHaveBeenCalledWith(5, 1, data); }); it('cardsDrawn dispatches via GameDispatch', () => { - const data = { number: 2, cards: [] } as any; + const data = create(Event_DrawCardsSchema, { number: 2, cards: [] }); GamePersistence.cardsDrawn(5, 1, data); expect(GameDispatch.cardsDrawn).toHaveBeenCalledWith(5, 1, data); }); it('cardsRevealed dispatches via GameDispatch', () => { - const data = { zoneName: 'hand', cards: [] } as any; + const data = create(Event_RevealCardsSchema, { zoneName: 'hand', cards: [] }); GamePersistence.cardsRevealed(5, 1, data); expect(GameDispatch.cardsRevealed).toHaveBeenCalledWith(5, 1, data); }); it('zoneShuffled dispatches via GameDispatch', () => { - const data = { zoneName: 'deck' } as any; + const data = create(Event_ShuffleSchema, { zoneName: 'deck' }); GamePersistence.zoneShuffled(5, 1, data); expect(GameDispatch.zoneShuffled).toHaveBeenCalledWith(5, 1, data); }); it('dieRolled dispatches via GameDispatch', () => { - const data = { die: 6, result: 4 } as any; + const data = create(Event_RollDieSchema, { sides: 6, value: 4 }); GamePersistence.dieRolled(5, 1, data); expect(GameDispatch.dieRolled).toHaveBeenCalledWith(5, 1, data); }); @@ -194,13 +216,13 @@ describe('GamePersistence', () => { }); it('zoneDumped dispatches via GameDispatch', () => { - const data = { zoneName: 'hand' } as any; + const data = create(Event_DumpZoneSchema, { zoneName: 'hand' }); GamePersistence.zoneDumped(5, 1, data); expect(GameDispatch.zoneDumped).toHaveBeenCalledWith(5, 1, data); }); it('zonePropertiesChanged dispatches via GameDispatch', () => { - const data = { zoneName: 'hand', alwaysRevealTopCard: true } as any; + const data = create(Event_ChangeZonePropertiesSchema, { zoneName: 'hand', alwaysRevealTopCard: true }); GamePersistence.zonePropertiesChanged(5, 1, data); expect(GameDispatch.zonePropertiesChanged).toHaveBeenCalledWith(5, 1, data); }); diff --git a/webclient/src/websocket/persistence/ModeratorPersistence.spec.ts b/webclient/src/websocket/persistence/ModeratorPersistence.spec.ts index 6c0a96be7..bd2971e10 100644 --- a/webclient/src/websocket/persistence/ModeratorPersistence.spec.ts +++ b/webclient/src/websocket/persistence/ModeratorPersistence.spec.ts @@ -13,20 +13,11 @@ vi.mock('store', () => ({ }, })); -vi.mock('../utils/NormalizeService', () => ({ - __esModule: true, - default: { - normalizeLogs: vi.fn((logs: any) => ({ normalized: logs })), - }, -})); - import { ModeratorPersistence } from './ModeratorPersistence'; import { ServerDispatch } from 'store'; -import NormalizeService from '../utils/NormalizeService'; beforeEach(() => { vi.clearAllMocks(); - (NormalizeService.normalizeLogs as vi.Mock).mockImplementation((logs: any) => ({ normalized: logs })); }); describe('ModeratorPersistence', () => { @@ -40,11 +31,10 @@ describe('ModeratorPersistence', () => { expect(ServerDispatch.banHistory).toHaveBeenCalledWith('alice', []); }); - it('viewLogs normalizes logs and dispatches', () => { + it('viewLogs dispatches raw logs', () => { const logs = [{ targetType: 'room' }] as any; ModeratorPersistence.viewLogs(logs); - expect(NormalizeService.normalizeLogs).toHaveBeenCalledWith(logs); - expect(ServerDispatch.viewLogs).toHaveBeenCalledWith({ normalized: logs }); + expect(ServerDispatch.viewLogs).toHaveBeenCalledWith(logs); }); it('warnHistory passes userName and warnHistory', () => { diff --git a/webclient/src/websocket/persistence/ModeratorPersistence.ts b/webclient/src/websocket/persistence/ModeratorPersistence.ts index d1b991fa0..f8993451a 100644 --- a/webclient/src/websocket/persistence/ModeratorPersistence.ts +++ b/webclient/src/websocket/persistence/ModeratorPersistence.ts @@ -1,8 +1,6 @@ import { ServerDispatch } from 'store'; import { BanHistoryItem, LogItem, WarnHistoryItem, WarnListItem } from 'types'; -import NormalizeService from '../utils/NormalizeService'; - export class ModeratorPersistence { static banFromServer(userName: string): void { ServerDispatch.banFromServer(userName); @@ -13,7 +11,7 @@ export class ModeratorPersistence { } static viewLogs(logs: LogItem[]): void { - ServerDispatch.viewLogs(NormalizeService.normalizeLogs(logs)); + ServerDispatch.viewLogs(logs); } static warnHistory(userName: string, warnHistory: WarnHistoryItem[]): void { diff --git a/webclient/src/websocket/persistence/RoomPersistence.spec.ts b/webclient/src/websocket/persistence/RoomPersistence.spec.ts index 9e9133b58..61bc5e7f0 100644 --- a/webclient/src/websocket/persistence/RoomPersistence.spec.ts +++ b/webclient/src/websocket/persistence/RoomPersistence.spec.ts @@ -1,5 +1,4 @@ vi.mock('store', () => ({ - store: { getState: vi.fn().mockReturnValue({}) }, RoomsDispatch: { clearStore: vi.fn(), joinRoom: vi.fn(), @@ -13,23 +12,10 @@ vi.mock('store', () => ({ gameCreated: vi.fn(), joinedGame: vi.fn(), }, - RoomsSelectors: { - getRoom: vi.fn(), - }, -})); - -vi.mock('../utils/NormalizeService', () => ({ - __esModule: true, - default: { - normalizeRoomInfo: vi.fn(), - normalizeGameObject: vi.fn(), - normalizeUserMessage: vi.fn(), - }, })); import { RoomPersistence } from './RoomPersistence'; -import { store, RoomsDispatch, RoomsSelectors } from 'store'; -import NormalizeService from '../utils/NormalizeService'; +import { RoomsDispatch } from 'store'; beforeEach(() => { vi.clearAllMocks(); @@ -41,10 +27,9 @@ describe('RoomPersistence', () => { expect(RoomsDispatch.clearStore).toHaveBeenCalled(); }); - it('joinRoom normalizes and dispatches', () => { + it('joinRoom dispatches raw roomInfo', () => { const room = { roomId: 1 } as any; RoomPersistence.joinRoom(room); - expect(NormalizeService.normalizeRoomInfo).toHaveBeenCalledWith(room); expect(RoomsDispatch.joinRoom).toHaveBeenCalledWith(room); }); @@ -53,34 +38,19 @@ describe('RoomPersistence', () => { expect(RoomsDispatch.leaveRoom).toHaveBeenCalledWith(5); }); - it('updateRooms -> RoomsDispatch.updateRooms', () => { - RoomPersistence.updateRooms([]); - expect(RoomsDispatch.updateRooms).toHaveBeenCalledWith([]); + it('updateRooms dispatches raw rooms', () => { + const rooms = [{ roomId: 1 }] as any; + RoomPersistence.updateRooms(rooms); + expect(RoomsDispatch.updateRooms).toHaveBeenCalledWith(rooms); }); describe('updateGames', () => { - it('normalizes game when gameType is missing and room exists', () => { - const game = { gameType: null, gameTypes: [1] } as any; - const room = { gametypeMap: { 1: 'Standard' } } as any; - (RoomsSelectors.getRoom as vi.Mock).mockReturnValue(room); + it('dispatches raw game list', () => { + const game = { gameTypes: [1] } as any; RoomPersistence.updateGames(1, [game]); - expect(NormalizeService.normalizeGameObject).toHaveBeenCalledWith(game, room.gametypeMap); expect(RoomsDispatch.updateGames).toHaveBeenCalledWith(1, [game]); }); - it('does not normalize when game already has gameType', () => { - const game = { gameType: 'Standard' } as any; - RoomPersistence.updateGames(1, [game]); - expect(NormalizeService.normalizeGameObject).not.toHaveBeenCalled(); - }); - - it('does not normalize when room is not found', () => { - const game = { gameType: null } as any; - (RoomsSelectors.getRoom as vi.Mock).mockReturnValue(null); - RoomPersistence.updateGames(1, [game]); - expect(NormalizeService.normalizeGameObject).not.toHaveBeenCalled(); - }); - it('returns without error when gameList is empty', () => { expect(() => RoomPersistence.updateGames(1, [])).not.toThrow(); expect(RoomsDispatch.updateGames).not.toHaveBeenCalled(); @@ -92,10 +62,9 @@ describe('RoomPersistence', () => { }); }); - it('addMessage normalizes message and dispatches', () => { + it('addMessage dispatches without pre-normalizing', () => { const msg = { name: 'alice', message: 'hi' } as any; RoomPersistence.addMessage(1, msg); - expect(NormalizeService.normalizeUserMessage).toHaveBeenCalledWith(msg); expect(RoomsDispatch.addMessage).toHaveBeenCalledWith(1, msg); }); diff --git a/webclient/src/websocket/persistence/RoomPersistence.ts b/webclient/src/websocket/persistence/RoomPersistence.ts index 20ed62066..e3774e0ae 100644 --- a/webclient/src/websocket/persistence/RoomPersistence.ts +++ b/webclient/src/websocket/persistence/RoomPersistence.ts @@ -1,14 +1,14 @@ -import { store, RoomsDispatch, RoomsSelectors } from 'store'; -import { Game, Message, Room, User } from 'types'; -import NormalizeService from '../utils/NormalizeService'; +import { RoomsDispatch } from 'store'; +import { Message, User } from 'types'; +import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; +import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; export class RoomPersistence { static clearStore() { RoomsDispatch.clearStore(); } - static joinRoom(roomInfo: Room) { - NormalizeService.normalizeRoomInfo(roomInfo); + static joinRoom(roomInfo: ServerInfo_Room) { RoomsDispatch.joinRoom(roomInfo); } @@ -16,32 +16,22 @@ export class RoomPersistence { RoomsDispatch.leaveRoom(roomId); } - static updateRooms(rooms: Room[]) { + static updateRooms(rooms: ServerInfo_Room[]) { RoomsDispatch.updateRooms(rooms); } - static updateGames(roomId: number, gameList: Game[]) { + static updateGames(roomId: number, gameList: ServerInfo_Game[]) { + // Guard: the server never sends an empty gameList to signal "clear all games". + // An empty array here means no game updates — skip the dispatch to avoid + // unnecessarily overwriting the existing game list with an empty one. if (!gameList?.length) { return; } - const game = gameList[0]; - - if (!game.gameType) { - const room = RoomsSelectors.getRoom(store.getState(), roomId); - - if (room) { - const { gametypeMap } = room; - NormalizeService.normalizeGameObject(game, gametypeMap); - } - } - RoomsDispatch.updateGames(roomId, gameList); } static addMessage(roomId: number, message: Message) { - NormalizeService.normalizeUserMessage(message); - RoomsDispatch.addMessage(roomId, message); } diff --git a/webclient/src/websocket/persistence/SessionPersistence.spec.ts b/webclient/src/websocket/persistence/SessionPersistence.spec.ts index 67a33fd9a..5bac239f4 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.spec.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.spec.ts @@ -64,26 +64,15 @@ vi.mock('websocket/utils', () => ({ sanitizeHtml: vi.fn((msg: string) => `sanitized:${msg}`), })); -vi.mock('../utils/NormalizeService', () => ({ - __esModule: true, - default: { - normalizeBannedUserError: vi.fn((r: string, t: number) => `banned:${r}:${t}`), - normalizeGameObject: vi.fn(), - }, -})); - import { SessionPersistence } from './SessionPersistence'; import { ServerDispatch, GameDispatch } from 'store'; import { sanitizeHtml } from 'websocket/utils'; -import NormalizeService from '../utils/NormalizeService'; import { StatusEnum } from 'types'; +import { Mock } from 'vitest'; beforeEach(() => { vi.clearAllMocks(); - (sanitizeHtml as vi.Mock).mockImplementation((msg: string) => `sanitized:${msg}`); - (NormalizeService.normalizeBannedUserError as vi.Mock).mockImplementation( - (r: string, t: number) => `banned:${r}:${t}` - ); + (sanitizeHtml as Mock).mockImplementation((msg: string) => `sanitized:${msg}`); }); describe('SessionPersistence', () => { @@ -230,15 +219,14 @@ describe('SessionPersistence', () => { expect(ServerDispatch.registrationSuccess).toHaveBeenCalled(); }); - it('registrationFailed normalizes ban error when endTime is given', () => { + it('registrationFailed passes reason and endTime to ServerDispatch', () => { SessionPersistence.registrationFailed('reason', 999); - expect(NormalizeService.normalizeBannedUserError).toHaveBeenCalledWith('reason', 999); - expect(ServerDispatch.registrationFailed).toHaveBeenCalledWith('banned:reason:999'); + expect(ServerDispatch.registrationFailed).toHaveBeenCalledWith('reason', 999); }); - it('registrationFailed uses reason directly when no endTime', () => { + it('registrationFailed passes reason only when no endTime', () => { SessionPersistence.registrationFailed('plain reason'); - expect(ServerDispatch.registrationFailed).toHaveBeenCalledWith('plain reason'); + expect(ServerDispatch.registrationFailed).toHaveBeenCalledWith('plain reason', undefined); }); it('registrationEmailError passes error', () => { @@ -298,18 +286,17 @@ describe('SessionPersistence', () => { expect(ServerDispatch.getUserInfo).toHaveBeenCalledWith(user); }); - it('getGamesOfUser normalizes game list and dispatches gamesOfUser', () => { + it('getGamesOfUser builds gametypeMap and dispatches raw games with map', () => { const gt = { gameTypeId: 1, description: 'Standard' }; const room = { gametypeList: [gt] }; const game = { gameId: 5, roomId: 1, gameTypes: [1], description: 'My Game', started: false }; - SessionPersistence.getGamesOfUser('alice', { roomList: [room], gameList: [game] }); - expect(NormalizeService.normalizeGameObject).toHaveBeenCalledWith(game, { 1: 'Standard' }); - expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', [game]); + SessionPersistence.getGamesOfUser('alice', { roomList: [room], gameList: [game] } as any); + expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', [game], { 1: 'Standard' }); }); it('getGamesOfUser handles empty response', () => { - SessionPersistence.getGamesOfUser('alice', {}); - expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', []); + SessionPersistence.getGamesOfUser('alice', {} as any); + expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', [], {}); }); it('gameJoined dispatches via GameDispatch.gameJoined', () => { @@ -328,8 +315,9 @@ describe('SessionPersistence', () => { }); it('playerPropertiesChanged dispatches via GameDispatch', () => { - SessionPersistence.playerPropertiesChanged(5, 1, {} as any); - expect(GameDispatch.playerPropertiesChanged).toHaveBeenCalledWith(5, 1, {}); + const props = { pingTime: 100 }; + SessionPersistence.playerPropertiesChanged(5, 1, { playerProperties: props } as any); + expect(GameDispatch.playerPropertiesChanged).toHaveBeenCalledWith(5, 1, props); }); it('serverShutdown passes data', () => { diff --git a/webclient/src/websocket/persistence/SessionPersistence.ts b/webclient/src/websocket/persistence/SessionPersistence.ts index 6e96005ac..d15215ec9 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.ts @@ -1,7 +1,6 @@ import { GameDispatch, ServerDispatch } from 'store'; import { DeckList, DeckStorageTreeItem, ReplayMatch, StatusEnum, User, WebSocketConnectOptions } from 'types'; import { GameEntry } from 'store/game/game.interfaces'; - import { sanitizeHtml } from 'websocket/utils'; import { GameJoinedData, @@ -10,11 +9,10 @@ import { ServerShutdownData, UserMessageData } from '../events/session/interfaces'; -import NormalizeService from '../utils/NormalizeService'; + import type { Response_GetGamesOfUser } from 'generated/proto/response_get_games_of_user_pb'; import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; import type { ServerInfo_GameType } from 'generated/proto/serverinfo_gametype_pb'; -import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; export class SessionPersistence { static initialized() { @@ -49,7 +47,7 @@ export class SessionPersistence { ServerDispatch.testConnectionFailed(); } - static updateBuddyList(buddyList) { + static updateBuddyList(buddyList: User[]) { ServerDispatch.updateBuddyList(buddyList); } @@ -61,7 +59,7 @@ export class SessionPersistence { ServerDispatch.removeFromBuddyList(userName); } - static updateIgnoreList(ignoreList) { + static updateIgnoreList(ignoreList: User[]) { ServerDispatch.updateIgnoreList(ignoreList); } @@ -126,9 +124,7 @@ export class SessionPersistence { } static registrationFailed(reason: string, endTime?: number) { - const reasonMsg = endTime ? NormalizeService.normalizeBannedUserError(reason, endTime) : reason; - - ServerDispatch.registrationFailed(reasonMsg); + ServerDispatch.registrationFailed(reason, endTime); } static registrationEmailError(error: string) { @@ -182,11 +178,8 @@ export class SessionPersistence { gametypeMap[gt.gameTypeId] = gt.description; }); }); - const games = (response.gameList || []).map((game: ServerInfo_Game) => { - NormalizeService.normalizeGameObject(game, gametypeMap); - return game; - }); - ServerDispatch.gamesOfUser(userName, games); + const games = response.gameList || []; + ServerDispatch.gamesOfUser(userName, games, gametypeMap); } static gameJoined(gameJoinedData: GameJoinedData): void { @@ -216,7 +209,9 @@ export class SessionPersistence { } static playerPropertiesChanged(gameId: number, playerId: number, payload: PlayerGamePropertiesData): void { - GameDispatch.playerPropertiesChanged(gameId, playerId, payload); + if (payload.playerProperties) { + GameDispatch.playerPropertiesChanged(gameId, playerId, payload.playerProperties); + } } static serverShutdown(data: ServerShutdownData): void { diff --git a/webclient/src/websocket/services/BackendService.spec.ts b/webclient/src/websocket/services/BackendService.spec.ts index 98d072338..5f33fd06e 100644 --- a/webclient/src/websocket/services/BackendService.spec.ts +++ b/webclient/src/websocket/services/BackendService.spec.ts @@ -93,11 +93,11 @@ describe('BackendService', () => { expect(onSuccess).not.toHaveBeenCalled(); }); - it('calls onSuccess with raw when responseCode is RespOk and no responseExt', () => { + it('calls onSuccess when responseCode is RespOk and no responseExt', () => { const onSuccess = vi.fn(); const raw = { responseCode: 1 }; invokeCallback({ onSuccess }, raw); - expect(onSuccess).toHaveBeenCalledWith(raw, raw); + expect(onSuccess).toHaveBeenCalledWith(); }); it('calls onSuccess with nested response when responseExt is set', () => { diff --git a/webclient/src/websocket/services/BackendService.ts b/webclient/src/websocket/services/BackendService.ts index c9b65eb44..61b856e83 100644 --- a/webclient/src/websocket/services/BackendService.ts +++ b/webclient/src/websocket/services/BackendService.ts @@ -1,5 +1,5 @@ import { create, getExtension, setExtension } from '@bufbuild/protobuf'; -import type { GenExtension } from '@bufbuild/protobuf'; +import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; import webClient from '../WebClient'; import { Response_ResponseCode, type Response } from 'generated/proto/response_pb'; @@ -9,56 +9,80 @@ import { RoomCommandSchema, type RoomCommand } from 'generated/proto/room_comman import { ModeratorCommandSchema, type ModeratorCommand } from 'generated/proto/moderator_commands_pb'; import { AdminCommandSchema, type AdminCommand } from 'generated/proto/admin_commands_pb'; -export interface CommandOptions { - responseExt?: GenExtension; - onSuccess?: (response: R, raw: Response) => void; +interface CommandOptionsBase { onError?: (responseCode: number, raw: Response) => void; onResponseCode?: { [code: number]: (raw: Response) => void }; onResponse?: (raw: Response) => void; } +export interface CommandOptionsWithResponse extends CommandOptionsBase { + responseExt: GenExtension; + onSuccess?: (response: R, raw: Response) => void; +} + +export interface CommandOptionsWithoutResponse extends CommandOptionsBase { + responseExt?: undefined; + onSuccess?: () => void; +} + +export type CommandOptions = CommandOptionsWithResponse | CommandOptionsWithoutResponse; + +function hasResponseExt(options: CommandOptions): options is CommandOptionsWithResponse { + return options.responseExt !== undefined; +} + export class BackendService { - static sendGameCommand(gameId: number, ext: GenExtension, value: V, options: CommandOptions = {}): void { + static sendGameCommand(gameId: number, ext: GenExtension, value: V, options?: CommandOptions): void { const cmd = create(GameCommandSchema); setExtension(cmd, ext, value); webClient.protobuf.sendGameCommand(gameId, cmd, (raw: Response) => { - BackendService.handleResponse(ext, raw, options); + if (options) { + BackendService.handleResponse(ext.typeName, raw, options); + } }); } - static sendSessionCommand(ext: GenExtension, value: V, options: CommandOptions = {}): void { + static sendSessionCommand(ext: GenExtension, value: V, options?: CommandOptions): void { const cmd = create(SessionCommandSchema); setExtension(cmd, ext, value); webClient.protobuf.sendSessionCommand(cmd, raw => { - BackendService.handleResponse(ext, raw, options); + if (options) { + BackendService.handleResponse(ext.typeName, raw, options); + } }); } - static sendRoomCommand(roomId: number, ext: GenExtension, value: V, options: CommandOptions = {}): void { + static sendRoomCommand(roomId: number, ext: GenExtension, value: V, options?: CommandOptions): void { const cmd = create(RoomCommandSchema); setExtension(cmd, ext, value); webClient.protobuf.sendRoomCommand(roomId, cmd, raw => { - BackendService.handleResponse(ext, raw, options); + if (options) { + BackendService.handleResponse(ext.typeName, raw, options); + } }); } - static sendModeratorCommand(ext: GenExtension, value: V, options: CommandOptions = {}): void { + static sendModeratorCommand(ext: GenExtension, value: V, options?: CommandOptions): void { const cmd = create(ModeratorCommandSchema); setExtension(cmd, ext, value); webClient.protobuf.sendModeratorCommand(cmd, raw => { - BackendService.handleResponse(ext, raw, options); + if (options) { + BackendService.handleResponse(ext.typeName, raw, options); + } }); } - static sendAdminCommand(ext: GenExtension, value: V, options: CommandOptions = {}): void { + static sendAdminCommand(ext: GenExtension, value: V, options?: CommandOptions): void { const cmd = create(AdminCommandSchema); setExtension(cmd, ext, value); webClient.protobuf.sendAdminCommand(cmd, raw => { - BackendService.handleResponse(ext, raw, options); + if (options) { + BackendService.handleResponse(ext.typeName, raw, options); + } }); } - private static handleResponse(ext: GenExtension, raw: Response, options: CommandOptions): void { + private static handleResponse(typeName: string, raw: Response, options: CommandOptions): void { if (options.onResponse) { options.onResponse(raw); return; @@ -67,11 +91,10 @@ export class BackendService { const { responseCode } = raw; if (responseCode === Response_ResponseCode.RespOk) { - if (options.onSuccess) { - const response = options.responseExt - ? getExtension(raw, options.responseExt) - : raw as unknown as R; - options.onSuccess(response, raw); + if (hasResponseExt(options)) { + options.onSuccess?.(getExtension(raw, options.responseExt), raw); + } else { + options.onSuccess?.(); } return; } @@ -84,7 +107,7 @@ export class BackendService { if (options.onError) { options.onError(responseCode, raw); } else { - console.error(`${ext.typeName} failed with response code: ${responseCode}`); + console.error(`${typeName} failed with response code: ${responseCode}`); } } } diff --git a/webclient/src/websocket/services/KeepAliveService.spec.ts b/webclient/src/websocket/services/KeepAliveService.spec.ts index df24f45da..ed53da05f 100644 --- a/webclient/src/websocket/services/KeepAliveService.spec.ts +++ b/webclient/src/websocket/services/KeepAliveService.spec.ts @@ -1,3 +1,12 @@ +vi.mock('../WebClient', () => ({ + __esModule: true, + default: { + socket: { + checkReadyState: vi.fn(), + }, + }, +})); + import { KeepAliveService } from './KeepAliveService'; import webClient from '../WebClient'; diff --git a/webclient/src/websocket/services/KeepAliveService.ts b/webclient/src/websocket/services/KeepAliveService.ts index 070a532e6..3e2e03a6a 100644 --- a/webclient/src/websocket/services/KeepAliveService.ts +++ b/webclient/src/websocket/services/KeepAliveService.ts @@ -14,7 +14,7 @@ export class KeepAliveService { this.socket = socket; } - public startPingLoop(interval: number, ping: Function): void { + public startPingLoop(interval: number, ping: (onPong: () => void) => void): void { this.keepalivecb = setInterval(() => { // check if the previous ping got no reply if (this.lastPingPending) { diff --git a/webclient/src/websocket/services/ProtobufService.spec.ts b/webclient/src/websocket/services/ProtobufService.spec.ts index 2e1913e59..deb01a4bd 100644 --- a/webclient/src/websocket/services/ProtobufService.spec.ts +++ b/webclient/src/websocket/services/ProtobufService.spec.ts @@ -36,7 +36,7 @@ vi.mock('../WebClient', () => ({ default: {}, })); -import { fromBinary, toBinary, hasExtension, getExtension } from '@bufbuild/protobuf'; +import { fromBinary, hasExtension, getExtension } from '@bufbuild/protobuf'; import { ServerMessage_MessageType } from 'generated/proto/server_message_pb'; import { ProtobufService } from './ProtobufService'; import { ping as sessionPing } from '../commands/session'; @@ -351,36 +351,4 @@ describe('ProtobufService', () => { }); }); - describe('processEvent', () => { - it('calls matching event handler with payload and raw', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - const handler = vi.fn(); - const mockExt = {}; - const registry = [[mockExt, handler]] as any; - const payload = { someData: 1 }; - const raw = { extra: true }; - - vi.mocked(hasExtension).mockReturnValue(true); - vi.mocked(getExtension).mockReturnValue(payload); - - (service as any).processEvent({}, registry, raw); - - expect(handler).toHaveBeenCalledWith(payload, raw); - }); - - it('stops after first matching event', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - const handler1 = vi.fn(); - const handler2 = vi.fn(); - const registry = [[{}, handler1], [{}, handler2]] as any; - - vi.mocked(hasExtension).mockReturnValueOnce(true).mockReturnValueOnce(false); - vi.mocked(getExtension).mockReturnValue({ x: 1 }); - - (service as any).processEvent({}, registry, {}); - - expect(handler1).toHaveBeenCalled(); - expect(handler2).not.toHaveBeenCalled(); - }); - }); }); diff --git a/webclient/src/websocket/services/ProtobufService.ts b/webclient/src/websocket/services/ProtobufService.ts index 1874cf50d..de6e287c0 100644 --- a/webclient/src/websocket/services/ProtobufService.ts +++ b/webclient/src/websocket/services/ProtobufService.ts @@ -1,10 +1,11 @@ import { create, fromBinary, hasExtension, getExtension, toBinary } from '@bufbuild/protobuf'; -import type { GenExtension, Message } from '@bufbuild/protobuf'; +import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; import type { Response } from 'generated/proto/response_pb'; import type { RoomEvent } from 'generated/proto/room_event_pb'; import type { SessionEvent } from 'generated/proto/session_event_pb'; import type { GameEventContainer } from 'generated/proto/game_event_container_pb'; +import type { GameEvent } from 'generated/proto/game_event_pb'; import { GameEvents, RoomEvents, SessionEvents } from '../events'; import { WebClient } from '../WebClient'; @@ -19,11 +20,57 @@ import type { RoomCommand } from 'generated/proto/room_commands_pb'; import type { ModeratorCommand } from 'generated/proto/moderator_commands_pb'; import type { AdminCommand } from 'generated/proto/admin_commands_pb'; -export type ExtensionRegistry = Array<[GenExtension, (...args: unknown[]) => void]>; +// Per-family registry entry types. Each family hardcodes its parent message type +// and the handler's exact secondary-argument signature, eliminating the previous +// `...args: unknown[]` erasure. + +type SessionRegistryEntry = [ + GenExtension, + (value: V) => void +]; +export type SessionExtensionRegistry = SessionRegistryEntry[]; + +type RoomRegistryEntry = [ + GenExtension, + (value: V, roomEvent: RoomEvent) => void +]; +export type RoomExtensionRegistry = RoomRegistryEntry[]; + +type GameRegistryEntry = [ + GenExtension, + (value: V, meta: GameEventMeta) => void +]; +export type GameExtensionRegistry = GameRegistryEntry[]; + +/** + * Type-safe factory functions. The compiler verifies at the call site that the + * handler's parameter types match the extension's value type and the family's + * secondary argument type. + */ +export function makeSessionEntry( + ext: GenExtension, + handler: (value: V) => void +): SessionRegistryEntry { + return [ext as GenExtension, handler as (value: unknown) => void]; +} + +export function makeRoomEntry( + ext: GenExtension, + handler: (value: V, roomEvent: RoomEvent) => void +): RoomRegistryEntry { + return [ext as GenExtension, handler as RoomRegistryEntry[1]]; +} + +export function makeGameEntry( + ext: GenExtension, + handler: (value: V, meta: GameEventMeta) => void +): GameRegistryEntry { + return [ext as GenExtension, handler as GameRegistryEntry[1]]; +} export class ProtobufService { private cmdId = 0; - private pendingCommands: { [cmdId: string]: Function } = {}; + private pendingCommands: { [cmdId: string]: (response: Response) => void } = {}; private webClient: WebClient; @@ -36,44 +83,44 @@ export class ProtobufService { this.pendingCommands = {}; } - public sendGameCommand(gameId: number, gameCmd: GameCommand, callback?: Function) { + public sendGameCommand(gameId: number, gameCmd: GameCommand, callback?: (raw: Response) => void) { const cmd = create(CommandContainerSchema, { gameId, gameCommand: [gameCmd], }); - this.sendCommand(cmd, (raw: Response) => callback && callback(raw)); + this.sendCommand(cmd, (raw: Response) => callback?.(raw)); } - public sendRoomCommand(roomId: number, roomCmd: RoomCommand, callback?: Function) { + public sendRoomCommand(roomId: number, roomCmd: RoomCommand, callback?: (raw: Response) => void) { const cmd = create(CommandContainerSchema, { roomId, roomCommand: [roomCmd], }); - this.sendCommand(cmd, raw => callback && callback(raw)); + this.sendCommand(cmd, raw => callback?.(raw)); } - public sendSessionCommand(sesCmd: SessionCommand, callback?: Function) { + public sendSessionCommand(sesCmd: SessionCommand, callback?: (raw: Response) => void) { const cmd = create(CommandContainerSchema, { sessionCommand: [sesCmd], }); - this.sendCommand(cmd, (raw) => callback && callback(raw)); + this.sendCommand(cmd, (raw) => callback?.(raw)); } - public sendModeratorCommand(modCmd: ModeratorCommand, callback?: Function) { + public sendModeratorCommand(modCmd: ModeratorCommand, callback?: (raw: Response) => void) { const cmd = create(CommandContainerSchema, { moderatorCommand: [modCmd], }); - this.sendCommand(cmd, (raw) => callback && callback(raw)); + this.sendCommand(cmd, (raw) => callback?.(raw)); } - public sendAdminCommand(adminCmd: AdminCommand, callback?: Function) { + public sendAdminCommand(adminCmd: AdminCommand, callback?: (raw: Response) => void) { const cmd = create(CommandContainerSchema, { adminCommand: [adminCmd], }); - this.sendCommand(cmd, (raw) => callback && callback(raw)); + this.sendCommand(cmd, (raw) => callback?.(raw)); } - public sendCommand(cmd: CommandContainer, callback: Function) { + public sendCommand(cmd: CommandContainer, callback: (raw: Response) => void) { this.cmdId++; cmd.cmdId = BigInt(this.cmdId); @@ -84,7 +131,7 @@ export class ProtobufService { } } - public sendKeepAliveCommand(pingReceived: Function) { + public sendKeepAliveCommand(pingReceived: () => void) { SessionCommands.ping(pingReceived); } @@ -133,14 +180,24 @@ export class ProtobufService { if (!event) { return; } - this.processEvent(event, RoomEvents, event); + for (const [ext, handler] of RoomEvents) { + if (hasExtension(event, ext)) { + handler(getExtension(event, ext), event); + return; + } + } } private processSessionEvent(event: SessionEvent | undefined) { if (!event) { return; } - this.processEvent(event, SessionEvents); + for (const [ext, handler] of SessionEvents) { + if (hasExtension(event, ext)) { + handler(getExtension(event, ext)); + return; + } + } } private processGameEvent(container: GameEventContainer | undefined): void { @@ -161,20 +218,12 @@ export class ProtobufService { for (const [ext, handler] of GameEvents) { if (hasExtension(event, ext)) { - (handler as Function)(getExtension(event, ext), meta); + handler(getExtension(event, ext), meta); break; } } } } - private processEvent(response: Message, registry: ExtensionRegistry, raw?: Message) { - for (const [ext, handler] of registry) { - if (hasExtension(response, ext)) { - (handler as Function)(getExtension(response, ext), raw); - return; - } - } - } } diff --git a/webclient/src/websocket/services/WebSocketService.spec.ts b/webclient/src/websocket/services/WebSocketService.spec.ts index ce511ae31..67fa1ed3d 100644 --- a/webclient/src/websocket/services/WebSocketService.spec.ts +++ b/webclient/src/websocket/services/WebSocketService.spec.ts @@ -1,4 +1,9 @@ import { installMockWebSocket } from '../__mocks__/helpers'; +import { Mock } from 'vitest'; + +vi.mock('../WebClient', () => ({ + WebClient: vi.fn(), +})); vi.mock('../commands/session', () => ({ updateStatus: vi.fn(), @@ -17,8 +22,9 @@ import { SessionPersistence } from '../persistence'; import { updateStatus } from '../commands/session'; import { StatusEnum } from 'types'; -let MockWS: vi.Mock; +let MockWS: Mock; let mockInstance: ReturnType['mockInstance']; +let restoreWebSocket: ReturnType['restore']; let mockWebClient: any; beforeEach(() => { @@ -28,6 +34,7 @@ beforeEach(() => { const installed = installMockWebSocket(); MockWS = installed.MockWS; mockInstance = installed.mockInstance; + restoreWebSocket = installed.restore; mockWebClient = { status: StatusEnum.CONNECTED, @@ -37,6 +44,7 @@ beforeEach(() => { }); afterEach(() => { + restoreWebSocket(); vi.useRealTimers(); }); @@ -106,7 +114,7 @@ describe('WebSocketService', () => { describe('socket event handlers (onopen)', () => { it('clears the connection timeout when socket opens', () => { - const clearSpy = vi.spyOn(global, 'clearTimeout'); + const clearSpy = vi.spyOn(globalThis, 'clearTimeout'); createConnectedService(); mockInstance.onopen(); expect(clearSpy).toHaveBeenCalled(); @@ -262,7 +270,7 @@ describe('WebSocketService', () => { it('calls SessionPersistence.testConnectionSuccessful on open', () => { createTestConnectedService(); - const timer = vi.spyOn(global, 'clearTimeout'); + vi.spyOn(globalThis, 'clearTimeout'); mockInstance.onopen(); expect(SessionPersistence.testConnectionSuccessful).toHaveBeenCalled(); expect(mockInstance.close).toHaveBeenCalled(); diff --git a/webclient/src/websocket/services/WebSocketService.ts b/webclient/src/websocket/services/WebSocketService.ts index 95fa2d356..4de880687 100644 --- a/webclient/src/websocket/services/WebSocketService.ts +++ b/webclient/src/websocket/services/WebSocketService.ts @@ -59,7 +59,7 @@ export class WebSocketService { return this.socket?.readyState === state; } - public send(message): void { + public send(message: Uint8Array): void { this.socket.send(message); } @@ -73,7 +73,7 @@ export class WebSocketService { clearTimeout(connectionTimer); updateStatus(StatusEnum.CONNECTED, 'Connected'); - this.keepAliveService.startPingLoop(this.keepalive, (pingReceived: Function) => { + this.keepAliveService.startPingLoop(this.keepalive, (pingReceived: () => void) => { this.webClient.keepAlive(pingReceived); }); }; diff --git a/webclient/src/websocket/utils/NormalizeService.spec.ts b/webclient/src/websocket/utils/NormalizeService.spec.ts deleted file mode 100644 index c0c20adc9..000000000 --- a/webclient/src/websocket/utils/NormalizeService.spec.ts +++ /dev/null @@ -1,110 +0,0 @@ -import NormalizeService from './NormalizeService'; - -describe('NormalizeService', () => { - describe('normalizeRoomInfo', () => { - it('builds gametypeMap from gametypeList', () => { - const roomInfo: any = { - gametypeList: [ - { gameTypeId: 1, description: 'Standard' }, - { gameTypeId: 2, description: 'Draft' }, - ], - gametypeMap: {}, - gameList: [], - }; - NormalizeService.normalizeRoomInfo(roomInfo); - expect(roomInfo.gametypeMap).toEqual({ 1: 'Standard', 2: 'Draft' }); - }); - - it('normalizes each game in gameList', () => { - const roomInfo: any = { - gametypeList: [{ gameTypeId: 5, description: 'Modern' }], - gametypeMap: {}, - gameList: [{ gameTypes: [5], description: 'My Game' }], - }; - NormalizeService.normalizeRoomInfo(roomInfo); - expect(roomInfo.gameList[0].gameType).toBe('Modern'); - }); - }); - - describe('normalizeGameObject', () => { - it('sets gameType from first element of gameTypes', () => { - const game: any = { gameTypes: [3], description: 'Test' }; - const map: any = { 3: 'Legacy' }; - NormalizeService.normalizeGameObject(game, map); - expect(game.gameType).toBe('Legacy'); - }); - - it('sets gameType to empty string when gameTypes is empty', () => { - const game: any = { gameTypes: [], description: 'Test' }; - NormalizeService.normalizeGameObject(game, {}); - expect(game.gameType).toBe(''); - }); - - it('sets gameType to empty string when gameTypes is null', () => { - const game: any = { gameTypes: null, description: 'Test' }; - NormalizeService.normalizeGameObject(game, {}); - expect(game.gameType).toBe(''); - }); - - it('sets description to empty string when description is falsy', () => { - const game: any = { gameTypes: [], description: null }; - NormalizeService.normalizeGameObject(game, {}); - expect(game.description).toBe(''); - }); - }); - - describe('normalizeLogs', () => { - it('groups logs by targetType', () => { - const logs: any[] = [ - { targetType: 'room', msg: 'a' }, - { targetType: 'chat', msg: 'b' }, - { targetType: 'room', msg: 'c' }, - ]; - const result = NormalizeService.normalizeLogs(logs); - expect(result['room']).toHaveLength(2); - expect(result['chat']).toHaveLength(1); - }); - - it('returns empty object for empty array', () => { - expect(NormalizeService.normalizeLogs([])).toEqual({}); - }); - }); - - describe('normalizeUserMessage', () => { - it('prepends username when name is present', () => { - const message: any = { name: 'Alice', message: 'hello' }; - NormalizeService.normalizeUserMessage(message); - expect(message.message).toBe('Alice: hello'); - }); - - it('does not modify message when name is absent', () => { - const message: any = { name: '', message: 'hello' }; - NormalizeService.normalizeUserMessage(message); - expect(message.message).toBe('hello'); - }); - }); - - describe('normalizeBannedUserError', () => { - it('returns permanently banned message when endTime is 0', () => { - const result = NormalizeService.normalizeBannedUserError('', 0); - expect(result).toBe('You are permanently banned'); - }); - - it('returns banned until date when endTime is given', () => { - const endTime = new Date('2030-01-01').getTime(); - const result = NormalizeService.normalizeBannedUserError('', endTime); - expect(result).toContain('You are banned until'); - expect(result).toContain(new Date(endTime).toString()); - }); - - it('appends reasonStr when provided', () => { - const result = NormalizeService.normalizeBannedUserError('bad behavior', 0); - expect(result).toContain('\n\nbad behavior'); - }); - - it('does not append when reasonStr is empty', () => { - const result = NormalizeService.normalizeBannedUserError('', 0); - expect(result).not.toContain('\n\n'); - }); - }); -}); diff --git a/webclient/src/websocket/utils/NormalizeService.ts b/webclient/src/websocket/utils/NormalizeService.ts deleted file mode 100644 index 4d9d37ba6..000000000 --- a/webclient/src/websocket/utils/NormalizeService.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Game, GametypeMap, LogItem, LogGroups, Message, Room } from 'types'; - -export default class NormalizeService { - // Flatten room gameTypes into map object - static normalizeRoomInfo(roomInfo: Room): void { - roomInfo.gametypeMap = {}; - - const { gametypeList, gametypeMap, gameList } = roomInfo; - - gametypeList.reduce((map, type) => { - map[type.gameTypeId] = type.description; - return map; - }, gametypeMap); - - gameList.forEach((game) => NormalizeService.normalizeGameObject(game, gametypeMap)); - } - - // Flatten gameTypes[] into gameType field - // Default sortable values ("" || 0 || -1) - static normalizeGameObject(game: Game, gametypeMap: GametypeMap): void { - const { gameTypes, description } = game; - const hasType = gameTypes && gameTypes.length; - game.gameType = hasType ? gametypeMap[gameTypes[0]] : ''; - - game.description = description || ''; - } - - // Flatten logs[] into object mapped by targetType (room, game, chat) - static normalizeLogs(logs: LogItem[]): LogGroups { - return logs.reduce((obj, log) => { - const { targetType } = log; - obj[targetType] = obj[targetType] || []; - obj[targetType].push(log); - return obj; - }, {} as LogGroups); - } - - // messages sent by current user dont have their username prepended - static normalizeUserMessage(message: Message): void { - const { name } = message; - - if (name) { - message.message = `${name}: ${message.message}`; - } - } - - // Banned reason string is not being exposed by the server - static normalizeBannedUserError(reasonStr: string, endTime: number): string { - let error; - - if (endTime) { - error = 'You are banned until ' + new Date(endTime).toString(); - } else { - error = 'You are permanently banned'; - } - - if (reasonStr) { - error += '\n\n' + reasonStr; - } - - return error; - } -} diff --git a/webclient/vite.config.ts b/webclient/vite.config.ts index 59a21c05b..cff39dd64 100644 --- a/webclient/vite.config.ts +++ b/webclient/vite.config.ts @@ -16,6 +16,6 @@ export default defineConfig({ environment: 'jsdom', setupFiles: ['./src/setupTests.ts'], include: ['src/**/*.spec.{ts,tsx}'], - css: true, + isolate: false, }, }); From 141f0e59f54a9dce67af9a38a57694ed78b46333 Mon Sep 17 00:00:00 2001 From: seavor Date: Tue, 14 Apr 2026 14:39:46 -0500 Subject: [PATCH 12/38] refactor web socket layer --- .../src/api/AuthenticationService.spec.ts | 17 +- webclient/src/api/AuthenticationService.tsx | 6 +- webclient/src/forms/LoginForm/LoginForm.tsx | 6 +- webclient/src/store/index.ts | 2 +- .../store/server/__mocks__/server-fixtures.ts | 1 + .../src/store/server/server.actions.spec.ts | 8 + webclient/src/store/server/server.actions.ts | 3 + .../src/store/server/server.dispatch.spec.ts | 10 + webclient/src/store/server/server.dispatch.ts | 3 + .../src/store/server/server.interfaces.ts | 1 + .../src/store/server/server.reducer.spec.ts | 6 + webclient/src/store/server/server.reducer.ts | 7 + .../src/store/server/server.selectors.spec.ts | 14 +- .../src/store/server/server.selectors.ts | 1 + webclient/src/store/server/server.types.ts | 1 + webclient/src/websocket/WebClient.spec.ts | 62 +++-- webclient/src/websocket/WebClient.ts | 49 +--- .../__mocks__/sessionCommandMocks.ts | 6 +- .../src/websocket/commands/admin/adjustMod.ts | 4 +- .../commands/admin/adminCommands.spec.ts | 19 +- .../websocket/commands/admin/reloadConfig.ts | 4 +- .../commands/admin/shutdownServer.ts | 4 +- .../commands/admin/updateServerMessage.ts | 4 +- .../src/websocket/commands/game/attachCard.ts | 4 +- .../commands/game/changeZoneProperties.ts | 4 +- .../src/websocket/commands/game/concede.ts | 4 +- .../websocket/commands/game/createArrow.ts | 4 +- .../websocket/commands/game/createCounter.ts | 4 +- .../websocket/commands/game/createToken.ts | 4 +- .../src/websocket/commands/game/deckSelect.ts | 4 +- .../src/websocket/commands/game/delCounter.ts | 4 +- .../websocket/commands/game/deleteArrow.ts | 4 +- .../src/websocket/commands/game/drawCards.ts | 4 +- .../src/websocket/commands/game/dumpZone.ts | 4 +- .../src/websocket/commands/game/flipCard.ts | 4 +- .../commands/game/gameCommands.spec.ts | 93 ++++--- .../src/websocket/commands/game/gameSay.ts | 4 +- .../websocket/commands/game/incCardCounter.ts | 4 +- .../src/websocket/commands/game/incCounter.ts | 4 +- .../src/websocket/commands/game/judge.ts | 4 +- .../websocket/commands/game/kickFromGame.ts | 4 +- .../src/websocket/commands/game/leaveGame.ts | 4 +- .../src/websocket/commands/game/moveCard.ts | 4 +- .../src/websocket/commands/game/mulligan.ts | 4 +- .../src/websocket/commands/game/nextTurn.ts | 4 +- .../src/websocket/commands/game/readyStart.ts | 4 +- .../websocket/commands/game/revealCards.ts | 4 +- .../websocket/commands/game/reverseTurn.ts | 4 +- .../websocket/commands/game/setActivePhase.ts | 4 +- .../websocket/commands/game/setCardAttr.ts | 4 +- .../websocket/commands/game/setCardCounter.ts | 4 +- .../src/websocket/commands/game/setCounter.ts | 4 +- .../commands/game/setSideboardLock.ts | 4 +- .../commands/game/setSideboardPlan.ts | 4 +- .../src/websocket/commands/game/shuffle.ts | 4 +- .../src/websocket/commands/game/unconcede.ts | 4 +- .../src/websocket/commands/game/undoDraw.ts | 4 +- .../commands/moderator/banFromServer.ts | 4 +- .../commands/moderator/forceActivateUser.ts | 4 +- .../commands/moderator/getAdminNotes.ts | 4 +- .../commands/moderator/getBanHistory.ts | 4 +- .../commands/moderator/getWarnHistory.ts | 4 +- .../commands/moderator/getWarnList.ts | 4 +- .../commands/moderator/grantReplayAccess.ts | 14 +- .../moderator/moderatorCommands.spec.ts | 37 +-- .../commands/moderator/updateAdminNotes.ts | 4 +- .../commands/moderator/viewLogHistory.ts | 4 +- .../websocket/commands/moderator/warnUser.ts | 4 +- .../src/websocket/commands/room/createGame.ts | 4 +- .../src/websocket/commands/room/joinGame.ts | 4 +- .../src/websocket/commands/room/leaveRoom.ts | 4 +- .../commands/room/roomCommands.spec.ts | 23 +- .../src/websocket/commands/room/roomSay.ts | 4 +- .../websocket/commands/session/accountEdit.ts | 4 +- .../commands/session/accountImage.ts | 4 +- .../commands/session/accountPassword.ts | 4 +- .../websocket/commands/session/activate.ts | 6 +- .../websocket/commands/session/addToList.ts | 4 +- .../src/websocket/commands/session/deckDel.ts | 4 +- .../websocket/commands/session/deckDelDir.ts | 4 +- .../websocket/commands/session/deckList.ts | 4 +- .../websocket/commands/session/deckNewDir.ts | 4 +- .../websocket/commands/session/deckUpload.ts | 4 +- .../session/forgotPasswordChallenge.ts | 6 +- .../commands/session/forgotPasswordRequest.ts | 6 +- .../commands/session/forgotPasswordReset.ts | 6 +- .../commands/session/getGamesOfUser.ts | 4 +- .../websocket/commands/session/getUserInfo.ts | 4 +- .../websocket/commands/session/joinRoom.ts | 4 +- .../websocket/commands/session/listRooms.ts | 4 +- .../websocket/commands/session/listUsers.ts | 4 +- .../src/websocket/commands/session/login.ts | 6 +- .../src/websocket/commands/session/message.ts | 4 +- .../src/websocket/commands/session/ping.ts | 4 +- .../websocket/commands/session/register.ts | 6 +- .../commands/session/removeFromList.ts | 4 +- .../commands/session/replayDeleteMatch.ts | 4 +- .../commands/session/replayGetCode.ts | 4 +- .../websocket/commands/session/replayList.ts | 4 +- .../commands/session/replayModifyMatch.ts | 4 +- .../commands/session/replaySubmitCode.ts | 4 +- .../commands/session/requestPasswordSalt.ts | 6 +- .../session/sessionCommands-complex.spec.ts | 33 +-- .../session/sessionCommands-simple.spec.ts | 57 ++--- webclient/src/websocket/config.ts | 27 ++ .../src/websocket/events/common/index.ts | 2 +- webclient/src/websocket/events/game/index.ts | 2 +- webclient/src/websocket/events/room/index.ts | 2 +- .../src/websocket/events/session/index.ts | 2 +- .../src/websocket/events/session/listRooms.ts | 4 +- .../events/session/serverIdentification.ts | 3 +- .../events/session/sessionEvents.spec.ts | 14 +- .../persistence/SessionPersistence.spec.ts | 18 ++ .../persistence/SessionPersistence.ts | 4 + .../websocket/services/BackendService.spec.ts | 131 ---------- .../src/websocket/services/BackendService.ts | 114 --------- .../services/KeepAliveService.spec.ts | 18 +- .../services/ProtobufService.spec.ts | 230 +++++++++++------- .../src/websocket/services/ProtobufService.ts | 174 +++++++------ .../services/WebSocketService.spec.ts | 44 ++-- .../websocket/services/WebSocketService.ts | 25 +- .../services/command-options.spec.ts | 59 +++++ .../src/websocket/services/command-options.ts | 54 ++++ .../src/websocket/services/protobuf-types.ts | 44 ++++ 124 files changed, 927 insertions(+), 853 deletions(-) create mode 100644 webclient/src/websocket/config.ts delete mode 100644 webclient/src/websocket/services/BackendService.spec.ts delete mode 100644 webclient/src/websocket/services/BackendService.ts create mode 100644 webclient/src/websocket/services/command-options.spec.ts create mode 100644 webclient/src/websocket/services/command-options.ts create mode 100644 webclient/src/websocket/services/protobuf-types.ts diff --git a/webclient/src/api/AuthenticationService.spec.ts b/webclient/src/api/AuthenticationService.spec.ts index 1ba4cd252..9d9dfcd79 100644 --- a/webclient/src/api/AuthenticationService.spec.ts +++ b/webclient/src/api/AuthenticationService.spec.ts @@ -3,9 +3,6 @@ vi.mock('websocket', () => ({ connect: vi.fn(), disconnect: vi.fn(), }, - webClient: { - connectionAttemptMade: false, - }, })); vi.mock('generated/proto/serverinfo_user_pb', () => ({ @@ -15,7 +12,7 @@ vi.mock('generated/proto/serverinfo_user_pb', () => ({ })); import { AuthenticationService } from './AuthenticationService'; -import { SessionCommands, webClient } from 'websocket'; +import { SessionCommands } from 'websocket'; import { StatusEnum, WebSocketConnectOptions, WebSocketConnectReason } from 'types'; const testOptions: WebSocketConnectOptions = { host: 'localhost', port: '4748', userName: 'user', password: 'pw' }; @@ -124,16 +121,4 @@ describe('AuthenticationService', () => { expect(AuthenticationService.isAdmin()).toBeUndefined(); }); }); - - describe('connectionAttemptMade', () => { - it('returns webClient.connectionAttemptMade when false', () => { - (webClient as any).connectionAttemptMade = false; - expect(AuthenticationService.connectionAttemptMade()).toBe(false); - }); - - it('returns webClient.connectionAttemptMade when true', () => { - (webClient as any).connectionAttemptMade = true; - expect(AuthenticationService.connectionAttemptMade()).toBe(true); - }); - }); }); diff --git a/webclient/src/api/AuthenticationService.tsx b/webclient/src/api/AuthenticationService.tsx index c5128fcbd..eca17118c 100644 --- a/webclient/src/api/AuthenticationService.tsx +++ b/webclient/src/api/AuthenticationService.tsx @@ -1,5 +1,5 @@ import { StatusEnum, User, WebSocketConnectReason, WebSocketConnectOptions } from 'types'; -import { SessionCommands, webClient } from 'websocket'; +import { SessionCommands } from 'websocket'; import { ServerInfo_User_UserLevelFlag } from 'generated/proto/serverinfo_user_pb'; export class AuthenticationService { @@ -48,8 +48,4 @@ export class AuthenticationService { static isAdmin() { } - - static connectionAttemptMade() { - return webClient.connectionAttemptMade; - } } diff --git a/webclient/src/forms/LoginForm/LoginForm.tsx b/webclient/src/forms/LoginForm/LoginForm.tsx index ed387c045..20c819304 100644 --- a/webclient/src/forms/LoginForm/LoginForm.tsx +++ b/webclient/src/forms/LoginForm/LoginForm.tsx @@ -5,11 +5,12 @@ import { useTranslation } from 'react-i18next'; import Button from '@mui/material/Button'; -import { AuthenticationService } from 'api'; import { CheckboxField, InputField, KnownHosts } from 'components'; import { useAutoConnect } from 'hooks'; import { HostDTO, SettingDTO } from 'services'; import { APP_USER } from 'types'; +import { useAppSelector } from 'store'; +import { Selectors as ServerSelectors } from 'store/server'; import './LoginForm.css'; @@ -21,6 +22,7 @@ const LoginForm = ({ onSubmit, disableSubmitButton, onResetPassword }: LoginForm const [host, setHost] = useState(null); const [useStoredPasswordLabel, setUseStoredPasswordLabel] = useState(false); const [autoConnect, setAutoConnect] = useAutoConnect(); + const connectionAttemptMade = useAppSelector(ServerSelectors.getConnectionAttemptMade); const validate = values => { const errors: any = {}; @@ -54,7 +56,7 @@ const LoginForm = ({ onSubmit, disableSubmitButton, onResetPassword }: LoginForm useEffect(() => { SettingDTO.get(APP_USER).then((userSetting: SettingDTO) => { - if (userSetting?.autoConnect && !AuthenticationService.connectionAttemptMade()) { + if (userSetting?.autoConnect && !connectionAttemptMade) { HostDTO.getAll().then(hosts => { let lastSelectedHost = hosts.find(({ lastSelected }) => lastSelected); diff --git a/webclient/src/store/index.ts b/webclient/src/store/index.ts index 7c257b09e..2ee14345d 100644 --- a/webclient/src/store/index.ts +++ b/webclient/src/store/index.ts @@ -1,4 +1,4 @@ -export { store } from './store'; +export { store, useAppSelector, useAppDispatch } from './store'; // Common export { SortUtil } from './common'; diff --git a/webclient/src/store/server/__mocks__/server-fixtures.ts b/webclient/src/store/server/__mocks__/server-fixtures.ts index 05c2f9747..330ad5159 100644 --- a/webclient/src/store/server/__mocks__/server-fixtures.ts +++ b/webclient/src/store/server/__mocks__/server-fixtures.ts @@ -130,6 +130,7 @@ export function makeServerState(overrides: Partial = {}): ServerSta buddyList: [], ignoreList: [], status: { + connectionAttemptMade: false, state: StatusEnum.DISCONNECTED, description: null, }, diff --git a/webclient/src/store/server/server.actions.spec.ts b/webclient/src/store/server/server.actions.spec.ts index f6817d93f..b25e768f1 100644 --- a/webclient/src/store/server/server.actions.spec.ts +++ b/webclient/src/store/server/server.actions.spec.ts @@ -25,6 +25,10 @@ describe('Actions', () => { expect(Actions.clearStore()).toEqual({ type: Types.CLEAR_STORE }); }); + it('connectionAttempted', () => { + expect(Actions.connectionAttempted()).toEqual({ type: Types.CONNECTION_ATTEMPTED }); + }); + it('loginSuccessful', () => { const options = makeConnectOptions(); expect(Actions.loginSuccessful(options)).toEqual({ type: Types.LOGIN_SUCCESSFUL, options }); @@ -360,4 +364,8 @@ describe('Actions', () => { const gametypeMap = { 1: 'Standard' }; expect(Actions.gamesOfUser('alice', games, gametypeMap)).toEqual({ type: Types.GAMES_OF_USER, userName: 'alice', games, gametypeMap }); }); + + it('clearRegistrationErrors', () => { + expect(Actions.clearRegistrationErrors()).toEqual({ type: Types.CLEAR_REGISTRATION_ERRORS }); + }); }); diff --git a/webclient/src/store/server/server.actions.ts b/webclient/src/store/server/server.actions.ts index f0c20b2fc..e0c5fbc84 100644 --- a/webclient/src/store/server/server.actions.ts +++ b/webclient/src/store/server/server.actions.ts @@ -14,6 +14,9 @@ export const Actions = { clearStore: () => ({ type: Types.CLEAR_STORE }), + connectionAttempted: () => ({ + type: Types.CONNECTION_ATTEMPTED + }), loginSuccessful: (options: WebSocketConnectOptions) => ({ type: Types.LOGIN_SUCCESSFUL, options diff --git a/webclient/src/store/server/server.dispatch.spec.ts b/webclient/src/store/server/server.dispatch.spec.ts index 5a4198d2f..ad2365a79 100644 --- a/webclient/src/store/server/server.dispatch.spec.ts +++ b/webclient/src/store/server/server.dispatch.spec.ts @@ -32,6 +32,11 @@ describe('Dispatch', () => { expect(store.dispatch).toHaveBeenCalledWith(Actions.clearStore()); }); + it('connectionAttempted dispatches Actions.connectionAttempted()', () => { + Dispatch.connectionAttempted(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.connectionAttempted()); + }); + it('loginSuccessful dispatches Actions.loginSuccessful()', () => { const options = makeConnectOptions(); Dispatch.loginSuccessful(options); @@ -391,4 +396,9 @@ describe('Dispatch', () => { Dispatch.gamesOfUser('alice', games, gametypeMap); expect(store.dispatch).toHaveBeenCalledWith(Actions.gamesOfUser('alice', games, gametypeMap)); }); + + it('clearRegistrationErrors dispatches correctly', () => { + Dispatch.clearRegistrationErrors(); + expect(store.dispatch).toHaveBeenCalledWith(Actions.clearRegistrationErrors()); + }); }); diff --git a/webclient/src/store/server/server.dispatch.ts b/webclient/src/store/server/server.dispatch.ts index 6f0177a7f..2030a4dad 100644 --- a/webclient/src/store/server/server.dispatch.ts +++ b/webclient/src/store/server/server.dispatch.ts @@ -14,6 +14,9 @@ export const Dispatch = { clearStore: () => { store.dispatch(Actions.clearStore()); }, + connectionAttempted: () => { + store.dispatch(Actions.connectionAttempted()); + }, loginSuccessful: (options: WebSocketConnectOptions) => { store.dispatch(Actions.loginSuccessful(options)); }, diff --git a/webclient/src/store/server/server.interfaces.ts b/webclient/src/store/server/server.interfaces.ts index 6ee8f29fe..e4cc101fa 100644 --- a/webclient/src/store/server/server.interfaces.ts +++ b/webclient/src/store/server/server.interfaces.ts @@ -76,6 +76,7 @@ export interface ServerState { } export interface ServerStateStatus { + connectionAttemptMade: boolean; description: string; state: number; } diff --git a/webclient/src/store/server/server.reducer.spec.ts b/webclient/src/store/server/server.reducer.spec.ts index aa961dfe0..f3ec5bbde 100644 --- a/webclient/src/store/server/server.reducer.spec.ts +++ b/webclient/src/store/server/server.reducer.spec.ts @@ -55,6 +55,12 @@ describe('Initialisation', () => { // ── Account & Connection ───────────────────────────────────────────────────── describe('Account & Connection', () => { + it('CONNECTION_ATTEMPTED → sets connectionAttemptMade to true', () => { + const state = makeServerState({ status: { connectionAttemptMade: false, state: StatusEnum.DISCONNECTED, description: null } }); + const result = serverReducer(state, { type: Types.CONNECTION_ATTEMPTED }); + expect(result.status.connectionAttemptMade).toBe(true); + }); + it('ACCOUNT_AWAITING_ACTIVATION → returns state unchanged', () => { const options = makeConnectOptions(); const state = makeServerState(); diff --git a/webclient/src/store/server/server.reducer.ts b/webclient/src/store/server/server.reducer.ts index 859b0a168..17440339d 100644 --- a/webclient/src/store/server/server.reducer.ts +++ b/webclient/src/store/server/server.reducer.ts @@ -69,6 +69,7 @@ const initialState: ServerState = { ignoreList: [], status: { + connectionAttemptMade: false, state: StatusEnum.DISCONNECTED, description: null }, @@ -112,6 +113,12 @@ export const serverReducer = (state = initialState, action: ServerAction) => { initialized: true } } + case Types.CONNECTION_ATTEMPTED: { + return { + ...state, + status: { ...state.status, connectionAttemptMade: true } + }; + } case Types.ACCOUNT_AWAITING_ACTIVATION: { return state; } diff --git a/webclient/src/store/server/server.selectors.spec.ts b/webclient/src/store/server/server.selectors.spec.ts index 9adcdbaca..f8658524f 100644 --- a/webclient/src/store/server/server.selectors.spec.ts +++ b/webclient/src/store/server/server.selectors.spec.ts @@ -34,15 +34,20 @@ describe('Selectors', () => { }); it('getDescription → returns status.description', () => { - const state = makeServerState({ status: { state: StatusEnum.CONNECTED, description: 'ok' } }); + const state = makeServerState({ status: { connectionAttemptMade: false, state: StatusEnum.CONNECTED, description: 'ok' } }); expect(Selectors.getDescription(rootState(state))).toBe('ok'); }); it('getState → returns status.state', () => { - const state = makeServerState({ status: { state: StatusEnum.LOGGED_IN, description: null } }); + const state = makeServerState({ status: { connectionAttemptMade: false, state: StatusEnum.LOGGED_IN, description: null } }); expect(Selectors.getState(rootState(state))).toBe(StatusEnum.LOGGED_IN); }); + it('getConnectionAttemptMade → returns status.connectionAttemptMade', () => { + const state = makeServerState({ status: { connectionAttemptMade: true, state: StatusEnum.DISCONNECTED, description: null } }); + expect(Selectors.getConnectionAttemptMade(rootState(state))).toBe(true); + }); + it('getUser → returns user', () => { const user = makeUser({ name: 'Alice' }); const state = makeServerState({ user }); @@ -89,4 +94,9 @@ describe('Selectors', () => { const state = makeServerState({ backendDecks: null }); expect(Selectors.getBackendDecks(rootState(state))).toBeNull(); }); + + it('getRegistrationError → returns registrationError', () => { + const state = makeServerState({ registrationError: 'bad input' }); + expect(Selectors.getRegistrationError(rootState(state))).toBe('bad input'); + }); }); diff --git a/webclient/src/store/server/server.selectors.ts b/webclient/src/store/server/server.selectors.ts index 260757ab9..e7e0ca1bc 100644 --- a/webclient/src/store/server/server.selectors.ts +++ b/webclient/src/store/server/server.selectors.ts @@ -11,6 +11,7 @@ export const Selectors = { getVersion: ({ server }: State) => server.info.version, getDescription: ({ server }: State) => server.status.description, getState: ({ server }: State) => server.status.state, + getConnectionAttemptMade: ({ server }: State) => server.status.connectionAttemptMade, getUser: ({ server }: State) => server.user, getUsers: ({ server }: State) => server.users, getLogs: ({ server }: State) => server.logs, diff --git a/webclient/src/store/server/server.types.ts b/webclient/src/store/server/server.types.ts index 159e33094..89d3e7666 100644 --- a/webclient/src/store/server/server.types.ts +++ b/webclient/src/store/server/server.types.ts @@ -1,6 +1,7 @@ export const Types = { INITIALIZED: '[Server] Initialized', CLEAR_STORE: '[Server] Clear Store', + CONNECTION_ATTEMPTED: '[Server] Connection Attempted', LOGIN_SUCCESSFUL: '[Server] Login Successful', LOGIN_FAILED: '[Server] Login Failed', CONNECTION_CLOSED: '[Server] Connection Closed', diff --git a/webclient/src/websocket/WebClient.spec.ts b/webclient/src/websocket/WebClient.spec.ts index 4f27d5580..799c7e552 100644 --- a/webclient/src/websocket/WebClient.spec.ts +++ b/webclient/src/websocket/WebClient.spec.ts @@ -1,19 +1,27 @@ +const captured = vi.hoisted(() => ({ + wsOptions: null as any, + pbOptions: null as any, +})); + vi.mock('./services/WebSocketService', () => ({ - WebSocketService: vi.fn().mockImplementation(function WebSocketServiceImpl() { + WebSocketService: vi.fn().mockImplementation(function WebSocketServiceImpl(options: any) { + captured.wsOptions = options; return { message$: { subscribe: vi.fn() }, connect: vi.fn(), testConnect: vi.fn(), disconnect: vi.fn(), + send: vi.fn(), + checkReadyState: vi.fn().mockReturnValue(true), }; }), })); vi.mock('./services/ProtobufService', () => ({ - ProtobufService: vi.fn().mockImplementation(function ProtobufServiceImpl() { + ProtobufService: vi.fn().mockImplementation(function ProtobufServiceImpl(options: any) { + captured.pbOptions = options; return { handleMessageEvent: vi.fn(), - sendKeepAliveCommand: vi.fn(), resetCommands: vi.fn(), }; }), @@ -21,17 +29,22 @@ vi.mock('./services/ProtobufService', () => ({ vi.mock('./persistence', () => ({ RoomPersistence: { clearStore: vi.fn() }, - SessionPersistence: { clearStore: vi.fn(), initialized: vi.fn() }, + SessionPersistence: { clearStore: vi.fn(), initialized: vi.fn(), connectionAttempted: vi.fn() }, })); vi.mock('store', () => ({ GameDispatch: { clearStore: vi.fn() }, })); +vi.mock('./commands/session', () => ({ + ping: vi.fn(), +})); + import { WebClient } from './WebClient'; import { WebSocketService } from './services/WebSocketService'; import { ProtobufService } from './services/ProtobufService'; import { RoomPersistence, SessionPersistence } from './persistence'; +import { ping } from './commands/session'; import { StatusEnum } from 'types'; import { Subject } from 'rxjs'; import { Mock } from 'vitest'; @@ -42,20 +55,23 @@ describe('WebClient', () => { beforeEach(() => { vi.clearAllMocks(); - (ProtobufService as Mock).mockImplementation(function ProtobufServiceImpl() { + (ProtobufService as Mock).mockImplementation(function ProtobufServiceImpl(options: any) { + captured.pbOptions = options; return { handleMessageEvent: vi.fn(), - sendKeepAliveCommand: vi.fn(), resetCommands: vi.fn(), }; }); messageSubject = new Subject(); - (WebSocketService as Mock).mockImplementation(function WebSocketServiceImpl() { + (WebSocketService as Mock).mockImplementation(function WebSocketServiceImpl(options: any) { + captured.wsOptions = options; return { message$: messageSubject, connect: vi.fn(), testConnect: vi.fn(), disconnect: vi.fn(), + send: vi.fn(), + checkReadyState: vi.fn().mockReturnValue(true), }; }); // suppress console.log from constructor in non-test-env check @@ -80,10 +96,10 @@ describe('WebClient', () => { }); describe('connect', () => { - it('sets connectionAttemptMade to true', () => { + it('calls SessionPersistence.connectionAttempted', () => { const opts: any = { host: 'h', port: 1 }; client.connect(opts); - expect(client.connectionAttemptMade).toBe(true); + expect(SessionPersistence.connectionAttempted).toHaveBeenCalled(); }); it('stores options and calls socket.connect', () => { @@ -109,14 +125,6 @@ describe('WebClient', () => { }); }); - describe('keepAlive', () => { - it('delegates to protobuf.sendKeepAliveCommand', () => { - const pingCb = vi.fn(); - client.keepAlive(pingCb); - expect(client.protobuf.sendKeepAliveCommand).toHaveBeenCalledWith(pingCb); - }); - }); - describe('updateStatus', () => { it('sets the status', () => { client.updateStatus(StatusEnum.CONNECTED); @@ -136,4 +144,24 @@ describe('WebClient', () => { expect(RoomPersistence.clearStore).not.toHaveBeenCalled(); }); }); + + describe('constructor closures', () => { + it('keepAliveFn calls ping with the callback', () => { + const cb = vi.fn(); + captured.wsOptions.keepAliveFn(cb); + expect(ping).toHaveBeenCalledWith(cb); + }); + + it('send closure delegates to socket.send', () => { + const data = new Uint8Array([1, 2, 3]); + captured.pbOptions.send(data); + expect(client.socket.send).toHaveBeenCalledWith(data); + }); + + it('isOpen closure delegates to socket.checkReadyState', () => { + const result = captured.pbOptions.isOpen(); + expect(client.socket.checkReadyState).toHaveBeenCalledWith(WebSocket.OPEN); + expect(result).toBe(true); + }); + }); }); diff --git a/webclient/src/websocket/WebClient.ts b/webclient/src/websocket/WebClient.ts index 302a0da3f..5895c653a 100644 --- a/webclient/src/websocket/WebClient.ts +++ b/webclient/src/websocket/WebClient.ts @@ -2,47 +2,28 @@ import { StatusEnum, WebSocketConnectOptions } from 'types'; import { ProtobufService } from './services/ProtobufService'; import { WebSocketService } from './services/WebSocketService'; +import { ping } from './commands/session'; import { GameDispatch } from 'store'; import { RoomPersistence, SessionPersistence } from './persistence'; export class WebClient { - public socket = new WebSocketService(this); - public protobuf = new ProtobufService(this); - - public protocolVersion = 14; - public clientConfig = { - clientid: 'webatrice', - clientver: 'webclient-1.0 (2019-10-31)', - clientfeatures: [ - 'client_id', - 'client_ver', - 'feature_set', - 'room_chat_history', - 'client_warnings', - /* unimplemented features */ - 'forgot_password', - 'idle_client', - 'mod_log_lookup', - 'user_ban_history', - // satisfy server reqs for POC - 'websocket', - '2.7.0_min_version', - '2.8.0_min_version' - ] - }; - - public clientOptions = { - autojoinrooms: true, - keepalive: 5000 - }; + public socket: WebSocketService; + public protobuf: ProtobufService; public options: WebSocketConnectOptions; public status: StatusEnum; - public connectionAttemptMade = false; - constructor() { + this.socket = new WebSocketService({ + keepAliveFn: (cb) => ping(cb), + }); + + this.protobuf = new ProtobufService({ + send: (data) => this.socket.send(data), + isOpen: () => this.socket.checkReadyState(WebSocket.OPEN), + }); + this.socket.message$.subscribe((message: MessageEvent) => { this.protobuf.handleMessageEvent(message); }); @@ -55,7 +36,7 @@ export class WebClient { } public connect(options: WebSocketConnectOptions) { - this.connectionAttemptMade = true; + SessionPersistence.connectionAttempted(); this.options = options; this.socket.connect(options); } @@ -77,10 +58,6 @@ export class WebClient { } } - public keepAlive(pingReceived: () => void) { - this.protobuf.sendKeepAliveCommand(pingReceived); - } - private clearStores() { GameDispatch.clearStore(); RoomPersistence.clearStore(); diff --git a/webclient/src/websocket/__mocks__/sessionCommandMocks.ts b/webclient/src/websocket/__mocks__/sessionCommandMocks.ts index cbab4833c..227eca31c 100644 --- a/webclient/src/websocket/__mocks__/sessionCommandMocks.ts +++ b/webclient/src/websocket/__mocks__/sessionCommandMocks.ts @@ -17,11 +17,11 @@ export function makeWebClientMock() { testConnect: vi.fn(), disconnect: vi.fn(), updateStatus: vi.fn(), - clientConfig: { clientid: 'webatrice', clientver: '1.0', clientfeatures: [] }, options: {}, - protocolVersion: 14, status: 0, - connectionAttemptMade: false, + protobuf: { + sendSessionCommand: vi.fn(), + }, }; } diff --git a/webclient/src/websocket/commands/admin/adjustMod.ts b/webclient/src/websocket/commands/admin/adjustMod.ts index 7b61e322a..f6fba41e8 100644 --- a/webclient/src/websocket/commands/admin/adjustMod.ts +++ b/webclient/src/websocket/commands/admin/adjustMod.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_AdjustMod_ext, Command_AdjustModSchema } from 'generated/proto/admin_commands_pb'; import { AdminPersistence } from '../../persistence'; export function adjustMod(userName: string, shouldBeMod?: boolean, shouldBeJudge?: boolean): void { - BackendService.sendAdminCommand(Command_AdjustMod_ext, create(Command_AdjustModSchema, { userName, shouldBeMod, shouldBeJudge }), { + webClient.protobuf.sendAdminCommand(Command_AdjustMod_ext, create(Command_AdjustModSchema, { userName, shouldBeMod, shouldBeJudge }), { onSuccess: () => { AdminPersistence.adjustMod(userName, shouldBeMod, shouldBeJudge); }, diff --git a/webclient/src/websocket/commands/admin/adminCommands.spec.ts b/webclient/src/websocket/commands/admin/adminCommands.spec.ts index fd1778e9f..d3422d148 100644 --- a/webclient/src/websocket/commands/admin/adminCommands.spec.ts +++ b/webclient/src/websocket/commands/admin/adminCommands.spec.ts @@ -1,7 +1,6 @@ -vi.mock('../../services/BackendService', () => ({ - BackendService: { - sendAdminCommand: vi.fn(), - }, +vi.mock('../../WebClient', () => ({ + __esModule: true, + default: { protobuf: { sendAdminCommand: vi.fn() } }, })); vi.mock('../../persistence', () => ({ @@ -14,7 +13,7 @@ vi.mock('../../persistence', () => ({ })); import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { AdminPersistence } from '../../persistence'; import { adjustMod } from './adjustMod'; import { reloadConfig } from './reloadConfig'; @@ -24,7 +23,7 @@ import { updateServerMessage } from './updateServerMessage'; import { Mock } from 'vitest'; const { invokeOnSuccess } = makeCallbackHelpers( - BackendService.sendAdminCommand as Mock, + webClient.protobuf.sendAdminCommand as Mock, 2 ); @@ -37,7 +36,7 @@ describe('adjustMod', () => { it('calls sendAdminCommand with Command_AdjustMod', () => { adjustMod('alice', true, false); - expect(BackendService.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); }); it('onSuccess calls AdminPersistence.adjustMod', () => { @@ -54,7 +53,7 @@ describe('reloadConfig', () => { it('calls sendAdminCommand with Command_ReloadConfig', () => { reloadConfig(); - expect(BackendService.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); }); it('onSuccess calls AdminPersistence.reloadConfig', () => { @@ -71,7 +70,7 @@ describe('shutdownServer', () => { it('calls sendAdminCommand with Command_ShutdownServer', () => { shutdownServer('maintenance', 10); - expect(BackendService.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); }); it('onSuccess calls AdminPersistence.shutdownServer', () => { @@ -88,7 +87,7 @@ describe('updateServerMessage', () => { it('calls sendAdminCommand with Command_UpdateServerMessage', () => { updateServerMessage(); - expect(BackendService.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendAdminCommand).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), expect.any(Object)); }); it('onSuccess calls AdminPersistence.updateServerMessage', () => { diff --git a/webclient/src/websocket/commands/admin/reloadConfig.ts b/webclient/src/websocket/commands/admin/reloadConfig.ts index e72735c60..08c92ffee 100644 --- a/webclient/src/websocket/commands/admin/reloadConfig.ts +++ b/webclient/src/websocket/commands/admin/reloadConfig.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ReloadConfig_ext, Command_ReloadConfigSchema } from 'generated/proto/admin_commands_pb'; import { AdminPersistence } from '../../persistence'; export function reloadConfig(): void { - BackendService.sendAdminCommand(Command_ReloadConfig_ext, create(Command_ReloadConfigSchema), { + webClient.protobuf.sendAdminCommand(Command_ReloadConfig_ext, create(Command_ReloadConfigSchema), { onSuccess: () => { AdminPersistence.reloadConfig(); }, diff --git a/webclient/src/websocket/commands/admin/shutdownServer.ts b/webclient/src/websocket/commands/admin/shutdownServer.ts index 7fc32fb24..86cd75259 100644 --- a/webclient/src/websocket/commands/admin/shutdownServer.ts +++ b/webclient/src/websocket/commands/admin/shutdownServer.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ShutdownServer_ext, Command_ShutdownServerSchema } from 'generated/proto/admin_commands_pb'; import { AdminPersistence } from '../../persistence'; export function shutdownServer(reason: string, minutes: number): void { - BackendService.sendAdminCommand(Command_ShutdownServer_ext, create(Command_ShutdownServerSchema, { reason, minutes }), { + webClient.protobuf.sendAdminCommand(Command_ShutdownServer_ext, create(Command_ShutdownServerSchema, { reason, minutes }), { onSuccess: () => { AdminPersistence.shutdownServer(); }, diff --git a/webclient/src/websocket/commands/admin/updateServerMessage.ts b/webclient/src/websocket/commands/admin/updateServerMessage.ts index d81b63028..4bd1c3f12 100644 --- a/webclient/src/websocket/commands/admin/updateServerMessage.ts +++ b/webclient/src/websocket/commands/admin/updateServerMessage.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_UpdateServerMessage_ext, Command_UpdateServerMessageSchema } from 'generated/proto/admin_commands_pb'; import { AdminPersistence } from '../../persistence'; export function updateServerMessage(): void { - BackendService.sendAdminCommand(Command_UpdateServerMessage_ext, create(Command_UpdateServerMessageSchema), { + webClient.protobuf.sendAdminCommand(Command_UpdateServerMessage_ext, create(Command_UpdateServerMessageSchema), { onSuccess: () => { AdminPersistence.updateServerMessage(); }, diff --git a/webclient/src/websocket/commands/game/attachCard.ts b/webclient/src/websocket/commands/game/attachCard.ts index 08162f651..08c6b110f 100644 --- a/webclient/src/websocket/commands/game/attachCard.ts +++ b/webclient/src/websocket/commands/game/attachCard.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_AttachCardSchema, Command_AttachCard_ext } from 'generated/proto/command_attach_card_pb'; import { AttachCardParams } from 'types'; export function attachCard(gameId: number, params: AttachCardParams): void { - BackendService.sendGameCommand(gameId, Command_AttachCard_ext, create(Command_AttachCardSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_AttachCard_ext, create(Command_AttachCardSchema, params)); } diff --git a/webclient/src/websocket/commands/game/changeZoneProperties.ts b/webclient/src/websocket/commands/game/changeZoneProperties.ts index 7ee1feaca..a35e9ec25 100644 --- a/webclient/src/websocket/commands/game/changeZoneProperties.ts +++ b/webclient/src/websocket/commands/game/changeZoneProperties.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ChangeZonePropertiesSchema, Command_ChangeZoneProperties_ext } from 'generated/proto/command_change_zone_properties_pb'; import { ChangeZonePropertiesParams } from 'types'; export function changeZoneProperties(gameId: number, params: ChangeZonePropertiesParams): void { - BackendService.sendGameCommand(gameId, Command_ChangeZoneProperties_ext, create(Command_ChangeZonePropertiesSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_ChangeZoneProperties_ext, create(Command_ChangeZonePropertiesSchema, params)); } diff --git a/webclient/src/websocket/commands/game/concede.ts b/webclient/src/websocket/commands/game/concede.ts index 50e9c6d29..8aaad96b1 100644 --- a/webclient/src/websocket/commands/game/concede.ts +++ b/webclient/src/websocket/commands/game/concede.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ConcedeSchema, Command_Concede_ext } from 'generated/proto/command_concede_pb'; export function concede(gameId: number): void { - BackendService.sendGameCommand(gameId, Command_Concede_ext, create(Command_ConcedeSchema)); + webClient.protobuf.sendGameCommand(gameId, Command_Concede_ext, create(Command_ConcedeSchema)); } diff --git a/webclient/src/websocket/commands/game/createArrow.ts b/webclient/src/websocket/commands/game/createArrow.ts index 0a641585b..d85b3168e 100644 --- a/webclient/src/websocket/commands/game/createArrow.ts +++ b/webclient/src/websocket/commands/game/createArrow.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_CreateArrowSchema, Command_CreateArrow_ext } from 'generated/proto/command_create_arrow_pb'; import { CreateArrowParams } from 'types'; export function createArrow(gameId: number, params: CreateArrowParams): void { - BackendService.sendGameCommand(gameId, Command_CreateArrow_ext, create(Command_CreateArrowSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_CreateArrow_ext, create(Command_CreateArrowSchema, params)); } diff --git a/webclient/src/websocket/commands/game/createCounter.ts b/webclient/src/websocket/commands/game/createCounter.ts index 1bbf99478..53153efc3 100644 --- a/webclient/src/websocket/commands/game/createCounter.ts +++ b/webclient/src/websocket/commands/game/createCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_CreateCounterSchema, Command_CreateCounter_ext } from 'generated/proto/command_create_counter_pb'; import { CreateCounterParams } from 'types'; export function createCounter(gameId: number, params: CreateCounterParams): void { - BackendService.sendGameCommand(gameId, Command_CreateCounter_ext, create(Command_CreateCounterSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_CreateCounter_ext, create(Command_CreateCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/createToken.ts b/webclient/src/websocket/commands/game/createToken.ts index 4b554e688..06780afba 100644 --- a/webclient/src/websocket/commands/game/createToken.ts +++ b/webclient/src/websocket/commands/game/createToken.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_CreateTokenSchema, Command_CreateToken_ext } from 'generated/proto/command_create_token_pb'; import { CreateTokenParams } from 'types'; export function createToken(gameId: number, params: CreateTokenParams): void { - BackendService.sendGameCommand(gameId, Command_CreateToken_ext, create(Command_CreateTokenSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_CreateToken_ext, create(Command_CreateTokenSchema, params)); } diff --git a/webclient/src/websocket/commands/game/deckSelect.ts b/webclient/src/websocket/commands/game/deckSelect.ts index 60a874150..33905c7ac 100644 --- a/webclient/src/websocket/commands/game/deckSelect.ts +++ b/webclient/src/websocket/commands/game/deckSelect.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DeckSelectSchema, Command_DeckSelect_ext } from 'generated/proto/command_deck_select_pb'; import { DeckSelectParams } from 'types'; export function deckSelect(gameId: number, params: DeckSelectParams): void { - BackendService.sendGameCommand(gameId, Command_DeckSelect_ext, create(Command_DeckSelectSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_DeckSelect_ext, create(Command_DeckSelectSchema, params)); } diff --git a/webclient/src/websocket/commands/game/delCounter.ts b/webclient/src/websocket/commands/game/delCounter.ts index b84fb5a0f..ba9e10d55 100644 --- a/webclient/src/websocket/commands/game/delCounter.ts +++ b/webclient/src/websocket/commands/game/delCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DelCounterSchema, Command_DelCounter_ext } from 'generated/proto/command_del_counter_pb'; import { DelCounterParams } from 'types'; export function delCounter(gameId: number, params: DelCounterParams): void { - BackendService.sendGameCommand(gameId, Command_DelCounter_ext, create(Command_DelCounterSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_DelCounter_ext, create(Command_DelCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/deleteArrow.ts b/webclient/src/websocket/commands/game/deleteArrow.ts index 67929e2a2..dc15a2d00 100644 --- a/webclient/src/websocket/commands/game/deleteArrow.ts +++ b/webclient/src/websocket/commands/game/deleteArrow.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DeleteArrowSchema, Command_DeleteArrow_ext } from 'generated/proto/command_delete_arrow_pb'; import { DeleteArrowParams } from 'types'; export function deleteArrow(gameId: number, params: DeleteArrowParams): void { - BackendService.sendGameCommand(gameId, Command_DeleteArrow_ext, create(Command_DeleteArrowSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_DeleteArrow_ext, create(Command_DeleteArrowSchema, params)); } diff --git a/webclient/src/websocket/commands/game/drawCards.ts b/webclient/src/websocket/commands/game/drawCards.ts index ad5a08771..1ba96ca8f 100644 --- a/webclient/src/websocket/commands/game/drawCards.ts +++ b/webclient/src/websocket/commands/game/drawCards.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DrawCardsSchema, Command_DrawCards_ext } from 'generated/proto/command_draw_cards_pb'; import { DrawCardsParams } from 'types'; export function drawCards(gameId: number, params: DrawCardsParams): void { - BackendService.sendGameCommand(gameId, Command_DrawCards_ext, create(Command_DrawCardsSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_DrawCards_ext, create(Command_DrawCardsSchema, params)); } diff --git a/webclient/src/websocket/commands/game/dumpZone.ts b/webclient/src/websocket/commands/game/dumpZone.ts index 9075b2dcf..3cee63e7e 100644 --- a/webclient/src/websocket/commands/game/dumpZone.ts +++ b/webclient/src/websocket/commands/game/dumpZone.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DumpZoneSchema, Command_DumpZone_ext } from 'generated/proto/command_dump_zone_pb'; import { DumpZoneParams } from 'types'; export function dumpZone(gameId: number, params: DumpZoneParams): void { - BackendService.sendGameCommand(gameId, Command_DumpZone_ext, create(Command_DumpZoneSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_DumpZone_ext, create(Command_DumpZoneSchema, params)); } diff --git a/webclient/src/websocket/commands/game/flipCard.ts b/webclient/src/websocket/commands/game/flipCard.ts index c12729d46..b8d1130cf 100644 --- a/webclient/src/websocket/commands/game/flipCard.ts +++ b/webclient/src/websocket/commands/game/flipCard.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_FlipCardSchema, Command_FlipCard_ext } from 'generated/proto/command_flip_card_pb'; import { FlipCardParams } from 'types'; export function flipCard(gameId: number, params: FlipCardParams): void { - BackendService.sendGameCommand(gameId, Command_FlipCard_ext, create(Command_FlipCardSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_FlipCard_ext, create(Command_FlipCardSchema, params)); } diff --git a/webclient/src/websocket/commands/game/gameCommands.spec.ts b/webclient/src/websocket/commands/game/gameCommands.spec.ts index 1daf3a7dc..ddb671f88 100644 --- a/webclient/src/websocket/commands/game/gameCommands.spec.ts +++ b/webclient/src/websocket/commands/game/gameCommands.spec.ts @@ -1,4 +1,4 @@ -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { create, setExtension } from '@bufbuild/protobuf'; import { GameCommandSchema, Command_Judge_ext } from 'generated/proto/game_commands_pb'; import { Command_DrawCardsSchema, Command_DrawCards_ext } from 'generated/proto/command_draw_cards_pb'; @@ -66,8 +66,9 @@ import { undoDraw } from './undoDraw'; import { unconcede } from './unconcede'; import { judge } from './judge'; -vi.mock('../../services/BackendService', () => ({ - BackendService: { sendGameCommand: vi.fn() }, +vi.mock('../../WebClient', () => ({ + __esModule: true, + default: { protobuf: { sendGameCommand: vi.fn() } }, })); const gameId = 1; @@ -75,116 +76,124 @@ const gameId = 1; import { Mock } from 'vitest'; beforeEach(() => { - (BackendService.sendGameCommand as Mock).mockClear(); + (webClient.protobuf.sendGameCommand as Mock).mockClear(); }); -describe('Game commands — delegate to BackendService.sendGameCommand', () => { +describe('Game commands — delegate to webClient.protobuf.sendGameCommand', () => { it('attachCard sends Command_AttachCard', () => { attachCard(gameId, { cardId: 10, startZone: 'hand' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_AttachCard_ext, expect.objectContaining({ cardId: 10, startZone: 'hand' }) ); }); it('changeZoneProperties sends Command_ChangeZoneProperties', () => { changeZoneProperties(gameId, { zoneName: 'side' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_ChangeZoneProperties_ext, expect.objectContaining({ zoneName: 'side' }) ); }); it('concede sends Command_Concede with empty object', () => { concede(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Concede_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Concede_ext, expect.any(Object)); }); it('createArrow sends Command_CreateArrow', () => { createArrow(gameId, { startPlayerId: 1, startZone: 'hand' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_CreateArrow_ext, expect.objectContaining({ startPlayerId: 1, startZone: 'hand' }) ); }); it('createCounter sends Command_CreateCounter', () => { createCounter(gameId, { counterName: 'life' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_CreateCounter_ext, expect.objectContaining({ counterName: 'life' }) ); }); it('createToken sends Command_CreateToken', () => { createToken(gameId, { cardName: 'Goblin', zone: 'play' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_CreateToken_ext, expect.objectContaining({ cardName: 'Goblin', zone: 'play' }) ); }); it('deckSelect sends Command_DeckSelect', () => { deckSelect(gameId, { deckId: 5 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DeckSelect_ext, expect.objectContaining({ deckId: 5 })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DeckSelect_ext, expect.objectContaining({ deckId: 5 })); }); it('delCounter sends Command_DelCounter', () => { delCounter(gameId, { counterId: 3 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DelCounter_ext, expect.objectContaining({ counterId: 3 })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_DelCounter_ext, expect.objectContaining({ counterId: 3 }) + ); }); it('deleteArrow sends Command_DeleteArrow', () => { deleteArrow(gameId, { arrowId: 2 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DeleteArrow_ext, expect.objectContaining({ arrowId: 2 })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_DeleteArrow_ext, expect.objectContaining({ arrowId: 2 }) + ); }); it('drawCards sends Command_DrawCards', () => { drawCards(gameId, { number: 3 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DrawCards_ext, expect.objectContaining({ number: 3 })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DrawCards_ext, expect.objectContaining({ number: 3 })); }); it('dumpZone sends Command_DumpZone', () => { dumpZone(gameId, { playerId: 2, zoneName: 'library' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_DumpZone_ext, expect.objectContaining({ playerId: 2, zoneName: 'library' }) ); }); it('flipCard sends Command_FlipCard', () => { flipCard(gameId, { cardId: 7, faceDown: false }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_FlipCard_ext, expect.objectContaining({ cardId: 7, faceDown: false }) ); }); it('gameSay sends Command_GameSay', () => { gameSay(gameId, { message: 'hello' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_GameSay_ext, expect.objectContaining({ message: 'hello' })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_GameSay_ext, expect.objectContaining({ message: 'hello' }) + ); }); it('incCardCounter sends Command_IncCardCounter', () => { incCardCounter(gameId, { cardId: 5, counterId: 1 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_IncCardCounter_ext, expect.objectContaining({ cardId: 5, counterId: 1 }) ); }); it('incCounter sends Command_IncCounter', () => { incCounter(gameId, { counterId: 1, delta: 5 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_IncCounter_ext, expect.objectContaining({ counterId: 1, delta: 5 }) ); }); it('kickFromGame sends Command_KickFromGame', () => { kickFromGame(gameId, { playerId: 2 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_KickFromGame_ext, expect.objectContaining({ playerId: 2 })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_KickFromGame_ext, expect.objectContaining({ playerId: 2 }) + ); }); it('leaveGame sends Command_LeaveGame with empty object', () => { leaveGame(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_LeaveGame_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_LeaveGame_ext, expect.any(Object)); }); it('moveCard sends Command_MoveCard', () => { moveCard(gameId, { startZone: 'hand', targetZone: 'graveyard' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_MoveCard_ext, expect.objectContaining({ startZone: 'hand', targetZone: 'graveyard' }) ); @@ -192,39 +201,45 @@ describe('Game commands — delegate to BackendService.sendGameCommand', () => { it('mulligan sends Command_Mulligan', () => { mulligan(gameId, { number: 7 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Mulligan_ext, expect.objectContaining({ number: 7 })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_Mulligan_ext, expect.objectContaining({ number: 7 }) + ); }); it('nextTurn sends Command_NextTurn with empty object', () => { nextTurn(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_NextTurn_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_NextTurn_ext, expect.any(Object)); }); it('readyStart sends Command_ReadyStart', () => { readyStart(gameId, { ready: true }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_ReadyStart_ext, expect.objectContaining({ ready: true })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_ReadyStart_ext, expect.objectContaining({ ready: true }) + ); }); it('revealCards sends Command_RevealCards', () => { revealCards(gameId, { zoneName: 'hand', cardId: [1, 2] }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_RevealCards_ext, expect.objectContaining({ zoneName: 'hand', cardId: [1, 2] }) ); }); it('reverseTurn sends Command_ReverseTurn with empty object', () => { reverseTurn(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_ReverseTurn_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_ReverseTurn_ext, expect.any(Object)); }); it('setActivePhase sends Command_SetActivePhase', () => { setActivePhase(gameId, { phase: 2 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_SetActivePhase_ext, expect.objectContaining({ phase: 2 })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_SetActivePhase_ext, expect.objectContaining({ phase: 2 }) + ); }); it('setCardAttr sends Command_SetCardAttr', () => { setCardAttr(gameId, { zone: 'play', cardId: 5, attrValue: '2' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_SetCardAttr_ext, expect.objectContaining({ zone: 'play', cardId: 5, attrValue: '2' }) ); @@ -232,45 +247,47 @@ describe('Game commands — delegate to BackendService.sendGameCommand', () => { it('setCardCounter sends Command_SetCardCounter', () => { setCardCounter(gameId, { cardId: 5, counterId: 1 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_SetCardCounter_ext, expect.objectContaining({ cardId: 5, counterId: 1 }) ); }); it('setCounter sends Command_SetCounter', () => { setCounter(gameId, { counterId: 1, value: 10 }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_SetCounter_ext, expect.objectContaining({ counterId: 1, value: 10 }) ); }); it('setSideboardLock sends Command_SetSideboardLock', () => { setSideboardLock(gameId, { locked: true }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_SetSideboardLock_ext, expect.objectContaining({ locked: true }) ); }); it('setSideboardPlan sends Command_SetSideboardPlan', () => { setSideboardPlan(gameId, { moveList: [] }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_SetSideboardPlan_ext, expect.objectContaining({ moveList: expect.any(Array) }) ); }); it('shuffle sends Command_Shuffle', () => { shuffle(gameId, { zoneName: 'hand' }); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Shuffle_ext, expect.objectContaining({ zoneName: 'hand' })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Command_Shuffle_ext, expect.objectContaining({ zoneName: 'hand' }) + ); }); it('undoDraw sends Command_UndoDraw with empty object', () => { undoDraw(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_UndoDraw_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_UndoDraw_ext, expect.any(Object)); }); it('unconcede sends Command_Unconcede with empty object', () => { unconcede(gameId); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Unconcede_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Unconcede_ext, expect.any(Object)); }); it('judge sends Command_Judge with targetId and wrapped gameCommand array', () => { @@ -278,7 +295,7 @@ describe('Game commands — delegate to BackendService.sendGameCommand', () => { const innerCmd = create(GameCommandSchema); setExtension(innerCmd, Command_DrawCards_ext, create(Command_DrawCardsSchema, { number: 2 })); judge(gameId, targetId, innerCmd); - expect(BackendService.sendGameCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, Command_Judge_ext, expect.objectContaining({ targetId: 3, gameCommand: expect.any(Array) }) diff --git a/webclient/src/websocket/commands/game/gameSay.ts b/webclient/src/websocket/commands/game/gameSay.ts index a55085526..86a798f9c 100644 --- a/webclient/src/websocket/commands/game/gameSay.ts +++ b/webclient/src/websocket/commands/game/gameSay.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_GameSaySchema, Command_GameSay_ext } from 'generated/proto/command_game_say_pb'; import { GameSayParams } from 'types'; export function gameSay(gameId: number, params: GameSayParams): void { - BackendService.sendGameCommand(gameId, Command_GameSay_ext, create(Command_GameSaySchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_GameSay_ext, create(Command_GameSaySchema, params)); } diff --git a/webclient/src/websocket/commands/game/incCardCounter.ts b/webclient/src/websocket/commands/game/incCardCounter.ts index dd678044f..6a72d7baa 100644 --- a/webclient/src/websocket/commands/game/incCardCounter.ts +++ b/webclient/src/websocket/commands/game/incCardCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_IncCardCounterSchema, Command_IncCardCounter_ext } from 'generated/proto/command_inc_card_counter_pb'; import { IncCardCounterParams } from 'types'; export function incCardCounter(gameId: number, params: IncCardCounterParams): void { - BackendService.sendGameCommand(gameId, Command_IncCardCounter_ext, create(Command_IncCardCounterSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_IncCardCounter_ext, create(Command_IncCardCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/incCounter.ts b/webclient/src/websocket/commands/game/incCounter.ts index 997fc1303..f00a358c9 100644 --- a/webclient/src/websocket/commands/game/incCounter.ts +++ b/webclient/src/websocket/commands/game/incCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_IncCounterSchema, Command_IncCounter_ext } from 'generated/proto/command_inc_counter_pb'; import { IncCounterParams } from 'types'; export function incCounter(gameId: number, params: IncCounterParams): void { - BackendService.sendGameCommand(gameId, Command_IncCounter_ext, create(Command_IncCounterSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_IncCounter_ext, create(Command_IncCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/judge.ts b/webclient/src/websocket/commands/game/judge.ts index 57ad869eb..5142131bb 100644 --- a/webclient/src/websocket/commands/game/judge.ts +++ b/webclient/src/websocket/commands/game/judge.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_JudgeSchema, Command_Judge_ext } from 'generated/proto/game_commands_pb'; import type { GameCommand } from 'generated/proto/game_commands_pb'; export function judge(gameId: number, targetId: number, innerGameCommand: GameCommand): void { - BackendService.sendGameCommand(gameId, Command_Judge_ext, create(Command_JudgeSchema, { + webClient.protobuf.sendGameCommand(gameId, Command_Judge_ext, create(Command_JudgeSchema, { targetId, gameCommand: [innerGameCommand], })); diff --git a/webclient/src/websocket/commands/game/kickFromGame.ts b/webclient/src/websocket/commands/game/kickFromGame.ts index e56574e41..ef41a755c 100644 --- a/webclient/src/websocket/commands/game/kickFromGame.ts +++ b/webclient/src/websocket/commands/game/kickFromGame.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_KickFromGameSchema, Command_KickFromGame_ext } from 'generated/proto/command_kick_from_game_pb'; import { KickFromGameParams } from 'types'; export function kickFromGame(gameId: number, params: KickFromGameParams): void { - BackendService.sendGameCommand(gameId, Command_KickFromGame_ext, create(Command_KickFromGameSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_KickFromGame_ext, create(Command_KickFromGameSchema, params)); } diff --git a/webclient/src/websocket/commands/game/leaveGame.ts b/webclient/src/websocket/commands/game/leaveGame.ts index 6bdcfc661..f46503510 100644 --- a/webclient/src/websocket/commands/game/leaveGame.ts +++ b/webclient/src/websocket/commands/game/leaveGame.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_LeaveGameSchema, Command_LeaveGame_ext } from 'generated/proto/command_leave_game_pb'; export function leaveGame(gameId: number): void { - BackendService.sendGameCommand(gameId, Command_LeaveGame_ext, create(Command_LeaveGameSchema)); + webClient.protobuf.sendGameCommand(gameId, Command_LeaveGame_ext, create(Command_LeaveGameSchema)); } diff --git a/webclient/src/websocket/commands/game/moveCard.ts b/webclient/src/websocket/commands/game/moveCard.ts index 193d85b93..400a6171c 100644 --- a/webclient/src/websocket/commands/game/moveCard.ts +++ b/webclient/src/websocket/commands/game/moveCard.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_MoveCardSchema, Command_MoveCard_ext } from 'generated/proto/command_move_card_pb'; import { MoveCardParams } from 'types'; export function moveCard(gameId: number, params: MoveCardParams): void { - BackendService.sendGameCommand(gameId, Command_MoveCard_ext, create(Command_MoveCardSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_MoveCard_ext, create(Command_MoveCardSchema, params)); } diff --git a/webclient/src/websocket/commands/game/mulligan.ts b/webclient/src/websocket/commands/game/mulligan.ts index 54c42635e..d9285ddfc 100644 --- a/webclient/src/websocket/commands/game/mulligan.ts +++ b/webclient/src/websocket/commands/game/mulligan.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_MulliganSchema, Command_Mulligan_ext } from 'generated/proto/command_mulligan_pb'; import { MulliganParams } from 'types'; export function mulligan(gameId: number, params: MulliganParams): void { - BackendService.sendGameCommand(gameId, Command_Mulligan_ext, create(Command_MulliganSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_Mulligan_ext, create(Command_MulliganSchema, params)); } diff --git a/webclient/src/websocket/commands/game/nextTurn.ts b/webclient/src/websocket/commands/game/nextTurn.ts index 756cca49a..70b332ef2 100644 --- a/webclient/src/websocket/commands/game/nextTurn.ts +++ b/webclient/src/websocket/commands/game/nextTurn.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_NextTurnSchema, Command_NextTurn_ext } from 'generated/proto/command_next_turn_pb'; export function nextTurn(gameId: number): void { - BackendService.sendGameCommand(gameId, Command_NextTurn_ext, create(Command_NextTurnSchema)); + webClient.protobuf.sendGameCommand(gameId, Command_NextTurn_ext, create(Command_NextTurnSchema)); } diff --git a/webclient/src/websocket/commands/game/readyStart.ts b/webclient/src/websocket/commands/game/readyStart.ts index d6a608d9c..def0a445e 100644 --- a/webclient/src/websocket/commands/game/readyStart.ts +++ b/webclient/src/websocket/commands/game/readyStart.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ReadyStartSchema, Command_ReadyStart_ext } from 'generated/proto/command_ready_start_pb'; import { ReadyStartParams } from 'types'; export function readyStart(gameId: number, params: ReadyStartParams): void { - BackendService.sendGameCommand(gameId, Command_ReadyStart_ext, create(Command_ReadyStartSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_ReadyStart_ext, create(Command_ReadyStartSchema, params)); } diff --git a/webclient/src/websocket/commands/game/revealCards.ts b/webclient/src/websocket/commands/game/revealCards.ts index 18e054613..aaa860928 100644 --- a/webclient/src/websocket/commands/game/revealCards.ts +++ b/webclient/src/websocket/commands/game/revealCards.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_RevealCardsSchema, Command_RevealCards_ext } from 'generated/proto/command_reveal_cards_pb'; import { RevealCardsParams } from 'types'; export function revealCards(gameId: number, params: RevealCardsParams): void { - BackendService.sendGameCommand(gameId, Command_RevealCards_ext, create(Command_RevealCardsSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_RevealCards_ext, create(Command_RevealCardsSchema, params)); } diff --git a/webclient/src/websocket/commands/game/reverseTurn.ts b/webclient/src/websocket/commands/game/reverseTurn.ts index 38d34600a..e9be52a5b 100644 --- a/webclient/src/websocket/commands/game/reverseTurn.ts +++ b/webclient/src/websocket/commands/game/reverseTurn.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ReverseTurnSchema, Command_ReverseTurn_ext } from 'generated/proto/command_reverse_turn_pb'; export function reverseTurn(gameId: number): void { - BackendService.sendGameCommand(gameId, Command_ReverseTurn_ext, create(Command_ReverseTurnSchema)); + webClient.protobuf.sendGameCommand(gameId, Command_ReverseTurn_ext, create(Command_ReverseTurnSchema)); } diff --git a/webclient/src/websocket/commands/game/setActivePhase.ts b/webclient/src/websocket/commands/game/setActivePhase.ts index 799a700f3..9ccb04284 100644 --- a/webclient/src/websocket/commands/game/setActivePhase.ts +++ b/webclient/src/websocket/commands/game/setActivePhase.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_SetActivePhaseSchema, Command_SetActivePhase_ext } from 'generated/proto/command_set_active_phase_pb'; import { SetActivePhaseParams } from 'types'; export function setActivePhase(gameId: number, params: SetActivePhaseParams): void { - BackendService.sendGameCommand(gameId, Command_SetActivePhase_ext, create(Command_SetActivePhaseSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_SetActivePhase_ext, create(Command_SetActivePhaseSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setCardAttr.ts b/webclient/src/websocket/commands/game/setCardAttr.ts index f1b2bbd56..5e7e8c57f 100644 --- a/webclient/src/websocket/commands/game/setCardAttr.ts +++ b/webclient/src/websocket/commands/game/setCardAttr.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_SetCardAttrSchema, Command_SetCardAttr_ext } from 'generated/proto/command_set_card_attr_pb'; import { SetCardAttrParams } from 'types'; export function setCardAttr(gameId: number, params: SetCardAttrParams): void { - BackendService.sendGameCommand(gameId, Command_SetCardAttr_ext, create(Command_SetCardAttrSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_SetCardAttr_ext, create(Command_SetCardAttrSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setCardCounter.ts b/webclient/src/websocket/commands/game/setCardCounter.ts index 2aea270f8..18a8a991f 100644 --- a/webclient/src/websocket/commands/game/setCardCounter.ts +++ b/webclient/src/websocket/commands/game/setCardCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_SetCardCounterSchema, Command_SetCardCounter_ext } from 'generated/proto/command_set_card_counter_pb'; import { SetCardCounterParams } from 'types'; export function setCardCounter(gameId: number, params: SetCardCounterParams): void { - BackendService.sendGameCommand(gameId, Command_SetCardCounter_ext, create(Command_SetCardCounterSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_SetCardCounter_ext, create(Command_SetCardCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setCounter.ts b/webclient/src/websocket/commands/game/setCounter.ts index fdbb90bf9..579561d62 100644 --- a/webclient/src/websocket/commands/game/setCounter.ts +++ b/webclient/src/websocket/commands/game/setCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_SetCounterSchema, Command_SetCounter_ext } from 'generated/proto/command_set_counter_pb'; import { SetCounterParams } from 'types'; export function setCounter(gameId: number, params: SetCounterParams): void { - BackendService.sendGameCommand(gameId, Command_SetCounter_ext, create(Command_SetCounterSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_SetCounter_ext, create(Command_SetCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setSideboardLock.ts b/webclient/src/websocket/commands/game/setSideboardLock.ts index a8b82dff9..ff639a461 100644 --- a/webclient/src/websocket/commands/game/setSideboardLock.ts +++ b/webclient/src/websocket/commands/game/setSideboardLock.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_SetSideboardLockSchema, Command_SetSideboardLock_ext } from 'generated/proto/command_set_sideboard_lock_pb'; import { SetSideboardLockParams } from 'types'; export function setSideboardLock(gameId: number, params: SetSideboardLockParams): void { - BackendService.sendGameCommand(gameId, Command_SetSideboardLock_ext, create(Command_SetSideboardLockSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_SetSideboardLock_ext, create(Command_SetSideboardLockSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setSideboardPlan.ts b/webclient/src/websocket/commands/game/setSideboardPlan.ts index 65cac24e6..d69d85c89 100644 --- a/webclient/src/websocket/commands/game/setSideboardPlan.ts +++ b/webclient/src/websocket/commands/game/setSideboardPlan.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_SetSideboardPlanSchema, Command_SetSideboardPlan_ext } from 'generated/proto/command_set_sideboard_plan_pb'; import { SetSideboardPlanParams } from 'types'; export function setSideboardPlan(gameId: number, params: SetSideboardPlanParams): void { - BackendService.sendGameCommand(gameId, Command_SetSideboardPlan_ext, create(Command_SetSideboardPlanSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_SetSideboardPlan_ext, create(Command_SetSideboardPlanSchema, params)); } diff --git a/webclient/src/websocket/commands/game/shuffle.ts b/webclient/src/websocket/commands/game/shuffle.ts index 750926d3b..943ad4a57 100644 --- a/webclient/src/websocket/commands/game/shuffle.ts +++ b/webclient/src/websocket/commands/game/shuffle.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ShuffleSchema, Command_Shuffle_ext } from 'generated/proto/command_shuffle_pb'; import { ShuffleParams } from 'types'; export function shuffle(gameId: number, params: ShuffleParams): void { - BackendService.sendGameCommand(gameId, Command_Shuffle_ext, create(Command_ShuffleSchema, params)); + webClient.protobuf.sendGameCommand(gameId, Command_Shuffle_ext, create(Command_ShuffleSchema, params)); } diff --git a/webclient/src/websocket/commands/game/unconcede.ts b/webclient/src/websocket/commands/game/unconcede.ts index 82cc0d63f..e6888f7ce 100644 --- a/webclient/src/websocket/commands/game/unconcede.ts +++ b/webclient/src/websocket/commands/game/unconcede.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_UnconcedeSchema, Command_Unconcede_ext } from 'generated/proto/command_concede_pb'; export function unconcede(gameId: number): void { - BackendService.sendGameCommand(gameId, Command_Unconcede_ext, create(Command_UnconcedeSchema)); + webClient.protobuf.sendGameCommand(gameId, Command_Unconcede_ext, create(Command_UnconcedeSchema)); } diff --git a/webclient/src/websocket/commands/game/undoDraw.ts b/webclient/src/websocket/commands/game/undoDraw.ts index 9f70df3b8..dd0a3358f 100644 --- a/webclient/src/websocket/commands/game/undoDraw.ts +++ b/webclient/src/websocket/commands/game/undoDraw.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_UndoDrawSchema, Command_UndoDraw_ext } from 'generated/proto/command_undo_draw_pb'; export function undoDraw(gameId: number): void { - BackendService.sendGameCommand(gameId, Command_UndoDraw_ext, create(Command_UndoDrawSchema)); + webClient.protobuf.sendGameCommand(gameId, Command_UndoDraw_ext, create(Command_UndoDrawSchema)); } diff --git a/webclient/src/websocket/commands/moderator/banFromServer.ts b/webclient/src/websocket/commands/moderator/banFromServer.ts index 9fba4e72b..c0227fddd 100644 --- a/webclient/src/websocket/commands/moderator/banFromServer.ts +++ b/webclient/src/websocket/commands/moderator/banFromServer.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_BanFromServer_ext, Command_BanFromServerSchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; export function banFromServer(minutes: number, userName?: string, address?: string, reason?: string, visibleReason?: string, clientid?: string, removeMessages?: number): void { - BackendService.sendModeratorCommand(Command_BanFromServer_ext, create(Command_BanFromServerSchema, { + webClient.protobuf.sendModeratorCommand(Command_BanFromServer_ext, create(Command_BanFromServerSchema, { minutes, userName, address, reason, visibleReason, clientid, removeMessages }), { onSuccess: () => { diff --git a/webclient/src/websocket/commands/moderator/forceActivateUser.ts b/webclient/src/websocket/commands/moderator/forceActivateUser.ts index 43dd1fc65..b31f8df31 100644 --- a/webclient/src/websocket/commands/moderator/forceActivateUser.ts +++ b/webclient/src/websocket/commands/moderator/forceActivateUser.ts @@ -1,5 +1,5 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ForceActivateUser_ext, Command_ForceActivateUserSchema, } from 'generated/proto/moderator_commands_pb'; @@ -7,7 +7,7 @@ import { ModeratorPersistence } from '../../persistence'; export function forceActivateUser(usernameToActivate: string, moderatorName: string): void { const cmd = create(Command_ForceActivateUserSchema, { usernameToActivate, moderatorName }); - BackendService.sendModeratorCommand(Command_ForceActivateUser_ext, cmd, { + webClient.protobuf.sendModeratorCommand(Command_ForceActivateUser_ext, cmd, { onSuccess: () => { ModeratorPersistence.forceActivateUser(usernameToActivate, moderatorName); }, diff --git a/webclient/src/websocket/commands/moderator/getAdminNotes.ts b/webclient/src/websocket/commands/moderator/getAdminNotes.ts index 48b374581..9f554e536 100644 --- a/webclient/src/websocket/commands/moderator/getAdminNotes.ts +++ b/webclient/src/websocket/commands/moderator/getAdminNotes.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_GetAdminNotes_ext, Command_GetAdminNotesSchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; import { Response_GetAdminNotes_ext } from 'generated/proto/response_get_admin_notes_pb'; export function getAdminNotes(userName: string): void { - BackendService.sendModeratorCommand(Command_GetAdminNotes_ext, create(Command_GetAdminNotesSchema, { userName }), { + webClient.protobuf.sendModeratorCommand(Command_GetAdminNotes_ext, create(Command_GetAdminNotesSchema, { userName }), { responseExt: Response_GetAdminNotes_ext, onSuccess: (response) => { ModeratorPersistence.getAdminNotes(userName, response.notes); diff --git a/webclient/src/websocket/commands/moderator/getBanHistory.ts b/webclient/src/websocket/commands/moderator/getBanHistory.ts index a6a084912..c0fc9253d 100644 --- a/webclient/src/websocket/commands/moderator/getBanHistory.ts +++ b/webclient/src/websocket/commands/moderator/getBanHistory.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_GetBanHistory_ext, Command_GetBanHistorySchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; import { Response_BanHistory_ext } from 'generated/proto/response_ban_history_pb'; export function getBanHistory(userName: string): void { - BackendService.sendModeratorCommand(Command_GetBanHistory_ext, create(Command_GetBanHistorySchema, { userName }), { + webClient.protobuf.sendModeratorCommand(Command_GetBanHistory_ext, create(Command_GetBanHistorySchema, { userName }), { responseExt: Response_BanHistory_ext, onSuccess: (response) => { ModeratorPersistence.banHistory(userName, response.banList); diff --git a/webclient/src/websocket/commands/moderator/getWarnHistory.ts b/webclient/src/websocket/commands/moderator/getWarnHistory.ts index 15b03595e..4699b9333 100644 --- a/webclient/src/websocket/commands/moderator/getWarnHistory.ts +++ b/webclient/src/websocket/commands/moderator/getWarnHistory.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_GetWarnHistory_ext, Command_GetWarnHistorySchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; import { Response_WarnHistory_ext } from 'generated/proto/response_warn_history_pb'; export function getWarnHistory(userName: string): void { - BackendService.sendModeratorCommand(Command_GetWarnHistory_ext, create(Command_GetWarnHistorySchema, { userName }), { + webClient.protobuf.sendModeratorCommand(Command_GetWarnHistory_ext, create(Command_GetWarnHistorySchema, { userName }), { responseExt: Response_WarnHistory_ext, onSuccess: (response) => { ModeratorPersistence.warnHistory(userName, response.warnList); diff --git a/webclient/src/websocket/commands/moderator/getWarnList.ts b/webclient/src/websocket/commands/moderator/getWarnList.ts index 49282811e..60c268f10 100644 --- a/webclient/src/websocket/commands/moderator/getWarnList.ts +++ b/webclient/src/websocket/commands/moderator/getWarnList.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_GetWarnList_ext, Command_GetWarnListSchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; import { Response_WarnList_ext } from 'generated/proto/response_warn_list_pb'; export function getWarnList(modName: string, userName: string, userClientid: string): void { - BackendService.sendModeratorCommand(Command_GetWarnList_ext, create(Command_GetWarnListSchema, { modName, userName, userClientid }), { + webClient.protobuf.sendModeratorCommand(Command_GetWarnList_ext, create(Command_GetWarnListSchema, { modName, userName, userClientid }), { responseExt: Response_WarnList_ext, onSuccess: (response) => { ModeratorPersistence.warnListOptions([response]); diff --git a/webclient/src/websocket/commands/moderator/grantReplayAccess.ts b/webclient/src/websocket/commands/moderator/grantReplayAccess.ts index 84adc725e..3826dd9d4 100644 --- a/webclient/src/websocket/commands/moderator/grantReplayAccess.ts +++ b/webclient/src/websocket/commands/moderator/grantReplayAccess.ts @@ -1,14 +1,18 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_GrantReplayAccess_ext, Command_GrantReplayAccessSchema, } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; export function grantReplayAccess(replayId: number, moderatorName: string): void { - BackendService.sendModeratorCommand(Command_GrantReplayAccess_ext, create(Command_GrantReplayAccessSchema, { replayId, moderatorName }), { - onSuccess: () => { - ModeratorPersistence.grantReplayAccess(replayId, moderatorName); + webClient.protobuf.sendModeratorCommand( + Command_GrantReplayAccess_ext, + create(Command_GrantReplayAccessSchema, { replayId, moderatorName }), + { + onSuccess: () => { + ModeratorPersistence.grantReplayAccess(replayId, moderatorName); + }, }, - }); + ); } diff --git a/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts b/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts index 2ca5eea7b..70a1bd9ff 100644 --- a/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts +++ b/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts @@ -1,7 +1,6 @@ -vi.mock('../../services/BackendService', () => ({ - BackendService: { - sendModeratorCommand: vi.fn(), - }, +vi.mock('../../WebClient', () => ({ + __esModule: true, + default: { protobuf: { sendModeratorCommand: vi.fn() } }, })); vi.mock('../../persistence', () => ({ @@ -20,7 +19,7 @@ vi.mock('../../persistence', () => ({ })); import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { ModeratorPersistence } from '../../persistence'; import { Command_BanFromServer_ext, @@ -53,7 +52,7 @@ import { warnUser } from './warnUser'; import { Mock } from 'vitest'; const { invokeOnSuccess } = makeCallbackHelpers( - BackendService.sendModeratorCommand as Mock, + webClient.protobuf.sendModeratorCommand as Mock, 2 ); @@ -66,7 +65,7 @@ describe('banFromServer', () => { it('calls sendModeratorCommand with Command_BanFromServer', () => { banFromServer(30, 'alice', '1.2.3.4', 'reason', 'visible', 'cid', 1); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( Command_BanFromServer_ext, expect.objectContaining({ minutes: 30, userName: 'alice' }), expect.any(Object) @@ -87,7 +86,9 @@ describe('forceActivateUser', () => { it('calls sendModeratorCommand with Command_ForceActivateUser', () => { forceActivateUser('alice', 'mod1'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith(Command_ForceActivateUser_ext, expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( + Command_ForceActivateUser_ext, expect.any(Object), expect.any(Object) + ); }); it('onSuccess calls ModeratorPersistence.forceActivateUser', () => { @@ -104,7 +105,7 @@ describe('getAdminNotes', () => { it('calls sendModeratorCommand with Command_GetAdminNotes', () => { getAdminNotes('alice'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( Command_GetAdminNotes_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_GetAdminNotes_ext }) @@ -126,7 +127,7 @@ describe('getBanHistory', () => { it('calls sendModeratorCommand with Command_GetBanHistory', () => { getBanHistory('alice'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( Command_GetBanHistory_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_BanHistory_ext }) @@ -148,7 +149,7 @@ describe('getWarnHistory', () => { it('calls sendModeratorCommand with Command_GetWarnHistory', () => { getWarnHistory('alice'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( Command_GetWarnHistory_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_WarnHistory_ext }) @@ -170,7 +171,7 @@ describe('getWarnList', () => { it('calls sendModeratorCommand with Command_GetWarnList', () => { getWarnList('mod1', 'alice', 'US'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( Command_GetWarnList_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_WarnList_ext }) @@ -192,7 +193,9 @@ describe('grantReplayAccess', () => { it('calls sendModeratorCommand with Command_GrantReplayAccess', () => { grantReplayAccess(10, 'mod1'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith(Command_GrantReplayAccess_ext, expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( + Command_GrantReplayAccess_ext, expect.any(Object), expect.any(Object) + ); }); it('onSuccess calls ModeratorPersistence.grantReplayAccess', () => { @@ -209,7 +212,9 @@ describe('updateAdminNotes', () => { it('calls sendModeratorCommand with Command_UpdateAdminNotes', () => { updateAdminNotes('alice', 'new notes'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith(Command_UpdateAdminNotes_ext, expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( + Command_UpdateAdminNotes_ext, expect.any(Object), expect.any(Object) + ); }); it('onSuccess calls ModeratorPersistence.updateAdminNotes', () => { @@ -226,7 +231,7 @@ describe('viewLogHistory', () => { it('calls sendModeratorCommand with Command_ViewLogHistory', () => { viewLogHistory({ dateRange: 7 } as any); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( Command_ViewLogHistory_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_ViewLogHistory_ext }) @@ -248,7 +253,7 @@ describe('warnUser', () => { it('calls sendModeratorCommand with Command_WarnUser', () => { warnUser('alice', 'bad behavior', 'cid'); - expect(BackendService.sendModeratorCommand).toHaveBeenCalledWith(Command_WarnUser_ext, expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith(Command_WarnUser_ext, expect.any(Object), expect.any(Object)); }); it('onSuccess calls ModeratorPersistence.warnUser', () => { diff --git a/webclient/src/websocket/commands/moderator/updateAdminNotes.ts b/webclient/src/websocket/commands/moderator/updateAdminNotes.ts index 6f50e71aa..448f58e23 100644 --- a/webclient/src/websocket/commands/moderator/updateAdminNotes.ts +++ b/webclient/src/websocket/commands/moderator/updateAdminNotes.ts @@ -1,12 +1,12 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_UpdateAdminNotes_ext, Command_UpdateAdminNotesSchema, } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; export function updateAdminNotes(userName: string, notes: string): void { - BackendService.sendModeratorCommand(Command_UpdateAdminNotes_ext, create(Command_UpdateAdminNotesSchema, { userName, notes }), { + webClient.protobuf.sendModeratorCommand(Command_UpdateAdminNotes_ext, create(Command_UpdateAdminNotesSchema, { userName, notes }), { onSuccess: () => { ModeratorPersistence.updateAdminNotes(userName, notes); }, diff --git a/webclient/src/websocket/commands/moderator/viewLogHistory.ts b/webclient/src/websocket/commands/moderator/viewLogHistory.ts index 82d101f99..264a535b2 100644 --- a/webclient/src/websocket/commands/moderator/viewLogHistory.ts +++ b/webclient/src/websocket/commands/moderator/viewLogHistory.ts @@ -1,12 +1,12 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ViewLogHistory_ext, Command_ViewLogHistorySchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; import { Response_ViewLogHistory_ext } from 'generated/proto/response_viewlog_history_pb'; import { LogFilters } from 'types'; export function viewLogHistory(filters: LogFilters): void { - BackendService.sendModeratorCommand(Command_ViewLogHistory_ext, create(Command_ViewLogHistorySchema, filters), { + webClient.protobuf.sendModeratorCommand(Command_ViewLogHistory_ext, create(Command_ViewLogHistorySchema, filters), { responseExt: Response_ViewLogHistory_ext, onSuccess: (response) => { ModeratorPersistence.viewLogs(response.logMessage); diff --git a/webclient/src/websocket/commands/moderator/warnUser.ts b/webclient/src/websocket/commands/moderator/warnUser.ts index 20cd7b565..dfe2f3211 100644 --- a/webclient/src/websocket/commands/moderator/warnUser.ts +++ b/webclient/src/websocket/commands/moderator/warnUser.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_WarnUser_ext, Command_WarnUserSchema } from 'generated/proto/moderator_commands_pb'; import { ModeratorPersistence } from '../../persistence'; export function warnUser(userName: string, reason: string, clientid?: string, removeMessages?: number): void { const cmd = create(Command_WarnUserSchema, { userName, reason, clientid, removeMessages }); - BackendService.sendModeratorCommand(Command_WarnUser_ext, cmd, { + webClient.protobuf.sendModeratorCommand(Command_WarnUser_ext, cmd, { onSuccess: () => { ModeratorPersistence.warnUser(userName); }, diff --git a/webclient/src/websocket/commands/room/createGame.ts b/webclient/src/websocket/commands/room/createGame.ts index c49691657..be1b5b401 100644 --- a/webclient/src/websocket/commands/room/createGame.ts +++ b/webclient/src/websocket/commands/room/createGame.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_CreateGame_ext, Command_CreateGameSchema } from 'generated/proto/room_commands_pb'; import { RoomPersistence } from '../../persistence'; import { GameConfig } from 'types'; export function createGame(roomId: number, gameConfig: GameConfig): void { - BackendService.sendRoomCommand(roomId, Command_CreateGame_ext, create(Command_CreateGameSchema, gameConfig), { + webClient.protobuf.sendRoomCommand(roomId, Command_CreateGame_ext, create(Command_CreateGameSchema, gameConfig), { onSuccess: () => { RoomPersistence.gameCreated(roomId); }, diff --git a/webclient/src/websocket/commands/room/joinGame.ts b/webclient/src/websocket/commands/room/joinGame.ts index 3c5db7d98..9975eccef 100644 --- a/webclient/src/websocket/commands/room/joinGame.ts +++ b/webclient/src/websocket/commands/room/joinGame.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_JoinGame_ext, Command_JoinGameSchema } from 'generated/proto/room_commands_pb'; import { RoomPersistence } from '../../persistence'; import { JoinGameParams } from 'types'; export function joinGame(roomId: number, joinGameParams: JoinGameParams): void { - BackendService.sendRoomCommand(roomId, Command_JoinGame_ext, create(Command_JoinGameSchema, joinGameParams), { + webClient.protobuf.sendRoomCommand(roomId, Command_JoinGame_ext, create(Command_JoinGameSchema, joinGameParams), { onSuccess: () => { RoomPersistence.joinedGame(roomId, joinGameParams.gameId); }, diff --git a/webclient/src/websocket/commands/room/leaveRoom.ts b/webclient/src/websocket/commands/room/leaveRoom.ts index 445f15ee3..67303f510 100644 --- a/webclient/src/websocket/commands/room/leaveRoom.ts +++ b/webclient/src/websocket/commands/room/leaveRoom.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_LeaveRoom_ext, Command_LeaveRoomSchema } from 'generated/proto/room_commands_pb'; import { RoomPersistence } from '../../persistence'; export function leaveRoom(roomId: number): void { - BackendService.sendRoomCommand(roomId, Command_LeaveRoom_ext, create(Command_LeaveRoomSchema), { + webClient.protobuf.sendRoomCommand(roomId, Command_LeaveRoom_ext, create(Command_LeaveRoomSchema), { onSuccess: () => { RoomPersistence.leaveRoom(roomId); }, diff --git a/webclient/src/websocket/commands/room/roomCommands.spec.ts b/webclient/src/websocket/commands/room/roomCommands.spec.ts index 691455c56..0916eb233 100644 --- a/webclient/src/websocket/commands/room/roomCommands.spec.ts +++ b/webclient/src/websocket/commands/room/roomCommands.spec.ts @@ -1,7 +1,6 @@ -vi.mock('../../services/BackendService', () => ({ - BackendService: { - sendRoomCommand: vi.fn(), - }, +vi.mock('../../WebClient', () => ({ + __esModule: true, + default: { protobuf: { sendRoomCommand: vi.fn() } }, })); vi.mock('../../persistence', () => ({ @@ -13,7 +12,7 @@ vi.mock('../../persistence', () => ({ })); import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { RoomPersistence } from '../../persistence'; import { Command_CreateGame_ext, Command_JoinGame_ext, Command_LeaveRoom_ext, Command_RoomSay_ext } from 'generated/proto/room_commands_pb'; import { createGame } from './createGame'; @@ -24,7 +23,7 @@ import { roomSay } from './roomSay'; import { Mock } from 'vitest'; const { invokeOnSuccess } = makeCallbackHelpers( - BackendService.sendRoomCommand as Mock, + webClient.protobuf.sendRoomCommand as Mock, // sendRoomCommand(roomId, ext, value, options) — options at index 3 3 ); @@ -38,7 +37,7 @@ describe('createGame', () => { it('calls sendRoomCommand with Command_CreateGame', () => { createGame(5, { maxPlayers: 4 } as any); - expect(BackendService.sendRoomCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendRoomCommand).toHaveBeenCalledWith( 5, Command_CreateGame_ext, expect.objectContaining({ maxPlayers: 4 }), expect.any(Object) ); }); @@ -57,7 +56,7 @@ describe('joinGame', () => { it('calls sendRoomCommand with Command_JoinGame', () => { joinGame(7, { gameId: 42, password: '' } as any); - expect(BackendService.sendRoomCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendRoomCommand).toHaveBeenCalledWith( 7, Command_JoinGame_ext, expect.objectContaining({ gameId: 42, password: '' }), expect.any(Object) ); }); @@ -76,7 +75,7 @@ describe('leaveRoom', () => { it('calls sendRoomCommand with Command_LeaveRoom', () => { leaveRoom(3); - expect(BackendService.sendRoomCommand).toHaveBeenCalledWith(3, Command_LeaveRoom_ext, expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendRoomCommand).toHaveBeenCalledWith(3, Command_LeaveRoom_ext, expect.any(Object), expect.any(Object)); }); it('onSuccess calls RoomPersistence.leaveRoom with roomId', () => { @@ -93,7 +92,7 @@ describe('roomSay', () => { it('calls sendRoomCommand with trimmed message', () => { roomSay(2, ' hello '); - expect(BackendService.sendRoomCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendRoomCommand).toHaveBeenCalledWith( 2, Command_RoomSay_ext, expect.objectContaining({ message: 'hello' }) @@ -102,11 +101,11 @@ describe('roomSay', () => { it('does not call sendRoomCommand when message is blank', () => { roomSay(2, ' '); - expect(BackendService.sendRoomCommand).not.toHaveBeenCalled(); + expect(webClient.protobuf.sendRoomCommand).not.toHaveBeenCalled(); }); it('does not call sendRoomCommand when message is empty string', () => { roomSay(2, ''); - expect(BackendService.sendRoomCommand).not.toHaveBeenCalled(); + expect(webClient.protobuf.sendRoomCommand).not.toHaveBeenCalled(); }); }); diff --git a/webclient/src/websocket/commands/room/roomSay.ts b/webclient/src/websocket/commands/room/roomSay.ts index 0ebf62b00..d847dc527 100644 --- a/webclient/src/websocket/commands/room/roomSay.ts +++ b/webclient/src/websocket/commands/room/roomSay.ts @@ -1,5 +1,5 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_RoomSay_ext, Command_RoomSaySchema } from 'generated/proto/room_commands_pb'; export function roomSay(roomId: number, message: string): void { @@ -9,5 +9,5 @@ export function roomSay(roomId: number, message: string): void { return; } - BackendService.sendRoomCommand(roomId, Command_RoomSay_ext, create(Command_RoomSaySchema, { message: trimmed })); + webClient.protobuf.sendRoomCommand(roomId, Command_RoomSay_ext, create(Command_RoomSaySchema, { message: trimmed })); } diff --git a/webclient/src/websocket/commands/session/accountEdit.ts b/webclient/src/websocket/commands/session/accountEdit.ts index f36f48bf9..ad77b4dfb 100644 --- a/webclient/src/websocket/commands/session/accountEdit.ts +++ b/webclient/src/websocket/commands/session/accountEdit.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_AccountEdit_ext, Command_AccountEditSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; export function accountEdit(passwordCheck: string, realName?: string, email?: string, country?: string): void { const cmd = create(Command_AccountEditSchema, { passwordCheck, realName, email, country }); - BackendService.sendSessionCommand(Command_AccountEdit_ext, cmd, { + webClient.protobuf.sendSessionCommand(Command_AccountEdit_ext, cmd, { onSuccess: () => { SessionPersistence.accountEditChanged(realName, email, country); }, diff --git a/webclient/src/websocket/commands/session/accountImage.ts b/webclient/src/websocket/commands/session/accountImage.ts index 8a5eebe18..da603055f 100644 --- a/webclient/src/websocket/commands/session/accountImage.ts +++ b/webclient/src/websocket/commands/session/accountImage.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_AccountImage_ext, Command_AccountImageSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; export function accountImage(image: Uint8Array): void { - BackendService.sendSessionCommand(Command_AccountImage_ext, create(Command_AccountImageSchema, { image }), { + webClient.protobuf.sendSessionCommand(Command_AccountImage_ext, create(Command_AccountImageSchema, { image }), { onSuccess: () => { SessionPersistence.accountImageChanged(image); }, diff --git a/webclient/src/websocket/commands/session/accountPassword.ts b/webclient/src/websocket/commands/session/accountPassword.ts index 7a06ca6d7..5cc561421 100644 --- a/webclient/src/websocket/commands/session/accountPassword.ts +++ b/webclient/src/websocket/commands/session/accountPassword.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_AccountPassword_ext, Command_AccountPasswordSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; export function accountPassword(oldPassword: string, newPassword: string, hashedNewPassword: string): void { const cmd = create(Command_AccountPasswordSchema, { oldPassword, newPassword, hashedNewPassword }); - BackendService.sendSessionCommand(Command_AccountPassword_ext, cmd, { + webClient.protobuf.sendSessionCommand(Command_AccountPassword_ext, cmd, { onSuccess: () => { SessionPersistence.accountPasswordChange(); }, diff --git a/webclient/src/websocket/commands/session/activate.ts b/webclient/src/websocket/commands/session/activate.ts index 25340d106..49614ab37 100644 --- a/webclient/src/websocket/commands/session/activate.ts +++ b/webclient/src/websocket/commands/session/activate.ts @@ -2,8 +2,8 @@ import { AccountActivationParams } from 'store'; import { StatusEnum, WebSocketConnectOptions } from 'types'; import { create } from '@bufbuild/protobuf'; +import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; import { Command_Activate_ext, Command_ActivateSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; import { Response_ResponseCode } from 'generated/proto/response_pb'; @@ -13,8 +13,8 @@ import { disconnect, login, updateStatus } from './'; export function activate(options: WebSocketConnectOptions, password?: string, passwordSalt?: string): void { const { userName, token } = options as unknown as AccountActivationParams; - BackendService.sendSessionCommand(Command_Activate_ext, create(Command_ActivateSchema, { - ...webClient.clientConfig, + webClient.protobuf.sendSessionCommand(Command_Activate_ext, create(Command_ActivateSchema, { + ...CLIENT_CONFIG, userName, token, }), { diff --git a/webclient/src/websocket/commands/session/addToList.ts b/webclient/src/websocket/commands/session/addToList.ts index 7dbee06e2..74bdfa23c 100644 --- a/webclient/src/websocket/commands/session/addToList.ts +++ b/webclient/src/websocket/commands/session/addToList.ts @@ -1,5 +1,5 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_AddToList_ext, Command_AddToListSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; @@ -12,7 +12,7 @@ export function addToIgnoreList(userName: string): void { } export function addToList(list: string, userName: string): void { - BackendService.sendSessionCommand(Command_AddToList_ext, create(Command_AddToListSchema, { list, userName }), { + webClient.protobuf.sendSessionCommand(Command_AddToList_ext, create(Command_AddToListSchema, { list, userName }), { onSuccess: () => { SessionPersistence.addToList(list, userName); }, diff --git a/webclient/src/websocket/commands/session/deckDel.ts b/webclient/src/websocket/commands/session/deckDel.ts index 04816ad46..bf0bd2672 100644 --- a/webclient/src/websocket/commands/session/deckDel.ts +++ b/webclient/src/websocket/commands/session/deckDel.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DeckDelSchema, Command_DeckDel_ext } from 'generated/proto/command_deck_del_pb'; import { SessionPersistence } from '../../persistence'; export function deckDel(deckId: number): void { - BackendService.sendSessionCommand(Command_DeckDel_ext, create(Command_DeckDelSchema, { deckId }), { + webClient.protobuf.sendSessionCommand(Command_DeckDel_ext, create(Command_DeckDelSchema, { deckId }), { onSuccess: () => { SessionPersistence.deleteServerDeck(deckId); }, diff --git a/webclient/src/websocket/commands/session/deckDelDir.ts b/webclient/src/websocket/commands/session/deckDelDir.ts index 1d7782e2d..7c442b575 100644 --- a/webclient/src/websocket/commands/session/deckDelDir.ts +++ b/webclient/src/websocket/commands/session/deckDelDir.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DeckDelDirSchema, Command_DeckDelDir_ext } from 'generated/proto/command_deck_del_dir_pb'; import { SessionPersistence } from '../../persistence'; export function deckDelDir(path: string): void { - BackendService.sendSessionCommand(Command_DeckDelDir_ext, create(Command_DeckDelDirSchema, { path }), { + webClient.protobuf.sendSessionCommand(Command_DeckDelDir_ext, create(Command_DeckDelDirSchema, { path }), { onSuccess: () => { SessionPersistence.deleteServerDeckDir(path); }, diff --git a/webclient/src/websocket/commands/session/deckList.ts b/webclient/src/websocket/commands/session/deckList.ts index f1260dcfc..71f611a26 100644 --- a/webclient/src/websocket/commands/session/deckList.ts +++ b/webclient/src/websocket/commands/session/deckList.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DeckListSchema, Command_DeckList_ext } from 'generated/proto/command_deck_list_pb'; import { SessionPersistence } from '../../persistence'; import { Response_DeckList_ext } from 'generated/proto/response_deck_list_pb'; export function deckList(): void { - BackendService.sendSessionCommand(Command_DeckList_ext, create(Command_DeckListSchema), { + webClient.protobuf.sendSessionCommand(Command_DeckList_ext, create(Command_DeckListSchema), { responseExt: Response_DeckList_ext, onSuccess: (response) => { if (response.root) { diff --git a/webclient/src/websocket/commands/session/deckNewDir.ts b/webclient/src/websocket/commands/session/deckNewDir.ts index dc6f75481..d208bf0f4 100644 --- a/webclient/src/websocket/commands/session/deckNewDir.ts +++ b/webclient/src/websocket/commands/session/deckNewDir.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DeckNewDirSchema, Command_DeckNewDir_ext } from 'generated/proto/command_deck_new_dir_pb'; import { SessionPersistence } from '../../persistence'; export function deckNewDir(path: string, dirName: string): void { - BackendService.sendSessionCommand(Command_DeckNewDir_ext, create(Command_DeckNewDirSchema, { path, dirName }), { + webClient.protobuf.sendSessionCommand(Command_DeckNewDir_ext, create(Command_DeckNewDirSchema, { path, dirName }), { onSuccess: () => { SessionPersistence.createServerDeckDir(path, dirName); }, diff --git a/webclient/src/websocket/commands/session/deckUpload.ts b/webclient/src/websocket/commands/session/deckUpload.ts index d3c3576b2..a13596329 100644 --- a/webclient/src/websocket/commands/session/deckUpload.ts +++ b/webclient/src/websocket/commands/session/deckUpload.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_DeckUploadSchema, Command_DeckUpload_ext } from 'generated/proto/command_deck_upload_pb'; import { SessionPersistence } from '../../persistence'; import { Response_DeckUpload_ext } from 'generated/proto/response_deck_upload_pb'; export function deckUpload(path: string, deckId: number, deckList: string): void { - BackendService.sendSessionCommand(Command_DeckUpload_ext, create(Command_DeckUploadSchema, { path, deckId, deckList }), { + webClient.protobuf.sendSessionCommand(Command_DeckUpload_ext, create(Command_DeckUploadSchema, { path, deckId, deckList }), { responseExt: Response_DeckUpload_ext, onSuccess: (response) => { if (response.newFile) { diff --git a/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts b/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts index 71d105f8d..98cfb25ac 100644 --- a/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts +++ b/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts @@ -2,8 +2,8 @@ import { ForgotPasswordChallengeParams } from 'store'; import { StatusEnum, WebSocketConnectOptions } from 'types'; import { create } from '@bufbuild/protobuf'; +import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; import { Command_ForgotPasswordChallenge_ext, Command_ForgotPasswordChallengeSchema, } from 'generated/proto/session_commands_pb'; @@ -13,8 +13,8 @@ import { disconnect, updateStatus } from './'; export function forgotPasswordChallenge(options: WebSocketConnectOptions): void { const { userName, email } = options as unknown as ForgotPasswordChallengeParams; - BackendService.sendSessionCommand(Command_ForgotPasswordChallenge_ext, create(Command_ForgotPasswordChallengeSchema, { - ...webClient.clientConfig, + webClient.protobuf.sendSessionCommand(Command_ForgotPasswordChallenge_ext, create(Command_ForgotPasswordChallengeSchema, { + ...CLIENT_CONFIG, userName, email, }), { diff --git a/webclient/src/websocket/commands/session/forgotPasswordRequest.ts b/webclient/src/websocket/commands/session/forgotPasswordRequest.ts index a14cd1ee5..6760b3908 100644 --- a/webclient/src/websocket/commands/session/forgotPasswordRequest.ts +++ b/webclient/src/websocket/commands/session/forgotPasswordRequest.ts @@ -2,8 +2,8 @@ import { ForgotPasswordParams } from 'store'; import { StatusEnum, WebSocketConnectOptions } from 'types'; import { create } from '@bufbuild/protobuf'; +import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; import { Command_ForgotPasswordRequest_ext, Command_ForgotPasswordRequestSchema, } from 'generated/proto/session_commands_pb'; @@ -15,8 +15,8 @@ import { disconnect, updateStatus } from './'; export function forgotPasswordRequest(options: WebSocketConnectOptions): void { const { userName } = options as unknown as ForgotPasswordParams; - BackendService.sendSessionCommand(Command_ForgotPasswordRequest_ext, create(Command_ForgotPasswordRequestSchema, { - ...webClient.clientConfig, + webClient.protobuf.sendSessionCommand(Command_ForgotPasswordRequest_ext, create(Command_ForgotPasswordRequestSchema, { + ...CLIENT_CONFIG, userName, }), { responseExt: Response_ForgotPasswordRequest_ext, diff --git a/webclient/src/websocket/commands/session/forgotPasswordReset.ts b/webclient/src/websocket/commands/session/forgotPasswordReset.ts index 543467b18..90625fd5c 100644 --- a/webclient/src/websocket/commands/session/forgotPasswordReset.ts +++ b/webclient/src/websocket/commands/session/forgotPasswordReset.ts @@ -3,8 +3,8 @@ import { StatusEnum, WebSocketConnectOptions } from 'types'; import { create } from '@bufbuild/protobuf'; import type { MessageInitShape } from '@bufbuild/protobuf'; +import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; import { Command_ForgotPasswordReset_ext, Command_ForgotPasswordResetSchema, } from 'generated/proto/session_commands_pb'; @@ -17,7 +17,7 @@ export function forgotPasswordReset(options: WebSocketConnectOptions, newPasswor const { userName, token } = options as unknown as ForgotPasswordResetParams; const params: MessageInitShape = { - ...webClient.clientConfig, + ...CLIENT_CONFIG, userName, token, ...(passwordSalt @@ -25,7 +25,7 @@ export function forgotPasswordReset(options: WebSocketConnectOptions, newPasswor : { newPassword }), }; - BackendService.sendSessionCommand(Command_ForgotPasswordReset_ext, create(Command_ForgotPasswordResetSchema, params), { + webClient.protobuf.sendSessionCommand(Command_ForgotPasswordReset_ext, create(Command_ForgotPasswordResetSchema, params), { onSuccess: () => { updateStatus(StatusEnum.DISCONNECTED, null); SessionPersistence.resetPasswordSuccess(); diff --git a/webclient/src/websocket/commands/session/getGamesOfUser.ts b/webclient/src/websocket/commands/session/getGamesOfUser.ts index 4f2cb2109..184a31494 100644 --- a/webclient/src/websocket/commands/session/getGamesOfUser.ts +++ b/webclient/src/websocket/commands/session/getGamesOfUser.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_GetGamesOfUser_ext, Command_GetGamesOfUserSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; import { Response_GetGamesOfUser_ext } from 'generated/proto/response_get_games_of_user_pb'; export function getGamesOfUser(userName: string): void { - BackendService.sendSessionCommand(Command_GetGamesOfUser_ext, create(Command_GetGamesOfUserSchema, { userName }), { + webClient.protobuf.sendSessionCommand(Command_GetGamesOfUser_ext, create(Command_GetGamesOfUserSchema, { userName }), { responseExt: Response_GetGamesOfUser_ext, onSuccess: (response) => { SessionPersistence.getGamesOfUser(userName, response); diff --git a/webclient/src/websocket/commands/session/getUserInfo.ts b/webclient/src/websocket/commands/session/getUserInfo.ts index 2a93e81d1..cf1bf0f4c 100644 --- a/webclient/src/websocket/commands/session/getUserInfo.ts +++ b/webclient/src/websocket/commands/session/getUserInfo.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_GetUserInfo_ext, Command_GetUserInfoSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; import { Response_GetUserInfo_ext } from 'generated/proto/response_get_user_info_pb'; export function getUserInfo(userName: string): void { - BackendService.sendSessionCommand(Command_GetUserInfo_ext, create(Command_GetUserInfoSchema, { userName }), { + webClient.protobuf.sendSessionCommand(Command_GetUserInfo_ext, create(Command_GetUserInfoSchema, { userName }), { responseExt: Response_GetUserInfo_ext, onSuccess: (response) => { SessionPersistence.getUserInfo(response.userInfo); diff --git a/webclient/src/websocket/commands/session/joinRoom.ts b/webclient/src/websocket/commands/session/joinRoom.ts index 371f98c16..267a7e07b 100644 --- a/webclient/src/websocket/commands/session/joinRoom.ts +++ b/webclient/src/websocket/commands/session/joinRoom.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_JoinRoom_ext, Command_JoinRoomSchema } from 'generated/proto/session_commands_pb'; import { RoomPersistence } from '../../persistence'; import { Response_JoinRoom_ext } from 'generated/proto/response_join_room_pb'; export function joinRoom(roomId: number): void { - BackendService.sendSessionCommand(Command_JoinRoom_ext, create(Command_JoinRoomSchema, { roomId }), { + webClient.protobuf.sendSessionCommand(Command_JoinRoom_ext, create(Command_JoinRoomSchema, { roomId }), { responseExt: Response_JoinRoom_ext, onSuccess: (response) => { if (response.roomInfo) { diff --git a/webclient/src/websocket/commands/session/listRooms.ts b/webclient/src/websocket/commands/session/listRooms.ts index 9a4efde14..efaeab483 100644 --- a/webclient/src/websocket/commands/session/listRooms.ts +++ b/webclient/src/websocket/commands/session/listRooms.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ListRooms_ext, Command_ListRoomsSchema } from 'generated/proto/session_commands_pb'; export function listRooms(): void { - BackendService.sendSessionCommand(Command_ListRooms_ext, create(Command_ListRoomsSchema)); + webClient.protobuf.sendSessionCommand(Command_ListRooms_ext, create(Command_ListRoomsSchema)); } diff --git a/webclient/src/websocket/commands/session/listUsers.ts b/webclient/src/websocket/commands/session/listUsers.ts index e10fe2ba5..50f831234 100644 --- a/webclient/src/websocket/commands/session/listUsers.ts +++ b/webclient/src/websocket/commands/session/listUsers.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ListUsers_ext, Command_ListUsersSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; import { Response_ListUsers_ext } from 'generated/proto/response_list_users_pb'; export function listUsers(): void { - BackendService.sendSessionCommand(Command_ListUsers_ext, create(Command_ListUsersSchema), { + webClient.protobuf.sendSessionCommand(Command_ListUsers_ext, create(Command_ListUsersSchema), { responseExt: Response_ListUsers_ext, onSuccess: (response) => { SessionPersistence.updateUsers(response.userList); diff --git a/webclient/src/websocket/commands/session/login.ts b/webclient/src/websocket/commands/session/login.ts index 68b0cd896..3d93ec0ec 100644 --- a/webclient/src/websocket/commands/session/login.ts +++ b/webclient/src/websocket/commands/session/login.ts @@ -1,8 +1,8 @@ import { StatusEnum, WebSocketConnectOptions } from 'types'; import { create } from '@bufbuild/protobuf'; import type { MessageInitShape } from '@bufbuild/protobuf'; +import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; import { Command_Login_ext, Command_LoginSchema } from 'generated/proto/session_commands_pb'; import { hashPassword } from '../../utils'; import { SessionPersistence } from '../../persistence'; @@ -20,7 +20,7 @@ export function login(options: WebSocketConnectOptions, password?: string, passw const { userName, hashedPassword } = options; const loginConfig: MessageInitShape = { - ...webClient.clientConfig, + ...CLIENT_CONFIG, clientid: 'webatrice', userName, ...(passwordSalt @@ -35,7 +35,7 @@ export function login(options: WebSocketConnectOptions, password?: string, passw disconnect(); }; - BackendService.sendSessionCommand(Command_Login_ext, create(Command_LoginSchema, loginConfig), { + webClient.protobuf.sendSessionCommand(Command_Login_ext, create(Command_LoginSchema, loginConfig), { responseExt: Response_Login_ext, onSuccess: (resp) => { const { buddyList, ignoreList, userInfo } = resp; diff --git a/webclient/src/websocket/commands/session/message.ts b/webclient/src/websocket/commands/session/message.ts index 19946779e..b310010be 100644 --- a/webclient/src/websocket/commands/session/message.ts +++ b/webclient/src/websocket/commands/session/message.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_Message_ext, Command_MessageSchema } from 'generated/proto/session_commands_pb'; export function message(userName: string, message: string): void { - BackendService.sendSessionCommand(Command_Message_ext, create(Command_MessageSchema, { userName, message })); + webClient.protobuf.sendSessionCommand(Command_Message_ext, create(Command_MessageSchema, { userName, message })); } diff --git a/webclient/src/websocket/commands/session/ping.ts b/webclient/src/websocket/commands/session/ping.ts index a8bbc7b94..bb015094a 100644 --- a/webclient/src/websocket/commands/session/ping.ts +++ b/webclient/src/websocket/commands/session/ping.ts @@ -1,9 +1,9 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_Ping_ext, Command_PingSchema } from 'generated/proto/session_commands_pb'; export function ping(pingReceived: () => void): void { - BackendService.sendSessionCommand(Command_Ping_ext, create(Command_PingSchema), { + webClient.protobuf.sendSessionCommand(Command_Ping_ext, create(Command_PingSchema), { onResponse: () => pingReceived(), }); } diff --git a/webclient/src/websocket/commands/session/register.ts b/webclient/src/websocket/commands/session/register.ts index cb2020102..8166f9f8e 100644 --- a/webclient/src/websocket/commands/session/register.ts +++ b/webclient/src/websocket/commands/session/register.ts @@ -3,8 +3,8 @@ import { StatusEnum, WebSocketConnectOptions } from 'types'; import { create, getExtension } from '@bufbuild/protobuf'; import type { MessageInitShape } from '@bufbuild/protobuf'; +import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; import { Command_Register_ext, Command_RegisterSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; import { hashPassword } from '../../utils'; @@ -17,7 +17,7 @@ export function register(options: WebSocketConnectOptions, password?: string, pa const { userName, email, country, realName } = options as ServerRegisterParams; const params: MessageInitShape = { - ...webClient.clientConfig, + ...CLIENT_CONFIG, userName, email, country, @@ -33,7 +33,7 @@ export function register(options: WebSocketConnectOptions, password?: string, pa disconnect(); }; - BackendService.sendSessionCommand(Command_Register_ext, create(Command_RegisterSchema, params), { + webClient.protobuf.sendSessionCommand(Command_Register_ext, create(Command_RegisterSchema, params), { onResponseCode: { [Response_ResponseCode.RespRegistrationAccepted]: () => { login(options, password, passwordSalt); diff --git a/webclient/src/websocket/commands/session/removeFromList.ts b/webclient/src/websocket/commands/session/removeFromList.ts index ff1254e27..033701815 100644 --- a/webclient/src/websocket/commands/session/removeFromList.ts +++ b/webclient/src/websocket/commands/session/removeFromList.ts @@ -1,5 +1,5 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_RemoveFromList_ext, Command_RemoveFromListSchema } from 'generated/proto/session_commands_pb'; import { SessionPersistence } from '../../persistence'; @@ -12,7 +12,7 @@ export function removeFromIgnoreList(userName: string): void { } export function removeFromList(list: string, userName: string): void { - BackendService.sendSessionCommand(Command_RemoveFromList_ext, create(Command_RemoveFromListSchema, { list, userName }), { + webClient.protobuf.sendSessionCommand(Command_RemoveFromList_ext, create(Command_RemoveFromListSchema, { list, userName }), { onSuccess: () => { SessionPersistence.removeFromList(list, userName); }, diff --git a/webclient/src/websocket/commands/session/replayDeleteMatch.ts b/webclient/src/websocket/commands/session/replayDeleteMatch.ts index 7d243e1b2..09f99c84a 100644 --- a/webclient/src/websocket/commands/session/replayDeleteMatch.ts +++ b/webclient/src/websocket/commands/session/replayDeleteMatch.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ReplayDeleteMatchSchema, Command_ReplayDeleteMatch_ext } from 'generated/proto/command_replay_delete_match_pb'; import { SessionPersistence } from '../../persistence'; export function replayDeleteMatch(gameId: number): void { - BackendService.sendSessionCommand(Command_ReplayDeleteMatch_ext, create(Command_ReplayDeleteMatchSchema, { gameId }), { + webClient.protobuf.sendSessionCommand(Command_ReplayDeleteMatch_ext, create(Command_ReplayDeleteMatchSchema, { gameId }), { onSuccess: () => { SessionPersistence.replayDeleteMatch(gameId); }, diff --git a/webclient/src/websocket/commands/session/replayGetCode.ts b/webclient/src/websocket/commands/session/replayGetCode.ts index 77f72c330..8c5b1121c 100644 --- a/webclient/src/websocket/commands/session/replayGetCode.ts +++ b/webclient/src/websocket/commands/session/replayGetCode.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ReplayGetCodeSchema, Command_ReplayGetCode_ext } from 'generated/proto/command_replay_get_code_pb'; import { Response_ReplayGetCode_ext } from 'generated/proto/response_replay_get_code_pb'; export function replayGetCode(gameId: number, onCodeReceived: (code: string) => void): void { - BackendService.sendSessionCommand(Command_ReplayGetCode_ext, create(Command_ReplayGetCodeSchema, { gameId }), { + webClient.protobuf.sendSessionCommand(Command_ReplayGetCode_ext, create(Command_ReplayGetCodeSchema, { gameId }), { responseExt: Response_ReplayGetCode_ext, onSuccess: (response) => { onCodeReceived(response.replayCode); diff --git a/webclient/src/websocket/commands/session/replayList.ts b/webclient/src/websocket/commands/session/replayList.ts index 1d6dc8aa5..7fd45a3b2 100644 --- a/webclient/src/websocket/commands/session/replayList.ts +++ b/webclient/src/websocket/commands/session/replayList.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ReplayListSchema, Command_ReplayList_ext } from 'generated/proto/command_replay_list_pb'; import { SessionPersistence } from '../../persistence'; import { Response_ReplayList_ext } from 'generated/proto/response_replay_list_pb'; export function replayList(): void { - BackendService.sendSessionCommand(Command_ReplayList_ext, create(Command_ReplayListSchema), { + webClient.protobuf.sendSessionCommand(Command_ReplayList_ext, create(Command_ReplayListSchema), { responseExt: Response_ReplayList_ext, onSuccess: (response) => { SessionPersistence.replayList(response.matchList); diff --git a/webclient/src/websocket/commands/session/replayModifyMatch.ts b/webclient/src/websocket/commands/session/replayModifyMatch.ts index fa2af33c5..b104a34ec 100644 --- a/webclient/src/websocket/commands/session/replayModifyMatch.ts +++ b/webclient/src/websocket/commands/session/replayModifyMatch.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ReplayModifyMatchSchema, Command_ReplayModifyMatch_ext } from 'generated/proto/command_replay_modify_match_pb'; import { SessionPersistence } from '../../persistence'; export function replayModifyMatch(gameId: number, doNotHide: boolean): void { - BackendService.sendSessionCommand(Command_ReplayModifyMatch_ext, create(Command_ReplayModifyMatchSchema, { gameId, doNotHide }), { + webClient.protobuf.sendSessionCommand(Command_ReplayModifyMatch_ext, create(Command_ReplayModifyMatchSchema, { gameId, doNotHide }), { onSuccess: () => { SessionPersistence.replayModifyMatch(gameId, doNotHide); }, diff --git a/webclient/src/websocket/commands/session/replaySubmitCode.ts b/webclient/src/websocket/commands/session/replaySubmitCode.ts index 1ae371e8d..0d55a79db 100644 --- a/webclient/src/websocket/commands/session/replaySubmitCode.ts +++ b/webclient/src/websocket/commands/session/replaySubmitCode.ts @@ -1,5 +1,5 @@ import { create } from '@bufbuild/protobuf'; -import { BackendService } from '../../services/BackendService'; +import webClient from '../../WebClient'; import { Command_ReplaySubmitCodeSchema, Command_ReplaySubmitCode_ext } from 'generated/proto/command_replay_submit_code_pb'; export function replaySubmitCode( @@ -7,7 +7,7 @@ export function replaySubmitCode( onSuccess?: () => void, onError?: (responseCode: number) => void, ): void { - BackendService.sendSessionCommand(Command_ReplaySubmitCode_ext, create(Command_ReplaySubmitCodeSchema, { replayCode }), { + webClient.protobuf.sendSessionCommand(Command_ReplaySubmitCode_ext, create(Command_ReplaySubmitCodeSchema, { replayCode }), { onSuccess, onError, }); diff --git a/webclient/src/websocket/commands/session/requestPasswordSalt.ts b/webclient/src/websocket/commands/session/requestPasswordSalt.ts index 5ba515719..0e9d40e87 100644 --- a/webclient/src/websocket/commands/session/requestPasswordSalt.ts +++ b/webclient/src/websocket/commands/session/requestPasswordSalt.ts @@ -2,8 +2,8 @@ import { RequestPasswordSaltParams } from 'store'; import { StatusEnum, WebSocketConnectOptions, WebSocketConnectReason } from 'types'; import { create } from '@bufbuild/protobuf'; +import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { BackendService } from '../../services/BackendService'; import { Command_RequestPasswordSalt_ext, Command_RequestPasswordSaltSchema, } from 'generated/proto/session_commands_pb'; @@ -36,8 +36,8 @@ export function requestPasswordSalt(options: WebSocketConnectOptions, password?: disconnect(); }; - BackendService.sendSessionCommand(Command_RequestPasswordSalt_ext, create(Command_RequestPasswordSaltSchema, { - ...webClient.clientConfig, + webClient.protobuf.sendSessionCommand(Command_RequestPasswordSalt_ext, create(Command_RequestPasswordSaltSchema, { + ...CLIENT_CONFIG, userName, }), { responseExt: Response_PasswordSalt_ext, diff --git a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts index d2ba2bd50..f82dc2b1d 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts @@ -1,12 +1,6 @@ // Tests for complex session commands that call webClient directly // or have multiple branching callbacks. -vi.mock('../../services/BackendService', () => ({ - BackendService: { - sendSessionCommand: vi.fn(), - }, -})); - vi.mock('../../persistence', async () => { const { makeSessionPersistenceMock } = await import('../../__mocks__/sessionCommandMocks'); return { @@ -33,7 +27,6 @@ vi.mock('./', async () => { import { Mock } from 'vitest'; import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; -import { BackendService } from '../../services/BackendService'; import { SessionPersistence } from '../../persistence'; import webClient from '../../WebClient'; import * as SessionIndexMocks from './'; @@ -66,7 +59,7 @@ import { forgotPasswordReset } from './forgotPasswordReset'; import { requestPasswordSalt } from './requestPasswordSalt'; const { invokeOnSuccess, invokeResponseCode, invokeOnError } = makeCallbackHelpers( - BackendService.sendSessionCommand as Mock, + webClient.protobuf.sendSessionCommand as Mock, 2 ); @@ -144,7 +137,7 @@ describe('login', () => { it('sends Command_Login with plain password when no salt', () => { login({ userName: 'alice' } as any, 'pw'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_Login_ext, expect.objectContaining({ password: 'pw' }), expect.objectContaining({ responseExt: Response_Login_ext }) @@ -153,7 +146,7 @@ describe('login', () => { it('sends Command_Login with hashedPassword when salt is given', () => { login({ userName: 'alice' } as any, 'pw', 'salt'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_Login_ext, expect.objectContaining({ hashedPassword: 'hashed_pw' }), expect.objectContaining({ responseExt: Response_Login_ext }) @@ -162,7 +155,7 @@ describe('login', () => { it('uses options.hashedPassword if provided', () => { login({ userName: 'alice', hashedPassword: 'pre_hashed' } as any, 'pw', 'salt'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_Login_ext, expect.objectContaining({ hashedPassword: 'pre_hashed' }), expect.objectContaining({ responseExt: Response_Login_ext }) @@ -270,7 +263,7 @@ describe('register', () => { it('sends Command_Register with plain password when no salt', () => { register({ userName: 'alice', email: 'a@b.com', country: 'US', realName: 'Al' } as any, 'pw'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_Register_ext, expect.objectContaining({ password: 'pw' }), expect.any(Object) @@ -279,7 +272,7 @@ describe('register', () => { it('uses hashedPassword when salt is provided', () => { register({ userName: 'alice' } as any, 'pw', 'salt'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_Register_ext, expect.objectContaining({ hashedPassword: 'hashed_pw' }), expect.any(Object) @@ -373,12 +366,12 @@ describe('activate', () => { it('sends Command_Activate with userName and token, not password', () => { activate({ userName: 'alice', token: 'tok' } as any, 'pw'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_Activate_ext, expect.objectContaining({ userName: 'alice', token: 'tok' }), expect.any(Object) ); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_Activate_ext, expect.not.objectContaining({ password: expect.anything() }), expect.any(Object) @@ -407,7 +400,7 @@ describe('forgotPasswordChallenge', () => { it('sends Command_ForgotPasswordChallenge', () => { forgotPasswordChallenge({ userName: 'alice', email: 'a@b.com' } as any); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ForgotPasswordChallenge_ext, expect.any(Object), expect.any(Object) ); }); @@ -434,7 +427,7 @@ describe('forgotPasswordRequest', () => { it('sends Command_ForgotPasswordRequest', () => { forgotPasswordRequest({ userName: 'alice' } as any); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ForgotPasswordRequest_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_ForgotPasswordRequest_ext }) @@ -472,7 +465,7 @@ describe('forgotPasswordReset', () => { it('sends Command_ForgotPasswordReset with plain newPassword when no salt', () => { forgotPasswordReset({ userName: 'alice', token: 'tok' } as any, 'newpw'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ForgotPasswordReset_ext, expect.objectContaining({ newPassword: 'newpw' }), expect.any(Object) @@ -481,7 +474,7 @@ describe('forgotPasswordReset', () => { it('sends hashed new password when salt provided', () => { forgotPasswordReset({ userName: 'alice', token: 'tok' } as any, 'newpw', 'salt'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ForgotPasswordReset_ext, expect.objectContaining({ hashedNewPassword: 'hashed_pw' }), expect.any(Object) @@ -510,7 +503,7 @@ describe('requestPasswordSalt', () => { it('sends Command_RequestPasswordSalt', () => { requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any, 'pw'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_RequestPasswordSalt_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_PasswordSalt_ext }) diff --git a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts index d6b1e3744..b135e8d15 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts @@ -1,11 +1,5 @@ // Shared mock setup for session command tests -vi.mock('../../services/BackendService', () => ({ - BackendService: { - sendSessionCommand: vi.fn(), - }, -})); - vi.mock('../../persistence', async () => { const { makeSessionPersistenceMock } = await import('../../__mocks__/sessionCommandMocks'); return { @@ -33,7 +27,6 @@ vi.mock('./', async () => { import { Mock } from 'vitest'; import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; -import { BackendService } from '../../services/BackendService'; import { SessionPersistence } from '../../persistence'; import { RoomPersistence } from '../../persistence'; import webClient from '../../WebClient'; @@ -95,7 +88,7 @@ import { replayGetCode } from './replayGetCode'; import { replaySubmitCode } from './replaySubmitCode'; const { invokeOnSuccess, invokeCallback } = makeCallbackHelpers( - BackendService.sendSessionCommand as Mock, + webClient.protobuf.sendSessionCommand as Mock, 2 ); @@ -113,7 +106,7 @@ describe('accountEdit', () => { it('sends Command_AccountEdit with correct params', () => { accountEdit('pw', 'Alice', 'a@b.com', 'US'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_AccountEdit_ext, expect.objectContaining({ passwordCheck: 'pw', realName: 'Alice', email: 'a@b.com', country: 'US' }), expect.any(Object) @@ -133,7 +126,7 @@ describe('accountImage', () => { it('sends Command_AccountImage', () => { const img = new Uint8Array([1, 2]); accountImage(img); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_AccountImage_ext, expect.objectContaining({ image: img }), expect.any(Object) ); }); @@ -151,7 +144,7 @@ describe('accountPassword', () => { it('sends Command_AccountPassword', () => { accountPassword('old', 'new', 'hashed'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_AccountPassword_ext, expect.objectContaining({ oldPassword: 'old', newPassword: 'new', hashedNewPassword: 'hashed' }), expect.any(Object) @@ -170,7 +163,7 @@ describe('deckDel', () => { it('sends Command_DeckDel', () => { deckDel(42); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_DeckDel_ext, expect.objectContaining({ deckId: 42 }), expect.any(Object) @@ -189,7 +182,7 @@ describe('deckDelDir', () => { it('sends Command_DeckDelDir', () => { deckDelDir('/path'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_DeckDelDir_ext, expect.objectContaining({ path: '/path' }), expect.any(Object) ); }); @@ -206,7 +199,7 @@ describe('deckList', () => { it('sends Command_DeckList', () => { deckList(); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_DeckList_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_DeckList_ext }) @@ -226,7 +219,7 @@ describe('deckNewDir', () => { it('sends Command_DeckNewDir', () => { deckNewDir('/path', 'dir'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_DeckNewDir_ext, expect.objectContaining({ path: '/path', dirName: 'dir' }), expect.any(Object) ); }); @@ -243,7 +236,7 @@ describe('deckUpload', () => { it('sends Command_DeckUpload', () => { deckUpload('/path', 1, 'content'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_DeckUpload_ext, expect.objectContaining({ path: '/path', deckId: 1, deckList: 'content' }), expect.objectContaining({ responseExt: Response_DeckUpload_ext }) @@ -272,7 +265,7 @@ describe('getGamesOfUser', () => { it('sends Command_GetGamesOfUser', () => { getGamesOfUser('alice'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_GetGamesOfUser_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_GetGamesOfUser_ext }) @@ -292,7 +285,7 @@ describe('getUserInfo', () => { it('sends Command_GetUserInfo', () => { getUserInfo('alice'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_GetUserInfo_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_GetUserInfo_ext }) @@ -312,7 +305,7 @@ describe('joinRoom', () => { it('sends Command_JoinRoom', () => { joinRoom(5); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_JoinRoom_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_JoinRoom_ext }) @@ -332,7 +325,7 @@ describe('listRooms (command)', () => { it('sends Command_ListRooms', () => { listRooms(); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(Command_ListRooms_ext, expect.any(Object)); + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith(Command_ListRooms_ext, expect.any(Object)); }); }); @@ -341,7 +334,7 @@ describe('listUsers', () => { it('sends Command_ListUsers', () => { listUsers(); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ListUsers_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_ListUsers_ext }) @@ -361,7 +354,7 @@ describe('message', () => { it('sends Command_Message', () => { message('bob', 'hi'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_Message_ext, expect.objectContaining({ userName: 'bob', message: 'hi' }) ); }); @@ -374,7 +367,7 @@ describe('ping', () => { it('sends Command_Ping', () => { const pingReceived = vi.fn(); ping(pingReceived); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(Command_Ping_ext, expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith(Command_Ping_ext, expect.any(Object), expect.any(Object)); }); it('calls pingReceived via onResponse', () => { @@ -390,7 +383,7 @@ describe('replayDeleteMatch', () => { it('sends Command_ReplayDeleteMatch', () => { replayDeleteMatch(7); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ReplayDeleteMatch_ext, expect.objectContaining({ gameId: 7 }), expect.any(Object) @@ -409,7 +402,7 @@ describe('replayList', () => { it('sends Command_ReplayList', () => { replayList(); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ReplayList_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_ReplayList_ext }) @@ -429,7 +422,7 @@ describe('replayModifyMatch', () => { it('sends Command_ReplayModifyMatch', () => { replayModifyMatch(7, true); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ReplayModifyMatch_ext, expect.objectContaining({ gameId: 7, doNotHide: true }), expect.any(Object) ); }); @@ -446,7 +439,7 @@ describe('addToList / addToBuddyList / addToIgnoreList', () => { it('addToBuddyList sends Command_AddToList with list=buddy', () => { addToBuddyList('alice'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_AddToList_ext, expect.objectContaining({ list: 'buddy' }), expect.any(Object) @@ -455,7 +448,7 @@ describe('addToList / addToBuddyList / addToIgnoreList', () => { it('addToIgnoreList sends Command_AddToList with list=ignore', () => { addToIgnoreList('bob'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_AddToList_ext, expect.objectContaining({ list: 'ignore' }), expect.any(Object) @@ -474,7 +467,7 @@ describe('removeFromList / removeFromBuddyList / removeFromIgnoreList', () => { it('removeFromBuddyList sends Command_RemoveFromList with list=buddy', () => { removeFromBuddyList('alice'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_RemoveFromList_ext, expect.objectContaining({ list: 'buddy' }), expect.any(Object) @@ -483,7 +476,7 @@ describe('removeFromList / removeFromBuddyList / removeFromIgnoreList', () => { it('removeFromIgnoreList sends Command_RemoveFromList with list=ignore', () => { removeFromIgnoreList('bob'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_RemoveFromList_ext, expect.objectContaining({ list: 'ignore' }), expect.any(Object) @@ -502,7 +495,7 @@ describe('replayGetCode', () => { it('sends Command_ReplayGetCode with gameId and responseExt', () => { replayGetCode(42, vi.fn()); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ReplayGetCode_ext, expect.any(Object), expect.objectContaining({ responseExt: Response_ReplayGetCode_ext }) @@ -522,7 +515,7 @@ describe('replaySubmitCode', () => { it('sends Command_ReplaySubmitCode with replayCode', () => { replaySubmitCode('42-abc123'); - expect(BackendService.sendSessionCommand).toHaveBeenCalledWith( + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( Command_ReplaySubmitCode_ext, expect.objectContaining({ replayCode: '42-abc123' }), expect.any(Object) ); }); diff --git a/webclient/src/websocket/config.ts b/webclient/src/websocket/config.ts new file mode 100644 index 000000000..9f3d12643 --- /dev/null +++ b/webclient/src/websocket/config.ts @@ -0,0 +1,27 @@ +export const CLIENT_CONFIG = { + clientid: 'webatrice', + clientver: 'webclient-1.0 (2019-10-31)', + clientfeatures: [ + 'client_id', + 'client_ver', + 'feature_set', + 'room_chat_history', + 'client_warnings', + /* unimplemented features */ + 'forgot_password', + 'idle_client', + 'mod_log_lookup', + 'user_ban_history', + // satisfy server reqs for POC + 'websocket', + '2.7.0_min_version', + '2.8.0_min_version' + ] +} as const; + +export const PROTOCOL_VERSION = 14; + +export const CLIENT_OPTIONS = { + autojoinrooms: true, + keepalive: 5000 +} as const; diff --git a/webclient/src/websocket/events/common/index.ts b/webclient/src/websocket/events/common/index.ts index f0be28944..c6832988d 100644 --- a/webclient/src/websocket/events/common/index.ts +++ b/webclient/src/websocket/events/common/index.ts @@ -1,3 +1,3 @@ -import { SessionExtensionRegistry } from '../../services/ProtobufService'; +import { SessionExtensionRegistry } from '../../services/protobuf-types'; export const CommonEvents: SessionExtensionRegistry = []; diff --git a/webclient/src/websocket/events/game/index.ts b/webclient/src/websocket/events/game/index.ts index 8c7963481..2e75dabc8 100644 --- a/webclient/src/websocket/events/game/index.ts +++ b/webclient/src/websocket/events/game/index.ts @@ -1,4 +1,4 @@ -import { GameExtensionRegistry, makeGameEntry } from '../../services/ProtobufService'; +import { GameExtensionRegistry, makeGameEntry } from '../../services/protobuf-types'; import { attachCard } from './attachCard'; import { changeZoneProperties } from './changeZoneProperties'; import { createArrow } from './createArrow'; diff --git a/webclient/src/websocket/events/room/index.ts b/webclient/src/websocket/events/room/index.ts index 799986dec..f1832faa9 100644 --- a/webclient/src/websocket/events/room/index.ts +++ b/webclient/src/websocket/events/room/index.ts @@ -1,4 +1,4 @@ -import { RoomExtensionRegistry, makeRoomEntry } from '../../services/ProtobufService'; +import { RoomExtensionRegistry, makeRoomEntry } from '../../services/protobuf-types'; import { joinRoom } from './joinRoom'; import { leaveRoom } from './leaveRoom'; diff --git a/webclient/src/websocket/events/session/index.ts b/webclient/src/websocket/events/session/index.ts index 84dc7ba7f..d2b8cc4e9 100644 --- a/webclient/src/websocket/events/session/index.ts +++ b/webclient/src/websocket/events/session/index.ts @@ -1,4 +1,4 @@ -import { SessionExtensionRegistry, makeSessionEntry } from '../../services/ProtobufService'; +import { SessionExtensionRegistry, makeSessionEntry } from '../../services/protobuf-types'; import { addToList } from './addToList'; import { connectionClosed } from './connectionClosed'; import { listRooms } from './listRooms'; diff --git a/webclient/src/websocket/events/session/listRooms.ts b/webclient/src/websocket/events/session/listRooms.ts index faba2968e..697bdaed3 100644 --- a/webclient/src/websocket/events/session/listRooms.ts +++ b/webclient/src/websocket/events/session/listRooms.ts @@ -1,4 +1,4 @@ -import webClient from '../../WebClient'; +import { CLIENT_OPTIONS } from '../../config'; import { joinRoom } from '../../commands/session'; import { RoomPersistence } from '../../persistence'; import { ListRoomsData } from './interfaces'; @@ -6,7 +6,7 @@ import { ListRoomsData } from './interfaces'; export function listRooms({ roomList }: ListRoomsData): void { RoomPersistence.updateRooms(roomList); - if (webClient.clientOptions.autojoinrooms) { + if (CLIENT_OPTIONS.autojoinrooms) { roomList.forEach(({ autoJoin, roomId }) => { if (autoJoin) { joinRoom(roomId); diff --git a/webclient/src/websocket/events/session/serverIdentification.ts b/webclient/src/websocket/events/session/serverIdentification.ts index 594d26e6d..d5998d3d3 100644 --- a/webclient/src/websocket/events/session/serverIdentification.ts +++ b/webclient/src/websocket/events/session/serverIdentification.ts @@ -1,6 +1,7 @@ import { StatusEnum, WebSocketConnectOptions, WebSocketConnectReason } from 'types'; import webClient from '../../WebClient'; +import { PROTOCOL_VERSION } from '../../config'; import { activate, disconnect, @@ -18,7 +19,7 @@ import { SessionPersistence } from '../../persistence'; export function serverIdentification(info: ServerIdentificationData): void { const { serverName, serverVersion, protocolVersion, serverOptions } = info; - if (protocolVersion !== webClient.protocolVersion) { + if (protocolVersion !== PROTOCOL_VERSION) { updateStatus(StatusEnum.DISCONNECTED, `Protocol version mismatch: ${protocolVersion}`); disconnect(); return; diff --git a/webclient/src/websocket/events/session/sessionEvents.spec.ts b/webclient/src/websocket/events/session/sessionEvents.spec.ts index 9f662cde7..7b35ad135 100644 --- a/webclient/src/websocket/events/session/sessionEvents.spec.ts +++ b/webclient/src/websocket/events/session/sessionEvents.spec.ts @@ -27,12 +27,15 @@ vi.mock('../../persistence', () => ({ vi.mock('../../WebClient', () => ({ __esModule: true, default: { - clientOptions: { autojoinrooms: false }, options: {}, - protocolVersion: 14, }, })); +vi.mock('../../config', () => ({ + CLIENT_OPTIONS: { autojoinrooms: false }, + PROTOCOL_VERSION: 14, +})); + vi.mock('../../commands/session', () => ({ joinRoom: vi.fn(), updateStatus: vi.fn(), @@ -70,6 +73,7 @@ import { Event_ServerIdentificationSchema } from 'generated/proto/event_server_i import { SessionPersistence, RoomPersistence } from '../../persistence'; import webClient from '../../WebClient'; +import * as Config from '../../config'; import * as SessionCmds from '../../commands/session'; import * as Utils from '../../utils'; import { gameJoined } from './gameJoined'; @@ -266,13 +270,13 @@ describe('listRooms', () => { }); it('does not call joinRoom when autojoinrooms is false', () => { - (webClient as any).clientOptions = { autojoinrooms: false }; + (Config as any).CLIENT_OPTIONS = { autojoinrooms: false }; listRooms(create(Event_ListRoomsSchema, { roomList: [{ autoJoin: true, roomId: 1 }] as any[] })); expect(SessionCmds.joinRoom).not.toHaveBeenCalled(); }); it('calls joinRoom for autoJoin rooms when autojoinrooms is true', () => { - (webClient as any).clientOptions = { autojoinrooms: true }; + (Config as any).CLIENT_OPTIONS = { autojoinrooms: true }; listRooms(create(Event_ListRoomsSchema, { roomList: [{ autoJoin: true, roomId: 2 }, { autoJoin: false, roomId: 3 }] as any[] })); expect(SessionCmds.joinRoom).toHaveBeenCalledTimes(1); expect(SessionCmds.joinRoom).toHaveBeenCalledWith(2); @@ -373,7 +377,7 @@ describe('connectionClosed', () => { describe('serverIdentification', () => { beforeEach(() => { - (webClient as any).protocolVersion = 14; + (Config as any).PROTOCOL_VERSION = 14; (webClient as any).options = {}; }); diff --git a/webclient/src/websocket/persistence/SessionPersistence.spec.ts b/webclient/src/websocket/persistence/SessionPersistence.spec.ts index 5bac239f4..9dbe4a10f 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.spec.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.spec.ts @@ -1,6 +1,7 @@ vi.mock('store', () => ({ ServerDispatch: { initialized: vi.fn(), + connectionAttempted: vi.fn(), clearStore: vi.fn(), loginSuccessful: vi.fn(), loginFailed: vi.fn(), @@ -387,4 +388,21 @@ describe('SessionPersistence', () => { SessionPersistence.replayDeleteMatch(7); expect(ServerDispatch.replayDeleteMatch).toHaveBeenCalledWith(7); }); + + it('connectionAttempted delegates to ServerDispatch', () => { + SessionPersistence.connectionAttempted(); + expect(ServerDispatch.connectionAttempted).toHaveBeenCalled(); + }); + + it('playerPropertiesChanged does nothing when payload has no playerProperties', () => { + SessionPersistence.playerPropertiesChanged(5, 1, {} as any); + expect(GameDispatch.playerPropertiesChanged).not.toHaveBeenCalled(); + }); + + it('getGamesOfUser handles rooms with missing gametypeList', () => { + const room = {} as any; + const game = { gameId: 5 }; + SessionPersistence.getGamesOfUser('alice', { roomList: [room], gameList: [game] } as any); + expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', [game], {}); + }); }); diff --git a/webclient/src/websocket/persistence/SessionPersistence.ts b/webclient/src/websocket/persistence/SessionPersistence.ts index d15215ec9..fcf670163 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.ts @@ -19,6 +19,10 @@ export class SessionPersistence { ServerDispatch.initialized(); } + static connectionAttempted() { + ServerDispatch.connectionAttempted(); + } + static clearStore() { ServerDispatch.clearStore(); } diff --git a/webclient/src/websocket/services/BackendService.spec.ts b/webclient/src/websocket/services/BackendService.spec.ts deleted file mode 100644 index 5f33fd06e..000000000 --- a/webclient/src/websocket/services/BackendService.spec.ts +++ /dev/null @@ -1,131 +0,0 @@ -vi.mock('@bufbuild/protobuf', () => ({ - create: vi.fn().mockReturnValue({}), - setExtension: vi.fn(), - getExtension: vi.fn(), -})); - -vi.mock('generated/proto/response_pb', () => ({ - Response_ResponseCode: { RespOk: 1 }, -})); - -vi.mock('../WebClient', () => { - const mockProtobuf = { - sendGameCommand: vi.fn(), - sendSessionCommand: vi.fn(), - sendRoomCommand: vi.fn(), - sendModeratorCommand: vi.fn(), - sendAdminCommand: vi.fn(), - }; - return { __esModule: true, default: { protobuf: mockProtobuf } }; -}); - -import { getExtension } from '@bufbuild/protobuf'; -import { BackendService } from './BackendService'; -import webClient from '../WebClient'; - -beforeEach(() => { - vi.clearAllMocks(); -}); - -function captureCallback(sendFn: ReturnType) { - const protobuf = webClient.protobuf as any; - const usesIndex2 = sendFn === protobuf.sendRoomCommand || sendFn === protobuf.sendGameCommand; - return sendFn.mock.calls[0][usesIndex2 ? 2 : 1]; -} - -describe('BackendService', () => { - describe('send commands', () => { - it.each([ - ['sendGameCommand', () => BackendService.sendGameCommand(7, {} as any, {} as any)], - ['sendSessionCommand', () => BackendService.sendSessionCommand({} as any, {} as any)], - ['sendRoomCommand', () => BackendService.sendRoomCommand(5, {} as any, {} as any)], - ['sendModeratorCommand', () => BackendService.sendModeratorCommand({} as any, {} as any)], - ['sendAdminCommand', () => BackendService.sendAdminCommand({} as any, {} as any)], - ])('%s delegates to protobuf', (methodName, invoke) => { - invoke(); - expect((webClient.protobuf as any)[methodName]).toHaveBeenCalled(); - }); - }); - - describe('handleResponse via non-session command callbacks', () => { - it('sendGameCommand callback invokes handleResponse', () => { - const onSuccess = vi.fn(); - BackendService.sendGameCommand(7, {} as any, {} as any, { onSuccess }); - const cb = (webClient.protobuf as any).sendGameCommand.mock.calls[0][2]; - cb({ responseCode: 1 }); - expect(onSuccess).toHaveBeenCalled(); - }); - - it('sendRoomCommand callback invokes handleResponse', () => { - const onSuccess = vi.fn(); - BackendService.sendRoomCommand(5, {} as any, {} as any, { onSuccess }); - captureCallback((webClient.protobuf as any).sendRoomCommand)({ responseCode: 1 }); - expect(onSuccess).toHaveBeenCalled(); - }); - - it('sendModeratorCommand callback invokes handleResponse', () => { - const onSuccess = vi.fn(); - BackendService.sendModeratorCommand({} as any, {} as any, { onSuccess }); - captureCallback((webClient.protobuf as any).sendModeratorCommand)({ responseCode: 1 }); - expect(onSuccess).toHaveBeenCalled(); - }); - - it('sendAdminCommand callback invokes handleResponse', () => { - const onSuccess = vi.fn(); - BackendService.sendAdminCommand({} as any, {} as any, { onSuccess }); - captureCallback((webClient.protobuf as any).sendAdminCommand)({ responseCode: 1 }); - expect(onSuccess).toHaveBeenCalled(); - }); - }); - - describe('handleResponse (via sendSessionCommand callback)', () => { - function invokeCallback(options: any, raw: any) { - BackendService.sendSessionCommand({} as any, {} as any, options); - const cb = (webClient.protobuf as any).sendSessionCommand.mock.calls[0][1]; - cb(raw); - } - - it('calls onResponse and returns early when provided', () => { - const onResponse = vi.fn(); - const onSuccess = vi.fn(); - invokeCallback({ onResponse, onSuccess }, { responseCode: 99 }); - expect(onResponse).toHaveBeenCalled(); - expect(onSuccess).not.toHaveBeenCalled(); - }); - - it('calls onSuccess when responseCode is RespOk and no responseExt', () => { - const onSuccess = vi.fn(); - const raw = { responseCode: 1 }; - invokeCallback({ onSuccess }, raw); - expect(onSuccess).toHaveBeenCalledWith(); - }); - - it('calls onSuccess with nested response when responseExt is set', () => { - vi.mocked(getExtension).mockReturnValue({ nested: true }); - const onSuccess = vi.fn(); - const fakeExt = {} as any; - const raw = { responseCode: 1 }; - invokeCallback({ onSuccess, responseExt: fakeExt }, raw); - expect(onSuccess).toHaveBeenCalledWith({ nested: true }, raw); - }); - - it('calls onResponseCode handler when code matches', () => { - const specificHandler = vi.fn(); - invokeCallback({ onResponseCode: { 5: specificHandler } }, { responseCode: 5 }); - expect(specificHandler).toHaveBeenCalled(); - }); - - it('calls onError when responseCode is not RespOk and no specific handler', () => { - const onError = vi.fn(); - invokeCallback({ onError }, { responseCode: 99 }); - expect(onError).toHaveBeenCalledWith(99, { responseCode: 99 }); - }); - - it('logs error to console when no callbacks for non-RespOk response', () => { - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - invokeCallback({}, { responseCode: 42 }); - expect(consoleSpy).toHaveBeenCalled(); - consoleSpy.mockRestore(); - }); - }); -}); diff --git a/webclient/src/websocket/services/BackendService.ts b/webclient/src/websocket/services/BackendService.ts deleted file mode 100644 index 61b856e83..000000000 --- a/webclient/src/websocket/services/BackendService.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { create, getExtension, setExtension } from '@bufbuild/protobuf'; -import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; - -import webClient from '../WebClient'; -import { Response_ResponseCode, type Response } from 'generated/proto/response_pb'; -import { SessionCommandSchema, type SessionCommand } from 'generated/proto/session_commands_pb'; -import { GameCommandSchema, type GameCommand } from 'generated/proto/game_commands_pb'; -import { RoomCommandSchema, type RoomCommand } from 'generated/proto/room_commands_pb'; -import { ModeratorCommandSchema, type ModeratorCommand } from 'generated/proto/moderator_commands_pb'; -import { AdminCommandSchema, type AdminCommand } from 'generated/proto/admin_commands_pb'; - -interface CommandOptionsBase { - onError?: (responseCode: number, raw: Response) => void; - onResponseCode?: { [code: number]: (raw: Response) => void }; - onResponse?: (raw: Response) => void; -} - -export interface CommandOptionsWithResponse extends CommandOptionsBase { - responseExt: GenExtension; - onSuccess?: (response: R, raw: Response) => void; -} - -export interface CommandOptionsWithoutResponse extends CommandOptionsBase { - responseExt?: undefined; - onSuccess?: () => void; -} - -export type CommandOptions = CommandOptionsWithResponse | CommandOptionsWithoutResponse; - -function hasResponseExt(options: CommandOptions): options is CommandOptionsWithResponse { - return options.responseExt !== undefined; -} - -export class BackendService { - static sendGameCommand(gameId: number, ext: GenExtension, value: V, options?: CommandOptions): void { - const cmd = create(GameCommandSchema); - setExtension(cmd, ext, value); - webClient.protobuf.sendGameCommand(gameId, cmd, (raw: Response) => { - if (options) { - BackendService.handleResponse(ext.typeName, raw, options); - } - }); - } - - static sendSessionCommand(ext: GenExtension, value: V, options?: CommandOptions): void { - const cmd = create(SessionCommandSchema); - setExtension(cmd, ext, value); - webClient.protobuf.sendSessionCommand(cmd, raw => { - if (options) { - BackendService.handleResponse(ext.typeName, raw, options); - } - }); - } - - static sendRoomCommand(roomId: number, ext: GenExtension, value: V, options?: CommandOptions): void { - const cmd = create(RoomCommandSchema); - setExtension(cmd, ext, value); - webClient.protobuf.sendRoomCommand(roomId, cmd, raw => { - if (options) { - BackendService.handleResponse(ext.typeName, raw, options); - } - }); - } - - static sendModeratorCommand(ext: GenExtension, value: V, options?: CommandOptions): void { - const cmd = create(ModeratorCommandSchema); - setExtension(cmd, ext, value); - webClient.protobuf.sendModeratorCommand(cmd, raw => { - if (options) { - BackendService.handleResponse(ext.typeName, raw, options); - } - }); - } - - static sendAdminCommand(ext: GenExtension, value: V, options?: CommandOptions): void { - const cmd = create(AdminCommandSchema); - setExtension(cmd, ext, value); - webClient.protobuf.sendAdminCommand(cmd, raw => { - if (options) { - BackendService.handleResponse(ext.typeName, raw, options); - } - }); - } - - private static handleResponse(typeName: string, raw: Response, options: CommandOptions): void { - if (options.onResponse) { - options.onResponse(raw); - return; - } - - const { responseCode } = raw; - - if (responseCode === Response_ResponseCode.RespOk) { - if (hasResponseExt(options)) { - options.onSuccess?.(getExtension(raw, options.responseExt), raw); - } else { - options.onSuccess?.(); - } - return; - } - - if (options.onResponseCode?.[responseCode]) { - options.onResponseCode[responseCode](raw); - return; - } - - if (options.onError) { - options.onError(responseCode, raw); - } else { - console.error(`${typeName} failed with response code: ${responseCode}`); - } - } -} - diff --git a/webclient/src/websocket/services/KeepAliveService.spec.ts b/webclient/src/websocket/services/KeepAliveService.spec.ts index ed53da05f..4d45d6305 100644 --- a/webclient/src/websocket/services/KeepAliveService.spec.ts +++ b/webclient/src/websocket/services/KeepAliveService.spec.ts @@ -1,23 +1,17 @@ -vi.mock('../WebClient', () => ({ - __esModule: true, - default: { - socket: { - checkReadyState: vi.fn(), - }, - }, -})); - import { KeepAliveService } from './KeepAliveService'; +import { WebSocketService } from './WebSocketService'; -import webClient from '../WebClient'; +vi.mock('./WebSocketService'); describe('KeepAliveService', () => { let service: KeepAliveService; + let mockSocket: { checkReadyState: ReturnType }; beforeEach(() => { vi.useFakeTimers(); - service = new KeepAliveService(webClient.socket); + mockSocket = { checkReadyState: vi.fn().mockReturnValue(true) }; + service = new KeepAliveService(mockSocket as unknown as WebSocketService); }); it('should create', () => { @@ -36,7 +30,7 @@ describe('KeepAliveService', () => { promise = new Promise(resolve => resolvePing = resolve); ping = (done) => promise.then(done); - checkReadyStateSpy = vi.spyOn(webClient.socket, 'checkReadyState'); + checkReadyStateSpy = vi.spyOn(mockSocket, 'checkReadyState'); checkReadyStateSpy.mockImplementation(() => true); service.startPingLoop(interval, ping); diff --git a/webclient/src/websocket/services/ProtobufService.spec.ts b/webclient/src/websocket/services/ProtobufService.spec.ts index deb01a4bd..4a4ec401d 100644 --- a/webclient/src/websocket/services/ProtobufService.spec.ts +++ b/webclient/src/websocket/services/ProtobufService.spec.ts @@ -4,6 +4,7 @@ vi.mock('@bufbuild/protobuf', () => ({ toBinary: vi.fn().mockReturnValue(new Uint8Array()), hasExtension: vi.fn().mockReturnValue(false), getExtension: vi.fn(), + setExtension: vi.fn(), })); vi.mock('generated/proto/commands_pb', () => ({ @@ -20,11 +21,6 @@ vi.mock('generated/proto/server_message_pb', () => ({ }, })); -vi.mock('../commands/session', () => ({ - SessionCommands: { ping: vi.fn() }, - ping: vi.fn(), -})); - vi.mock('../events', () => ({ GameEvents: [], RoomEvents: [], @@ -39,8 +35,7 @@ vi.mock('../WebClient', () => ({ import { fromBinary, hasExtension, getExtension } from '@bufbuild/protobuf'; import { ServerMessage_MessageType } from 'generated/proto/server_message_pb'; import { ProtobufService } from './ProtobufService'; -import { ping as sessionPing } from '../commands/session'; -import { GameEvents } from '../events'; +import { GameEvents, RoomEvents, SessionEvents } from '../events'; let mockSocket: any; @@ -48,7 +43,7 @@ beforeEach(() => { vi.clearAllMocks(); mockSocket = { - checkReadyState: vi.fn().mockReturnValue(true), + isOpen: vi.fn().mockReturnValue(true), send: vi.fn(), }; }); @@ -56,34 +51,34 @@ beforeEach(() => { describe('ProtobufService', () => { describe('resetCommands', () => { it('resets cmdId and pendingCommands', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); service.sendSessionCommand({} as any, vi.fn()); expect((service as any).cmdId).toBe(1); service.resetCommands(); expect((service as any).cmdId).toBe(0); - expect((service as any).pendingCommands).toEqual({}); + expect((service as any).pendingCommands).toEqual(new Map()); }); }); describe('sendCommand', () => { it('increments cmdId and stores callback', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); const cb = vi.fn(); service.sendCommand({} as any, cb); expect((service as any).cmdId).toBe(1); - expect((service as any).pendingCommands[1]).toBe(cb); + expect((service as any).pendingCommands.get(1)).toBe(cb); }); it('sends encoded data when socket is OPEN', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - mockSocket.checkReadyState.mockReturnValue(true); + const service = new ProtobufService(mockSocket); + mockSocket.isOpen.mockReturnValue(true); service.sendCommand({} as any, vi.fn()); expect(mockSocket.send).toHaveBeenCalled(); }); it('does not send when socket is not OPEN', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - mockSocket.checkReadyState.mockReturnValue(false); + const service = new ProtobufService(mockSocket); + mockSocket.isOpen.mockReturnValue(false); service.sendCommand({} as any, vi.fn()); expect(mockSocket.send).not.toHaveBeenCalled(); }); @@ -91,156 +86,146 @@ describe('ProtobufService', () => { describe('sendSessionCommand', () => { it('stores callback and increments cmdId', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - const cb = vi.fn(); - service.sendSessionCommand({} as any, cb); + const service = new ProtobufService(mockSocket); + service.sendSessionCommand({} as any, {}); expect((service as any).cmdId).toBe(1); - expect((service as any).pendingCommands[1]).toBeTypeOf('function'); + expect((service as any).pendingCommands.get(1)).toBeTypeOf('function'); }); - it('invokes callback with raw response when the pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + it('invokes onResponse with raw response when the pending command is triggered', () => { + const service = new ProtobufService(mockSocket); const cb = vi.fn(); - service.sendSessionCommand({} as any, cb); + service.sendSessionCommand({} as any, {}, { onResponse: cb }); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); storedCb({ responseData: true }); expect(cb).toHaveBeenCalledWith({ responseData: true }); }); it('does not throw when no callback is provided and pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendSessionCommand({} as any); + const service = new ProtobufService(mockSocket); + service.sendSessionCommand({} as any, {}); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); expect(() => storedCb({ responseData: true })).not.toThrow(); }); }); describe('sendRoomCommand', () => { it('stores callback and increments cmdId', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendRoomCommand(42, {} as any, vi.fn()); + const service = new ProtobufService(mockSocket); + service.sendRoomCommand(42, {} as any, {}); expect((service as any).cmdId).toBe(1); }); - it('invokes callback with raw response when the pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + it('invokes onResponse with raw response when the pending command is triggered', () => { + const service = new ProtobufService(mockSocket); const cb = vi.fn(); - service.sendRoomCommand(42, {} as any, cb); + service.sendRoomCommand(42, {} as any, {}, { onResponse: cb }); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); storedCb({ responseData: true }); expect(cb).toHaveBeenCalledWith({ responseData: true }); }); it('does not throw when no callback is provided and pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendRoomCommand(42, {} as any); + const service = new ProtobufService(mockSocket); + service.sendRoomCommand(42, {} as any, {}); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); expect(() => storedCb({ responseData: true })).not.toThrow(); }); }); describe('sendGameCommand', () => { it('stores callback and increments cmdId', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendGameCommand(7, {} as any, vi.fn()); + const service = new ProtobufService(mockSocket); + service.sendGameCommand(7, {} as any, {}); expect((service as any).cmdId).toBe(1); }); - it('invokes callback with raw response when the pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + it('invokes onResponse with raw response when the pending command is triggered', () => { + const service = new ProtobufService(mockSocket); const cb = vi.fn(); - service.sendGameCommand(7, {} as any, cb); + service.sendGameCommand(7, {} as any, {}, { onResponse: cb }); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); storedCb({ responseData: true }); expect(cb).toHaveBeenCalledWith({ responseData: true }); }); it('does not throw when no callback is provided and pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendGameCommand(7, {} as any); + const service = new ProtobufService(mockSocket); + service.sendGameCommand(7, {} as any, {}); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); expect(() => storedCb({ responseData: true })).not.toThrow(); }); }); describe('sendModeratorCommand', () => { it('stores callback and increments cmdId', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendModeratorCommand({} as any, vi.fn()); + const service = new ProtobufService(mockSocket); + service.sendModeratorCommand({} as any, {}); expect((service as any).cmdId).toBe(1); }); - it('invokes callback with raw response when the pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + it('invokes onResponse with raw response when the pending command is triggered', () => { + const service = new ProtobufService(mockSocket); const cb = vi.fn(); - service.sendModeratorCommand({} as any, cb); + service.sendModeratorCommand({} as any, {}, { onResponse: cb }); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); storedCb({ responseData: true }); expect(cb).toHaveBeenCalledWith({ responseData: true }); }); it('does not throw when no callback is provided and pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendModeratorCommand({} as any); + const service = new ProtobufService(mockSocket); + service.sendModeratorCommand({} as any, {}); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); expect(() => storedCb({ responseData: true })).not.toThrow(); }); }); describe('sendAdminCommand', () => { it('stores callback and increments cmdId', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendAdminCommand({} as any, vi.fn()); + const service = new ProtobufService(mockSocket); + service.sendAdminCommand({} as any, {}); expect((service as any).cmdId).toBe(1); }); - it('invokes callback with raw response when the pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + it('invokes onResponse with raw response when the pending command is triggered', () => { + const service = new ProtobufService(mockSocket); const cb = vi.fn(); - service.sendAdminCommand({} as any, cb); + service.sendAdminCommand({} as any, {}, { onResponse: cb }); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); storedCb({ responseData: true }); expect(cb).toHaveBeenCalledWith({ responseData: true }); }); it('does not throw when no callback is provided and pending command is triggered', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - service.sendAdminCommand({} as any); + const service = new ProtobufService(mockSocket); + service.sendAdminCommand({} as any, {}); - const storedCb = (service as any).pendingCommands[1]; + const storedCb = (service as any).pendingCommands.get(1); expect(() => storedCb({ responseData: true })).not.toThrow(); }); }); - describe('sendKeepAliveCommand', () => { - it('delegates to SessionCommands.ping', () => { - const service = new ProtobufService({ socket: mockSocket } as any); - const pingReceived = vi.fn(); - service.sendKeepAliveCommand(pingReceived); - expect(sessionPing).toHaveBeenCalledWith(pingReceived); - }); - }); - describe('handleMessageEvent', () => { it('routes RESPONSE message to processServerResponse', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); const cb = vi.fn(); (service as any).cmdId = 1; - (service as any).pendingCommands[1] = cb; + (service as any).pendingCommands.set(1, cb); vi.mocked(fromBinary).mockReturnValue({ messageType: ServerMessage_MessageType.RESPONSE, @@ -249,11 +234,11 @@ describe('ProtobufService', () => { service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); expect(cb).toHaveBeenCalledWith({ cmdId: BigInt(1) }); - expect((service as any).pendingCommands[1]).toBeUndefined(); + expect((service as any).pendingCommands.get(1)).toBeUndefined(); }); it('routes ROOM_EVENT message', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); const processRoomEvent = vi.spyOn(service as any, 'processRoomEvent'); vi.mocked(fromBinary).mockReturnValue({ @@ -266,7 +251,7 @@ describe('ProtobufService', () => { }); it('routes SESSION_EVENT message', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); const processSessionEvent = vi.spyOn(service as any, 'processSessionEvent'); vi.mocked(fromBinary).mockReturnValue({ @@ -279,7 +264,7 @@ describe('ProtobufService', () => { }); it('routes GAME_EVENT_CONTAINER message', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); const processGameEvent = vi.spyOn(service as any, 'processGameEvent'); vi.mocked(fromBinary).mockReturnValue({ @@ -292,7 +277,7 @@ describe('ProtobufService', () => { }); it('logs unknown message types (default case)', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); vi.mocked(fromBinary).mockReturnValue({ @@ -305,13 +290,13 @@ describe('ProtobufService', () => { }); it('does nothing when decoded message is null', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); vi.mocked(fromBinary).mockReturnValue(null as any); expect(() => service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent)).not.toThrow(); }); it('catches and logs decode errors', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); vi.mocked(fromBinary).mockImplementation(() => { throw new Error('decode error'); @@ -324,14 +309,14 @@ describe('ProtobufService', () => { describe('processGameEvent', () => { it('returns early when container has no eventList', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); vi.mocked(hasExtension).mockReturnValue(false); (service as any).processGameEvent(null, {}); expect(hasExtension).not.toHaveBeenCalled(); }); it('dispatches to a GameEvents handler when hasExtension returns true', () => { - const service = new ProtobufService({ socket: mockSocket } as any); + const service = new ProtobufService(mockSocket); const handler = vi.fn(); const mockExt = {}; const payload = { someData: 1 }; @@ -349,6 +334,85 @@ describe('ProtobufService', () => { expect(handler).toHaveBeenCalledWith(payload, expect.objectContaining({ gameId: 42, playerId: 5 })); (GameEvents as any).pop(); }); + + it('defaults gameId and playerId to -1 when undefined', () => { + const service = new ProtobufService(mockSocket); + const handler = vi.fn(); + const mockExt = {}; + const payload = { someData: 1 }; + + (GameEvents as any).push([mockExt, handler]); + vi.mocked(hasExtension).mockReturnValue(true); + vi.mocked(getExtension).mockReturnValue(payload); + + (service as any).processGameEvent({ + gameId: undefined, + eventList: [{ playerId: undefined }], + }); + + expect(handler).toHaveBeenCalledWith(payload, expect.objectContaining({ gameId: -1, playerId: -1 })); + (GameEvents as any).pop(); + }); + }); + + describe('processServerResponse', () => { + it('returns early when response is undefined', () => { + const service = new ProtobufService(mockSocket); + (service as any).pendingCommands.set(1, vi.fn()); + (service as any).processServerResponse(undefined); + expect((service as any).pendingCommands.size).toBe(1); + }); + }); + + describe('processRoomEvent', () => { + it('returns early when event is undefined', () => { + const service = new ProtobufService(mockSocket); + vi.mocked(hasExtension).mockReturnValue(false); + (service as any).processRoomEvent(undefined); + expect(hasExtension).not.toHaveBeenCalled(); + }); + + it('dispatches to a RoomEvents handler when hasExtension returns true', () => { + const service = new ProtobufService(mockSocket); + const handler = vi.fn(); + const mockExt = {}; + const payload = { roomData: 1 }; + + (RoomEvents as any).push([mockExt, handler]); + vi.mocked(hasExtension).mockReturnValue(true); + vi.mocked(getExtension).mockReturnValue(payload); + + const event = { roomId: 10 }; + (service as any).processRoomEvent(event); + + expect(handler).toHaveBeenCalledWith(payload, event); + (RoomEvents as any).pop(); + }); + }); + + describe('processSessionEvent', () => { + it('returns early when event is undefined', () => { + const service = new ProtobufService(mockSocket); + vi.mocked(hasExtension).mockReturnValue(false); + (service as any).processSessionEvent(undefined); + expect(hasExtension).not.toHaveBeenCalled(); + }); + + it('dispatches to a SessionEvents handler when hasExtension returns true', () => { + const service = new ProtobufService(mockSocket); + const handler = vi.fn(); + const mockExt = {}; + const payload = { sessionData: 1 }; + + (SessionEvents as any).push([mockExt, handler]); + vi.mocked(hasExtension).mockReturnValue(true); + vi.mocked(getExtension).mockReturnValue(payload); + + (service as any).processSessionEvent({ sessionId: 7 }); + + expect(handler).toHaveBeenCalledWith(payload); + (SessionEvents as any).pop(); + }); }); }); diff --git a/webclient/src/websocket/services/ProtobufService.ts b/webclient/src/websocket/services/ProtobufService.ts index de6e287c0..7333c58b2 100644 --- a/webclient/src/websocket/services/ProtobufService.ts +++ b/webclient/src/websocket/services/ProtobufService.ts @@ -1,140 +1,132 @@ -import { create, fromBinary, hasExtension, getExtension, toBinary } from '@bufbuild/protobuf'; +import { create, fromBinary, hasExtension, getExtension, setExtension, toBinary } from '@bufbuild/protobuf'; import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; import type { Response } from 'generated/proto/response_pb'; import type { RoomEvent } from 'generated/proto/room_event_pb'; import type { SessionEvent } from 'generated/proto/session_event_pb'; import type { GameEventContainer } from 'generated/proto/game_event_container_pb'; -import type { GameEvent } from 'generated/proto/game_event_pb'; import { GameEvents, RoomEvents, SessionEvents } from '../events'; -import { WebClient } from '../WebClient'; -import { SessionCommands } from 'websocket'; import { GameEventMeta } from 'types'; import { CommandContainerSchema, type CommandContainer } from 'generated/proto/commands_pb'; import { ServerMessageSchema, ServerMessage_MessageType, type ServerMessage } from 'generated/proto/server_message_pb'; -import type { SessionCommand } from 'generated/proto/session_commands_pb'; -import type { GameCommand } from 'generated/proto/game_commands_pb'; -import type { RoomCommand } from 'generated/proto/room_commands_pb'; -import type { ModeratorCommand } from 'generated/proto/moderator_commands_pb'; -import type { AdminCommand } from 'generated/proto/admin_commands_pb'; +import { SessionCommandSchema, type SessionCommand } from 'generated/proto/session_commands_pb'; +import { GameCommandSchema, type GameCommand } from 'generated/proto/game_commands_pb'; +import { RoomCommandSchema, type RoomCommand } from 'generated/proto/room_commands_pb'; +import { ModeratorCommandSchema, type ModeratorCommand } from 'generated/proto/moderator_commands_pb'; +import { AdminCommandSchema, type AdminCommand } from 'generated/proto/admin_commands_pb'; -// Per-family registry entry types. Each family hardcodes its parent message type -// and the handler's exact secondary-argument signature, eliminating the previous -// `...args: unknown[]` erasure. +import { type CommandOptions, handleResponse } from './command-options'; -type SessionRegistryEntry = [ - GenExtension, - (value: V) => void -]; -export type SessionExtensionRegistry = SessionRegistryEntry[]; - -type RoomRegistryEntry = [ - GenExtension, - (value: V, roomEvent: RoomEvent) => void -]; -export type RoomExtensionRegistry = RoomRegistryEntry[]; - -type GameRegistryEntry = [ - GenExtension, - (value: V, meta: GameEventMeta) => void -]; -export type GameExtensionRegistry = GameRegistryEntry[]; - -/** - * Type-safe factory functions. The compiler verifies at the call site that the - * handler's parameter types match the extension's value type and the family's - * secondary argument type. - */ -export function makeSessionEntry( - ext: GenExtension, - handler: (value: V) => void -): SessionRegistryEntry { - return [ext as GenExtension, handler as (value: unknown) => void]; -} - -export function makeRoomEntry( - ext: GenExtension, - handler: (value: V, roomEvent: RoomEvent) => void -): RoomRegistryEntry { - return [ext as GenExtension, handler as RoomRegistryEntry[1]]; -} - -export function makeGameEntry( - ext: GenExtension, - handler: (value: V, meta: GameEventMeta) => void -): GameRegistryEntry { - return [ext as GenExtension, handler as GameRegistryEntry[1]]; +export interface SocketTransport { + send(data: Uint8Array): void; + isOpen(): boolean; } export class ProtobufService { private cmdId = 0; - private pendingCommands: { [cmdId: string]: (response: Response) => void } = {}; + private pendingCommands = new Map void>(); - private webClient: WebClient; + private transport: SocketTransport; - constructor(webClient: WebClient) { - this.webClient = webClient; + constructor(transport: SocketTransport) { + this.transport = transport; } public resetCommands() { this.cmdId = 0; - this.pendingCommands = {}; + this.pendingCommands.clear(); } - public sendGameCommand(gameId: number, gameCmd: GameCommand, callback?: (raw: Response) => void) { - const cmd = create(CommandContainerSchema, { - gameId, - gameCommand: [gameCmd], + public sendGameCommand( + gameId: number, + ext: GenExtension, + value: V, + options?: CommandOptions + ): void { + const gameCmd = create(GameCommandSchema); + setExtension(gameCmd, ext, value); + const cmd = create(CommandContainerSchema, { gameId, gameCommand: [gameCmd] }); + this.sendCommand(cmd, raw => { + if (options) { + handleResponse(ext.typeName, raw, options); + } }); - this.sendCommand(cmd, (raw: Response) => callback?.(raw)); } - public sendRoomCommand(roomId: number, roomCmd: RoomCommand, callback?: (raw: Response) => void) { - const cmd = create(CommandContainerSchema, { - roomId, - roomCommand: [roomCmd], + public sendRoomCommand( + roomId: number, + ext: GenExtension, + value: V, + options?: CommandOptions + ): void { + const roomCmd = create(RoomCommandSchema); + setExtension(roomCmd, ext, value); + const cmd = create(CommandContainerSchema, { roomId, roomCommand: [roomCmd] }); + this.sendCommand(cmd, raw => { + if (options) { + handleResponse(ext.typeName, raw, options); + } }); - this.sendCommand(cmd, raw => callback?.(raw)); } - public sendSessionCommand(sesCmd: SessionCommand, callback?: (raw: Response) => void) { - const cmd = create(CommandContainerSchema, { - sessionCommand: [sesCmd], + public sendSessionCommand( + ext: GenExtension, + value: V, + options?: CommandOptions + ): void { + const sesCmd = create(SessionCommandSchema); + setExtension(sesCmd, ext, value); + const cmd = create(CommandContainerSchema, { sessionCommand: [sesCmd] }); + this.sendCommand(cmd, raw => { + if (options) { + handleResponse(ext.typeName, raw, options); + } }); - this.sendCommand(cmd, (raw) => callback?.(raw)); } - public sendModeratorCommand(modCmd: ModeratorCommand, callback?: (raw: Response) => void) { - const cmd = create(CommandContainerSchema, { - moderatorCommand: [modCmd], + public sendModeratorCommand( + ext: GenExtension, + value: V, + options?: CommandOptions + ): void { + const modCmd = create(ModeratorCommandSchema); + setExtension(modCmd, ext, value); + const cmd = create(CommandContainerSchema, { moderatorCommand: [modCmd] }); + this.sendCommand(cmd, raw => { + if (options) { + handleResponse(ext.typeName, raw, options); + } }); - this.sendCommand(cmd, (raw) => callback?.(raw)); } - public sendAdminCommand(adminCmd: AdminCommand, callback?: (raw: Response) => void) { - const cmd = create(CommandContainerSchema, { - adminCommand: [adminCmd], + public sendAdminCommand( + ext: GenExtension, + value: V, + options?: CommandOptions + ): void { + const adminCmd = create(AdminCommandSchema); + setExtension(adminCmd, ext, value); + const cmd = create(CommandContainerSchema, { adminCommand: [adminCmd] }); + this.sendCommand(cmd, raw => { + if (options) { + handleResponse(ext.typeName, raw, options); + } }); - this.sendCommand(cmd, (raw) => callback?.(raw)); } public sendCommand(cmd: CommandContainer, callback: (raw: Response) => void) { this.cmdId++; cmd.cmdId = BigInt(this.cmdId); - this.pendingCommands[this.cmdId] = callback; + this.pendingCommands.set(this.cmdId, callback); - if (this.webClient.socket.checkReadyState(WebSocket.OPEN)) { - this.webClient.socket.send(toBinary(CommandContainerSchema, cmd)); + if (this.transport.isOpen()) { + this.transport.send(toBinary(CommandContainerSchema, cmd)); } } - public sendKeepAliveCommand(pingReceived: () => void) { - SessionCommands.ping(pingReceived); - } - public handleMessageEvent({ data }: MessageEvent): void { try { const uint8msg = new Uint8Array(data); @@ -170,9 +162,9 @@ export class ProtobufService { } const cmdId = Number(response.cmdId); - if (this.pendingCommands[cmdId]) { - this.pendingCommands[cmdId](response); - delete this.pendingCommands[cmdId]; + if (this.pendingCommands.has(cmdId)) { + this.pendingCommands.get(cmdId)!(response); + this.pendingCommands.delete(cmdId); } } diff --git a/webclient/src/websocket/services/WebSocketService.spec.ts b/webclient/src/websocket/services/WebSocketService.spec.ts index 67fa1ed3d..f01c2b2c1 100644 --- a/webclient/src/websocket/services/WebSocketService.spec.ts +++ b/webclient/src/websocket/services/WebSocketService.spec.ts @@ -5,6 +5,10 @@ vi.mock('../WebClient', () => ({ WebClient: vi.fn(), })); +vi.mock('../config', () => ({ + CLIENT_OPTIONS: { keepalive: 1000 }, +})); + vi.mock('../commands/session', () => ({ updateStatus: vi.fn(), })); @@ -25,7 +29,7 @@ import { StatusEnum } from 'types'; let MockWS: Mock; let mockInstance: ReturnType['mockInstance']; let restoreWebSocket: ReturnType['restore']; -let mockWebClient: any; +let mockConfig: any; beforeEach(() => { vi.useFakeTimers(); @@ -36,10 +40,8 @@ beforeEach(() => { mockInstance = installed.mockInstance; restoreWebSocket = installed.restore; - mockWebClient = { - status: StatusEnum.CONNECTED, - clientOptions: { keepalive: 1000 }, - keepAlive: vi.fn(), + mockConfig = { + keepAliveFn: vi.fn(), }; }); @@ -50,25 +52,25 @@ afterEach(() => { describe('WebSocketService', () => { function createConnectedService() { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); service.connect({ host: 'h', port: 1 } as any, 'ws'); return service; } function createTestConnectedService() { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); service.testConnect({ host: 'h', port: 1 } as any, 'ws'); return service; } describe('constructor', () => { it('subscribes disconnected$ from KeepAliveService', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); expect(service).toBeDefined(); }); it('calls disconnect and updateStatus when keepAlive disconnected$ fires', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); service.connect({ host: 'localhost', port: 8080 } as any, 'ws'); // trigger keepAliveService.disconnected$ (service as any).keepAliveService.disconnected$.next(); @@ -79,7 +81,7 @@ describe('WebSocketService', () => { describe('connect', () => { it('creates a WebSocket with wss protocol by default', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); Object.defineProperty(window, 'location', { value: { hostname: 'example.com' }, writable: true, @@ -90,7 +92,7 @@ describe('WebSocketService', () => { }); it('switches to ws protocol when hostname is localhost', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); Object.defineProperty(window, 'location', { value: { hostname: 'localhost' }, writable: true, @@ -127,22 +129,22 @@ describe('WebSocketService', () => { }); it('starts the ping loop with the keepalive interval', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); const startSpy = vi.spyOn((service as any).keepAliveService, 'startPingLoop'); service.connect({ host: 'h', port: 1 } as any, 'ws'); mockInstance.onopen(); expect(startSpy).toHaveBeenCalledWith(1000, expect.any(Function)); }); - it('ping loop callback calls webClient.keepAlive', () => { - const service = new WebSocketService(mockWebClient); + it('ping loop callback calls keepAliveFn', () => { + const service = new WebSocketService(mockConfig); const startSpy = vi.spyOn((service as any).keepAliveService, 'startPingLoop'); service.connect({ host: 'h', port: 1 } as any, 'ws'); mockInstance.onopen(); const pingCb = startSpy.mock.calls[0][1] as (done: Function) => void; const done = vi.fn(); pingCb(done); - expect(mockWebClient.keepAlive).toHaveBeenCalledWith(done); + expect(mockConfig.keepAliveFn).toHaveBeenCalledWith(done); }); }); @@ -155,13 +157,13 @@ describe('WebSocketService', () => { it('does not overwrite status if already DISCONNECTED', () => { createConnectedService(); - mockWebClient.status = StatusEnum.DISCONNECTED; + mockInstance.onerror(); mockInstance.onclose(); expect(updateStatus).not.toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Connection Closed'); }); it('ends the ping loop on close', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); const endSpy = vi.spyOn((service as any).keepAliveService, 'endPingLoop'); service.connect({ host: 'h', port: 1 } as any, 'ws'); mockInstance.onclose(); @@ -226,7 +228,7 @@ describe('WebSocketService', () => { }); it('returns false when socket is null', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); // no connect called, socket is undefined expect(service.checkReadyState(WebSocket.OPEN)).toBe(false); }); @@ -234,7 +236,7 @@ describe('WebSocketService', () => { describe('testConnect', () => { it('creates a test WebSocket with correct URL', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); Object.defineProperty(window, 'location', { value: { hostname: 'example.com' }, writable: true, @@ -245,7 +247,7 @@ describe('WebSocketService', () => { }); it('uses ws protocol on localhost', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); Object.defineProperty(window, 'location', { value: { hostname: 'localhost' }, writable: true, @@ -256,7 +258,7 @@ describe('WebSocketService', () => { }); it('closes previous testSocket when connecting again', () => { - const service = new WebSocketService(mockWebClient); + const service = new WebSocketService(mockConfig); service.testConnect({ host: 'h', port: 1 } as any, 'ws'); const firstInstance = mockInstance; // install second mock instance and restore after test diff --git a/webclient/src/websocket/services/WebSocketService.ts b/webclient/src/websocket/services/WebSocketService.ts index 4de880687..d0b09eab5 100644 --- a/webclient/src/websocket/services/WebSocketService.ts +++ b/webclient/src/websocket/services/WebSocketService.ts @@ -3,23 +3,28 @@ import { Subject } from 'rxjs'; import { StatusEnum, WebSocketConnectOptions } from 'types'; import { KeepAliveService } from './KeepAliveService'; -import { WebClient } from '../WebClient'; +import { CLIENT_OPTIONS } from '../config'; import { SessionPersistence } from '../persistence'; import { updateStatus } from '../commands/session'; +export interface WebSocketServiceConfig { + keepAliveFn: (pingReceived: () => void) => void; +} + export class WebSocketService { private socket: WebSocket; private testSocket: WebSocket; - private webClient: WebClient; + private config: WebSocketServiceConfig; private keepAliveService: KeepAliveService; + private errorFired = false; public message$: Subject = new Subject(); private keepalive: number; - constructor(webClient: WebClient) { - this.webClient = webClient; + constructor(config: WebSocketServiceConfig) { + this.config = config; this.keepAliveService = new KeepAliveService(this); this.keepAliveService.disconnected$.subscribe(() => { @@ -34,7 +39,7 @@ export class WebSocketService { } const { host, port } = options; - this.keepalive = this.webClient.clientOptions.keepalive; + this.keepalive = CLIENT_OPTIONS.keepalive; this.socket = this.createWebSocket(`${protocol}://${host}:${port}`); } @@ -71,23 +76,25 @@ export class WebSocketService { socket.onopen = () => { clearTimeout(connectionTimer); + this.errorFired = false; updateStatus(StatusEnum.CONNECTED, 'Connected'); this.keepAliveService.startPingLoop(this.keepalive, (pingReceived: () => void) => { - this.webClient.keepAlive(pingReceived); + this.config.keepAliveFn(pingReceived); }); }; socket.onclose = () => { // dont overwrite failure messages - if (this.webClient.status !== StatusEnum.DISCONNECTED) { + if (!this.errorFired) { updateStatus(StatusEnum.DISCONNECTED, 'Connection Closed'); } - + this.errorFired = false; this.keepAliveService.endPingLoop(); }; socket.onerror = () => { + this.errorFired = true; updateStatus(StatusEnum.DISCONNECTED, 'Connection Failed'); SessionPersistence.connectionFailed(); }; @@ -108,7 +115,7 @@ export class WebSocketService { const socket = new WebSocket(url); socket.binaryType = 'arraybuffer'; - const connectionTimer = setTimeout(() => socket.close(), this.webClient.clientOptions.keepalive); + const connectionTimer = setTimeout(() => socket.close(), CLIENT_OPTIONS.keepalive); socket.onopen = () => { clearTimeout(connectionTimer); diff --git a/webclient/src/websocket/services/command-options.spec.ts b/webclient/src/websocket/services/command-options.spec.ts new file mode 100644 index 000000000..8c520781c --- /dev/null +++ b/webclient/src/websocket/services/command-options.spec.ts @@ -0,0 +1,59 @@ +vi.mock('@bufbuild/protobuf', () => ({ + getExtension: vi.fn(), +})); + +vi.mock('generated/proto/response_pb', () => ({ + Response_ResponseCode: { RespOk: 1 }, +})); + +import { getExtension } from '@bufbuild/protobuf'; +import { handleResponse } from './command-options'; + +beforeEach(() => { + vi.resetAllMocks(); +}); + +describe('handleResponse', () => { + it('calls onResponse and returns early when provided', () => { + const onResponse = vi.fn(); + const onSuccess = vi.fn(); + handleResponse('test', { responseCode: 99 } as any, { onResponse, onSuccess }); + expect(onResponse).toHaveBeenCalled(); + expect(onSuccess).not.toHaveBeenCalled(); + }); + + it('calls onSuccess when responseCode is RespOk and no responseExt', () => { + const onSuccess = vi.fn(); + const raw = { responseCode: 1 } as any; + handleResponse('test', raw, { onSuccess }); + expect(onSuccess).toHaveBeenCalledWith(); + }); + + it('calls onSuccess with nested response when responseExt is set', () => { + vi.mocked(getExtension).mockReturnValue({ nested: true } as any); + const onSuccess = vi.fn(); + const fakeExt = {} as any; + const raw = { responseCode: 1 } as any; + handleResponse('test', raw, { onSuccess, responseExt: fakeExt }); + expect(onSuccess).toHaveBeenCalledWith({ nested: true }, raw); + }); + + it('calls onResponseCode handler when code matches', () => { + const specificHandler = vi.fn(); + handleResponse('test', { responseCode: 5 } as any, { onResponseCode: { 5: specificHandler } }); + expect(specificHandler).toHaveBeenCalled(); + }); + + it('calls onError when responseCode is not RespOk and no specific handler', () => { + const onError = vi.fn(); + handleResponse('test', { responseCode: 99 } as any, { onError }); + expect(onError).toHaveBeenCalledWith(99, { responseCode: 99 }); + }); + + it('logs error to console when no callbacks for non-RespOk response', () => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + handleResponse('test.Type', { responseCode: 42 } as any, {}); + expect(consoleSpy).toHaveBeenCalled(); + consoleSpy.mockRestore(); + }); +}); diff --git a/webclient/src/websocket/services/command-options.ts b/webclient/src/websocket/services/command-options.ts new file mode 100644 index 000000000..d9d3b0be8 --- /dev/null +++ b/webclient/src/websocket/services/command-options.ts @@ -0,0 +1,54 @@ +import { getExtension } from '@bufbuild/protobuf'; +import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; +import { Response_ResponseCode, type Response } from 'generated/proto/response_pb'; + +interface CommandOptionsBase { + onError?: (responseCode: number, raw: Response) => void; + onResponseCode?: { [code: number]: (raw: Response) => void }; + onResponse?: (raw: Response) => void; +} + +export interface CommandOptionsWithResponse extends CommandOptionsBase { + responseExt: GenExtension; + onSuccess?: (response: R, raw: Response) => void; +} + +export interface CommandOptionsWithoutResponse extends CommandOptionsBase { + responseExt?: undefined; + onSuccess?: () => void; +} + +export type CommandOptions = CommandOptionsWithResponse | CommandOptionsWithoutResponse; + +export function hasResponseExt(options: CommandOptions): options is CommandOptionsWithResponse { + return options.responseExt !== undefined; +} + +export function handleResponse(typeName: string, raw: Response, options: CommandOptions): void { + if (options.onResponse) { + options.onResponse(raw); + return; + } + + const { responseCode } = raw; + + if (responseCode === Response_ResponseCode.RespOk) { + if (hasResponseExt(options)) { + options.onSuccess?.(getExtension(raw, options.responseExt), raw); + } else { + options.onSuccess?.(); + } + return; + } + + if (options.onResponseCode?.[responseCode]) { + options.onResponseCode[responseCode](raw); + return; + } + + if (options.onError) { + options.onError(responseCode, raw); + } else { + console.error(`${typeName} failed with response code: ${responseCode}`); + } +} diff --git a/webclient/src/websocket/services/protobuf-types.ts b/webclient/src/websocket/services/protobuf-types.ts new file mode 100644 index 000000000..395442de7 --- /dev/null +++ b/webclient/src/websocket/services/protobuf-types.ts @@ -0,0 +1,44 @@ +import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; +import type { RoomEvent } from 'generated/proto/room_event_pb'; +import type { SessionEvent } from 'generated/proto/session_event_pb'; +import type { GameEvent } from 'generated/proto/game_event_pb'; +import type { GameEventMeta } from 'types'; + +type SessionRegistryEntry = [ + GenExtension, + (value: V) => void +]; +export type SessionExtensionRegistry = SessionRegistryEntry[]; + +type RoomRegistryEntry = [ + GenExtension, + (value: V, roomEvent: RoomEvent) => void +]; +export type RoomExtensionRegistry = RoomRegistryEntry[]; + +type GameRegistryEntry = [ + GenExtension, + (value: V, meta: GameEventMeta) => void +]; +export type GameExtensionRegistry = GameRegistryEntry[]; + +export function makeSessionEntry( + ext: GenExtension, + handler: (value: V) => void +): SessionRegistryEntry { + return [ext as GenExtension, handler as (value: unknown) => void]; +} + +export function makeRoomEntry( + ext: GenExtension, + handler: (value: V, roomEvent: RoomEvent) => void +): RoomRegistryEntry { + return [ext as GenExtension, handler as RoomRegistryEntry[1]]; +} + +export function makeGameEntry( + ext: GenExtension, + handler: (value: V, meta: GameEventMeta) => void +): GameRegistryEntry { + return [ext as GenExtension, handler as GameRegistryEntry[1]]; +} From cea9ae62d8281d11948f000c4ed5e336d2556dfb Mon Sep 17 00:00:00 2001 From: seavor Date: Tue, 14 Apr 2026 15:14:43 -0500 Subject: [PATCH 13/38] remove type aliases --- webclient/src/api/AuthenticationService.tsx | 6 +- .../components/UserDisplay/UserDisplay.tsx | 5 +- webclient/src/store/common/SortUtil.ts | 7 +- webclient/src/store/common/normalizers.ts | 5 +- .../src/store/game/__mocks__/fixtures.ts | 14 ++- webclient/src/store/game/game.actions.ts | 84 ++++++++--------- webclient/src/store/game/game.dispatch.ts | 84 ++++++++--------- webclient/src/store/game/game.interfaces.ts | 13 ++- webclient/src/store/game/game.reducer.spec.ts | 5 +- webclient/src/store/game/game.reducer.ts | 37 ++++---- webclient/src/store/game/game.selectors.ts | 4 +- .../store/rooms/__mocks__/rooms-fixtures.ts | 4 +- webclient/src/store/rooms/rooms.actions.tsx | 5 +- webclient/src/store/rooms/rooms.dispatch.tsx | 5 +- .../store/server/__mocks__/server-fixtures.ts | 32 +++---- webclient/src/store/server/server.actions.ts | 47 ++++++---- webclient/src/store/server/server.dispatch.ts | 47 ++++++---- .../src/store/server/server.interfaces.ts | 35 ++++--- .../src/store/server/server.reducer.spec.ts | 3 +- webclient/src/store/server/server.reducer.ts | 26 +++-- webclient/src/types/deckList.ts | 9 -- webclient/src/types/game.ts | 94 +------------------ webclient/src/types/index.ts | 5 +- webclient/src/types/moderator.ts | 7 -- webclient/src/types/replay.ts | 5 - webclient/src/types/server.ts | 8 +- webclient/src/types/session.ts | 1 - webclient/src/types/user.ts | 5 - webclient/src/types/utilities.ts | 8 ++ .../src/websocket/events/game/attachCard.ts | 5 +- .../events/game/changeZoneProperties.ts | 5 +- .../src/websocket/events/game/createArrow.ts | 5 +- .../websocket/events/game/createCounter.ts | 5 +- .../src/websocket/events/game/createToken.ts | 5 +- .../src/websocket/events/game/delCounter.ts | 5 +- .../src/websocket/events/game/deleteArrow.ts | 5 +- .../src/websocket/events/game/destroyCard.ts | 5 +- .../src/websocket/events/game/drawCards.ts | 5 +- .../src/websocket/events/game/dumpZone.ts | 5 +- .../src/websocket/events/game/flipCard.ts | 5 +- .../src/websocket/events/game/gameSay.ts | 5 +- .../websocket/events/game/gameStateChanged.ts | 5 +- .../src/websocket/events/game/joinGame.ts | 5 +- .../src/websocket/events/game/moveCard.ts | 5 +- .../events/game/playerPropertiesChanged.ts | 5 +- .../src/websocket/events/game/revealCards.ts | 5 +- .../src/websocket/events/game/reverseTurn.ts | 5 +- .../src/websocket/events/game/rollDie.ts | 5 +- .../websocket/events/game/setActivePhase.ts | 5 +- .../websocket/events/game/setActivePlayer.ts | 5 +- .../src/websocket/events/game/setCardAttr.ts | 5 +- .../websocket/events/game/setCardCounter.ts | 5 +- .../src/websocket/events/game/setCounter.ts | 5 +- .../src/websocket/events/game/shuffle.ts | 5 +- .../websocket/persistence/GamePersistence.ts | 84 ++++++++--------- .../persistence/ModeratorPersistence.ts | 13 ++- .../websocket/persistence/RoomPersistence.ts | 5 +- .../persistence/SessionPersistence.ts | 30 +++--- 58 files changed, 412 insertions(+), 455 deletions(-) delete mode 100644 webclient/src/types/deckList.ts delete mode 100644 webclient/src/types/moderator.ts delete mode 100644 webclient/src/types/replay.ts delete mode 100644 webclient/src/types/session.ts create mode 100644 webclient/src/types/utilities.ts diff --git a/webclient/src/api/AuthenticationService.tsx b/webclient/src/api/AuthenticationService.tsx index eca17118c..e88226dca 100644 --- a/webclient/src/api/AuthenticationService.tsx +++ b/webclient/src/api/AuthenticationService.tsx @@ -1,6 +1,6 @@ -import { StatusEnum, User, WebSocketConnectReason, WebSocketConnectOptions } from 'types'; +import { StatusEnum, WebSocketConnectReason, WebSocketConnectOptions } from 'types'; import { SessionCommands } from 'websocket'; -import { ServerInfo_User_UserLevelFlag } from 'generated/proto/serverinfo_user_pb'; +import { ServerInfo_User, ServerInfo_User_UserLevelFlag } from 'generated/proto/serverinfo_user_pb'; export class AuthenticationService { static login(options: WebSocketConnectOptions): void { @@ -39,7 +39,7 @@ export class AuthenticationService { return state === StatusEnum.LOGGED_IN; } - static isModerator(user: User): boolean { + static isModerator(user: ServerInfo_User): boolean { const moderatorLevel = ServerInfo_User_UserLevelFlag.IsModerator; // @TODO tell cockatrice not to do this so shittily return (user.userLevel & moderatorLevel) === moderatorLevel; diff --git a/webclient/src/components/UserDisplay/UserDisplay.tsx b/webclient/src/components/UserDisplay/UserDisplay.tsx index b2ea7ab41..74a3acfe0 100644 --- a/webclient/src/components/UserDisplay/UserDisplay.tsx +++ b/webclient/src/components/UserDisplay/UserDisplay.tsx @@ -8,7 +8,8 @@ import MenuItem from '@mui/material/MenuItem'; import { Images } from 'images/Images'; import { SessionService } from 'api'; import { ServerSelectors } from 'store'; -import { RouteEnum, User } from 'types'; +import { RouteEnum } from 'types'; +import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; import { useAppSelector } from 'store/store'; import './UserDisplay.css'; @@ -87,7 +88,7 @@ const UserDisplay = ({ user }: UserDisplayProps) => { }; interface UserDisplayProps { - user: User; + user: ServerInfo_User; } export default UserDisplay; diff --git a/webclient/src/store/common/SortUtil.ts b/webclient/src/store/common/SortUtil.ts index 7dada2c9c..8a15b931f 100644 --- a/webclient/src/store/common/SortUtil.ts +++ b/webclient/src/store/common/SortUtil.ts @@ -1,4 +1,5 @@ -import { SortBy, SortDirection, User } from 'types'; +import { SortBy, SortDirection } from 'types'; +import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; export default class SortUtil { static sortByField(arr: T[], sortBy: SortBy): void { @@ -51,7 +52,7 @@ export default class SortUtil { } } - static sortUsersByField(users: User[], sortBy: SortBy) { + static sortUsersByField(users: ServerInfo_User[], sortBy: SortBy) { if (users.length) { users.sort((a, b) => SortUtil.userComparator(a, b, sortBy)) } @@ -75,7 +76,7 @@ export default class SortUtil { arr.sort((a, b) => SortUtil.stringComparator(a, b, sortBy)); } - private static userComparator(a: User, b: User, sortBy: SortBy, sortByUserLevel = true) { + private static userComparator(a: ServerInfo_User, b: ServerInfo_User, sortBy: SortBy, sortByUserLevel = true) { if (sortByUserLevel) { const adminSortBy = { field: 'userLevel', diff --git a/webclient/src/store/common/normalizers.ts b/webclient/src/store/common/normalizers.ts index cd08a8fd5..a68949984 100644 --- a/webclient/src/store/common/normalizers.ts +++ b/webclient/src/store/common/normalizers.ts @@ -1,7 +1,8 @@ import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; import type { ServerInfo_GameType } from 'generated/proto/serverinfo_gametype_pb'; -import { Game, GametypeMap, LogItem, LogGroups, Message, Room } from 'types'; +import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb'; +import { Game, GametypeMap, LogGroups, Message, Room } from 'types'; /** Flatten a gametype list into a lookup map of { gameTypeId → description }. */ export function normalizeGametypeMap(gametypeList: ServerInfo_GameType[]): GametypeMap { @@ -40,7 +41,7 @@ export function normalizeGameObject(game: ServerInfo_Game, gametypeMap: Gametype } /** Group a flat LogItem[] into { room, game, chat } buckets for the server store. */ -export function normalizeLogs(logs: LogItem[]): LogGroups { +export function normalizeLogs(logs: ServerInfo_ChatMessage[]): LogGroups { return logs.reduce((obj, log) => { const type = log.targetType as keyof LogGroups; obj[type] = obj[type] || []; diff --git a/webclient/src/store/game/__mocks__/fixtures.ts b/webclient/src/store/game/__mocks__/fixtures.ts index b491de17d..87a9ea346 100644 --- a/webclient/src/store/game/__mocks__/fixtures.ts +++ b/webclient/src/store/game/__mocks__/fixtures.ts @@ -1,4 +1,8 @@ -import { ArrowInfo, CardInfo, CounterInfo, PlayerProperties, ProtoInit } from 'types'; +import { ProtoInit } from 'types'; +import type { ServerInfo_Card } from 'generated/proto/serverinfo_card_pb'; +import type { ServerInfo_Counter } from 'generated/proto/serverinfo_counter_pb'; +import type { ServerInfo_Arrow } from 'generated/proto/serverinfo_arrow_pb'; +import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb'; import { create } from '@bufbuild/protobuf'; import { ServerInfo_CardSchema } from 'generated/proto/serverinfo_card_pb'; import { ServerInfo_CounterSchema } from 'generated/proto/serverinfo_counter_pb'; @@ -7,7 +11,7 @@ import { ServerInfo_ArrowSchema } from 'generated/proto/serverinfo_arrow_pb'; import { ServerInfo_PlayerPropertiesSchema } from 'generated/proto/serverinfo_playerproperties_pb'; import { GameEntry, GamesState, PlayerEntry, ZoneEntry } from '../game.interfaces'; -export function makeCard(overrides: ProtoInit = {}): CardInfo { +export function makeCard(overrides: ProtoInit = {}): ServerInfo_Card { return create(ServerInfo_CardSchema, { id: 1, name: 'Test Card', @@ -30,7 +34,7 @@ export function makeCard(overrides: ProtoInit = {}): CardInfo { }); } -export function makeCounter(overrides: ProtoInit = {}): CounterInfo { +export function makeCounter(overrides: ProtoInit = {}): ServerInfo_Counter { return create(ServerInfo_CounterSchema, { id: 1, name: 'Life', @@ -41,7 +45,7 @@ export function makeCounter(overrides: ProtoInit = {}): CounterInfo }); } -export function makeArrow(overrides: ProtoInit = {}): ArrowInfo { +export function makeArrow(overrides: ProtoInit = {}): ServerInfo_Arrow { return create(ServerInfo_ArrowSchema, { id: 1, startPlayerId: 1, @@ -68,7 +72,7 @@ export function makeZoneEntry(overrides: Partial = {}): ZoneEntry { }; } -export function makePlayerProperties(overrides: ProtoInit = {}): PlayerProperties { +export function makePlayerProperties(overrides: ProtoInit = {}): ServerInfo_PlayerProperties { return create(ServerInfo_PlayerPropertiesSchema, { playerId: 1, spectator: false, diff --git a/webclient/src/store/game/game.actions.ts b/webclient/src/store/game/game.actions.ts index 977207205..d02b5d402 100644 --- a/webclient/src/store/game/game.actions.ts +++ b/webclient/src/store/game/game.actions.ts @@ -1,25 +1,23 @@ -import { - AttachCardData, - ChangeZonePropertiesData, - CreateArrowData, - CreateCounterData, - CreateTokenData, - DelCounterData, - DeleteArrowData, - DestroyCardData, - DrawCardsData, - DumpZoneData, - FlipCardData, - GameStateChangedData, - MoveCardData, - PlayerProperties, - RevealCardsData, - RollDieData, - SetCardAttrData, - SetCardCounterData, - SetCounterData, - ShuffleData, -} from 'types'; +import type { Event_AttachCard } from 'generated/proto/event_attach_card_pb'; +import type { Event_ChangeZoneProperties } from 'generated/proto/event_change_zone_properties_pb'; +import type { Event_CreateArrow } from 'generated/proto/event_create_arrow_pb'; +import type { Event_CreateCounter } from 'generated/proto/event_create_counter_pb'; +import type { Event_CreateToken } from 'generated/proto/event_create_token_pb'; +import type { Event_DelCounter } from 'generated/proto/event_del_counter_pb'; +import type { Event_DeleteArrow } from 'generated/proto/event_delete_arrow_pb'; +import type { Event_DestroyCard } from 'generated/proto/event_destroy_card_pb'; +import type { Event_DrawCards } from 'generated/proto/event_draw_cards_pb'; +import type { Event_DumpZone } from 'generated/proto/event_dump_zone_pb'; +import type { Event_FlipCard } from 'generated/proto/event_flip_card_pb'; +import type { Event_GameStateChanged } from 'generated/proto/event_game_state_changed_pb'; +import type { Event_MoveCard } from 'generated/proto/event_move_card_pb'; +import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb'; +import type { Event_RevealCards } from 'generated/proto/event_reveal_cards_pb'; +import type { Event_RollDie } from 'generated/proto/event_roll_die_pb'; +import type { Event_SetCardAttr } from 'generated/proto/event_set_card_attr_pb'; +import type { Event_SetCardCounter } from 'generated/proto/event_set_card_counter_pb'; +import type { Event_SetCounter } from 'generated/proto/event_set_counter_pb'; +import type { Event_Shuffle } from 'generated/proto/event_shuffle_pb'; import { GameEntry } from './game.interfaces'; import { Types } from './game.types'; @@ -50,13 +48,13 @@ export const Actions = { hostId, }), - gameStateChanged: (gameId: number, data: GameStateChangedData) => ({ + gameStateChanged: (gameId: number, data: Event_GameStateChanged) => ({ type: Types.GAME_STATE_CHANGED, gameId, data, }), - playerJoined: (gameId: number, playerProperties: PlayerProperties) => ({ + playerJoined: (gameId: number, playerProperties: ServerInfo_PlayerProperties) => ({ type: Types.PLAYER_JOINED, gameId, playerProperties, @@ -69,7 +67,7 @@ export const Actions = { reason, }), - playerPropertiesChanged: (gameId: number, playerId: number, properties: PlayerProperties) => ({ + playerPropertiesChanged: (gameId: number, playerId: number, properties: ServerInfo_PlayerProperties) => ({ type: Types.PLAYER_PROPERTIES_CHANGED, gameId, playerId, @@ -81,112 +79,112 @@ export const Actions = { gameId, }), - cardMoved: (gameId: number, playerId: number, data: MoveCardData) => ({ + cardMoved: (gameId: number, playerId: number, data: Event_MoveCard) => ({ type: Types.CARD_MOVED, gameId, playerId, data, }), - cardFlipped: (gameId: number, playerId: number, data: FlipCardData) => ({ + cardFlipped: (gameId: number, playerId: number, data: Event_FlipCard) => ({ type: Types.CARD_FLIPPED, gameId, playerId, data, }), - cardDestroyed: (gameId: number, playerId: number, data: DestroyCardData) => ({ + cardDestroyed: (gameId: number, playerId: number, data: Event_DestroyCard) => ({ type: Types.CARD_DESTROYED, gameId, playerId, data, }), - cardAttached: (gameId: number, playerId: number, data: AttachCardData) => ({ + cardAttached: (gameId: number, playerId: number, data: Event_AttachCard) => ({ type: Types.CARD_ATTACHED, gameId, playerId, data, }), - tokenCreated: (gameId: number, playerId: number, data: CreateTokenData) => ({ + tokenCreated: (gameId: number, playerId: number, data: Event_CreateToken) => ({ type: Types.TOKEN_CREATED, gameId, playerId, data, }), - cardAttrChanged: (gameId: number, playerId: number, data: SetCardAttrData) => ({ + cardAttrChanged: (gameId: number, playerId: number, data: Event_SetCardAttr) => ({ type: Types.CARD_ATTR_CHANGED, gameId, playerId, data, }), - cardCounterChanged: (gameId: number, playerId: number, data: SetCardCounterData) => ({ + cardCounterChanged: (gameId: number, playerId: number, data: Event_SetCardCounter) => ({ type: Types.CARD_COUNTER_CHANGED, gameId, playerId, data, }), - arrowCreated: (gameId: number, playerId: number, data: CreateArrowData) => ({ + arrowCreated: (gameId: number, playerId: number, data: Event_CreateArrow) => ({ type: Types.ARROW_CREATED, gameId, playerId, data, }), - arrowDeleted: (gameId: number, playerId: number, data: DeleteArrowData) => ({ + arrowDeleted: (gameId: number, playerId: number, data: Event_DeleteArrow) => ({ type: Types.ARROW_DELETED, gameId, playerId, data, }), - counterCreated: (gameId: number, playerId: number, data: CreateCounterData) => ({ + counterCreated: (gameId: number, playerId: number, data: Event_CreateCounter) => ({ type: Types.COUNTER_CREATED, gameId, playerId, data, }), - counterSet: (gameId: number, playerId: number, data: SetCounterData) => ({ + counterSet: (gameId: number, playerId: number, data: Event_SetCounter) => ({ type: Types.COUNTER_SET, gameId, playerId, data, }), - counterDeleted: (gameId: number, playerId: number, data: DelCounterData) => ({ + counterDeleted: (gameId: number, playerId: number, data: Event_DelCounter) => ({ type: Types.COUNTER_DELETED, gameId, playerId, data, }), - cardsDrawn: (gameId: number, playerId: number, data: DrawCardsData) => ({ + cardsDrawn: (gameId: number, playerId: number, data: Event_DrawCards) => ({ type: Types.CARDS_DRAWN, gameId, playerId, data, }), - cardsRevealed: (gameId: number, playerId: number, data: RevealCardsData) => ({ + cardsRevealed: (gameId: number, playerId: number, data: Event_RevealCards) => ({ type: Types.CARDS_REVEALED, gameId, playerId, data, }), - zoneShuffled: (gameId: number, playerId: number, data: ShuffleData) => ({ + zoneShuffled: (gameId: number, playerId: number, data: Event_Shuffle) => ({ type: Types.ZONE_SHUFFLED, gameId, playerId, data, }), - dieRolled: (gameId: number, playerId: number, data: RollDieData) => ({ + dieRolled: (gameId: number, playerId: number, data: Event_RollDie) => ({ type: Types.DIE_ROLLED, gameId, playerId, @@ -211,14 +209,14 @@ export const Actions = { reversed, }), - zoneDumped: (gameId: number, playerId: number, data: DumpZoneData) => ({ + zoneDumped: (gameId: number, playerId: number, data: Event_DumpZone) => ({ type: Types.ZONE_DUMPED, gameId, playerId, data, }), - zonePropertiesChanged: (gameId: number, playerId: number, data: ChangeZonePropertiesData) => ({ + zonePropertiesChanged: (gameId: number, playerId: number, data: Event_ChangeZoneProperties) => ({ type: Types.ZONE_PROPERTIES_CHANGED, gameId, playerId, diff --git a/webclient/src/store/game/game.dispatch.ts b/webclient/src/store/game/game.dispatch.ts index f56f6c5d7..ea0dbcfff 100644 --- a/webclient/src/store/game/game.dispatch.ts +++ b/webclient/src/store/game/game.dispatch.ts @@ -1,25 +1,23 @@ -import { - AttachCardData, - ChangeZonePropertiesData, - CreateArrowData, - CreateCounterData, - CreateTokenData, - DelCounterData, - DeleteArrowData, - DestroyCardData, - DrawCardsData, - DumpZoneData, - FlipCardData, - GameStateChangedData, - MoveCardData, - PlayerProperties, - RevealCardsData, - RollDieData, - SetCardAttrData, - SetCardCounterData, - SetCounterData, - ShuffleData, -} from 'types'; +import type { Event_AttachCard } from 'generated/proto/event_attach_card_pb'; +import type { Event_ChangeZoneProperties } from 'generated/proto/event_change_zone_properties_pb'; +import type { Event_CreateArrow } from 'generated/proto/event_create_arrow_pb'; +import type { Event_CreateCounter } from 'generated/proto/event_create_counter_pb'; +import type { Event_CreateToken } from 'generated/proto/event_create_token_pb'; +import type { Event_DelCounter } from 'generated/proto/event_del_counter_pb'; +import type { Event_DeleteArrow } from 'generated/proto/event_delete_arrow_pb'; +import type { Event_DestroyCard } from 'generated/proto/event_destroy_card_pb'; +import type { Event_DrawCards } from 'generated/proto/event_draw_cards_pb'; +import type { Event_DumpZone } from 'generated/proto/event_dump_zone_pb'; +import type { Event_FlipCard } from 'generated/proto/event_flip_card_pb'; +import type { Event_GameStateChanged } from 'generated/proto/event_game_state_changed_pb'; +import type { Event_MoveCard } from 'generated/proto/event_move_card_pb'; +import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb'; +import type { Event_RevealCards } from 'generated/proto/event_reveal_cards_pb'; +import type { Event_RollDie } from 'generated/proto/event_roll_die_pb'; +import type { Event_SetCardAttr } from 'generated/proto/event_set_card_attr_pb'; +import type { Event_SetCardCounter } from 'generated/proto/event_set_card_counter_pb'; +import type { Event_SetCounter } from 'generated/proto/event_set_counter_pb'; +import type { Event_Shuffle } from 'generated/proto/event_shuffle_pb'; import { store } from 'store/store'; import { Actions } from './game.actions'; import { GameEntry } from './game.interfaces'; @@ -45,11 +43,11 @@ export const Dispatch = { store.dispatch(Actions.gameHostChanged(gameId, hostId)); }, - gameStateChanged: (gameId: number, data: GameStateChangedData) => { + gameStateChanged: (gameId: number, data: Event_GameStateChanged) => { store.dispatch(Actions.gameStateChanged(gameId, data)); }, - playerJoined: (gameId: number, playerProperties: PlayerProperties) => { + playerJoined: (gameId: number, playerProperties: ServerInfo_PlayerProperties) => { store.dispatch(Actions.playerJoined(gameId, playerProperties)); }, @@ -57,7 +55,7 @@ export const Dispatch = { store.dispatch(Actions.playerLeft(gameId, playerId, reason)); }, - playerPropertiesChanged: (gameId: number, playerId: number, properties: PlayerProperties) => { + playerPropertiesChanged: (gameId: number, playerId: number, properties: ServerInfo_PlayerProperties) => { store.dispatch(Actions.playerPropertiesChanged(gameId, playerId, properties)); }, @@ -65,67 +63,67 @@ export const Dispatch = { store.dispatch(Actions.kicked(gameId)); }, - cardMoved: (gameId: number, playerId: number, data: MoveCardData) => { + cardMoved: (gameId: number, playerId: number, data: Event_MoveCard) => { store.dispatch(Actions.cardMoved(gameId, playerId, data)); }, - cardFlipped: (gameId: number, playerId: number, data: FlipCardData) => { + cardFlipped: (gameId: number, playerId: number, data: Event_FlipCard) => { store.dispatch(Actions.cardFlipped(gameId, playerId, data)); }, - cardDestroyed: (gameId: number, playerId: number, data: DestroyCardData) => { + cardDestroyed: (gameId: number, playerId: number, data: Event_DestroyCard) => { store.dispatch(Actions.cardDestroyed(gameId, playerId, data)); }, - cardAttached: (gameId: number, playerId: number, data: AttachCardData) => { + cardAttached: (gameId: number, playerId: number, data: Event_AttachCard) => { store.dispatch(Actions.cardAttached(gameId, playerId, data)); }, - tokenCreated: (gameId: number, playerId: number, data: CreateTokenData) => { + tokenCreated: (gameId: number, playerId: number, data: Event_CreateToken) => { store.dispatch(Actions.tokenCreated(gameId, playerId, data)); }, - cardAttrChanged: (gameId: number, playerId: number, data: SetCardAttrData) => { + cardAttrChanged: (gameId: number, playerId: number, data: Event_SetCardAttr) => { store.dispatch(Actions.cardAttrChanged(gameId, playerId, data)); }, - cardCounterChanged: (gameId: number, playerId: number, data: SetCardCounterData) => { + cardCounterChanged: (gameId: number, playerId: number, data: Event_SetCardCounter) => { store.dispatch(Actions.cardCounterChanged(gameId, playerId, data)); }, - arrowCreated: (gameId: number, playerId: number, data: CreateArrowData) => { + arrowCreated: (gameId: number, playerId: number, data: Event_CreateArrow) => { store.dispatch(Actions.arrowCreated(gameId, playerId, data)); }, - arrowDeleted: (gameId: number, playerId: number, data: DeleteArrowData) => { + arrowDeleted: (gameId: number, playerId: number, data: Event_DeleteArrow) => { store.dispatch(Actions.arrowDeleted(gameId, playerId, data)); }, - counterCreated: (gameId: number, playerId: number, data: CreateCounterData) => { + counterCreated: (gameId: number, playerId: number, data: Event_CreateCounter) => { store.dispatch(Actions.counterCreated(gameId, playerId, data)); }, - counterSet: (gameId: number, playerId: number, data: SetCounterData) => { + counterSet: (gameId: number, playerId: number, data: Event_SetCounter) => { store.dispatch(Actions.counterSet(gameId, playerId, data)); }, - counterDeleted: (gameId: number, playerId: number, data: DelCounterData) => { + counterDeleted: (gameId: number, playerId: number, data: Event_DelCounter) => { store.dispatch(Actions.counterDeleted(gameId, playerId, data)); }, - cardsDrawn: (gameId: number, playerId: number, data: DrawCardsData) => { + cardsDrawn: (gameId: number, playerId: number, data: Event_DrawCards) => { store.dispatch(Actions.cardsDrawn(gameId, playerId, data)); }, - cardsRevealed: (gameId: number, playerId: number, data: RevealCardsData) => { + cardsRevealed: (gameId: number, playerId: number, data: Event_RevealCards) => { store.dispatch(Actions.cardsRevealed(gameId, playerId, data)); }, - zoneShuffled: (gameId: number, playerId: number, data: ShuffleData) => { + zoneShuffled: (gameId: number, playerId: number, data: Event_Shuffle) => { store.dispatch(Actions.zoneShuffled(gameId, playerId, data)); }, - dieRolled: (gameId: number, playerId: number, data: RollDieData) => { + dieRolled: (gameId: number, playerId: number, data: Event_RollDie) => { store.dispatch(Actions.dieRolled(gameId, playerId, data)); }, @@ -141,11 +139,11 @@ export const Dispatch = { store.dispatch(Actions.turnReversed(gameId, reversed)); }, - zoneDumped: (gameId: number, playerId: number, data: DumpZoneData) => { + zoneDumped: (gameId: number, playerId: number, data: Event_DumpZone) => { store.dispatch(Actions.zoneDumped(gameId, playerId, data)); }, - zonePropertiesChanged: (gameId: number, playerId: number, data: ChangeZonePropertiesData) => { + zonePropertiesChanged: (gameId: number, playerId: number, data: Event_ChangeZoneProperties) => { store.dispatch(Actions.zonePropertiesChanged(gameId, playerId, data)); }, diff --git a/webclient/src/store/game/game.interfaces.ts b/webclient/src/store/game/game.interfaces.ts index c7ee6749b..62abb9774 100644 --- a/webclient/src/store/game/game.interfaces.ts +++ b/webclient/src/store/game/game.interfaces.ts @@ -1,4 +1,7 @@ -import { ArrowInfo, CardInfo, CounterInfo, PlayerProperties } from 'types'; +import type { ServerInfo_Card } from 'generated/proto/serverinfo_card_pb'; +import type { ServerInfo_Counter } from 'generated/proto/serverinfo_counter_pb'; +import type { ServerInfo_Arrow } from 'generated/proto/serverinfo_arrow_pb'; +import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb'; export interface GamesState { games: { [gameId: number]: GameEntry }; @@ -29,14 +32,14 @@ export interface GameEntry { /** Normalized from ServerInfo_Player — keyed collections for O(1) lookup. */ export interface PlayerEntry { - properties: PlayerProperties; + properties: ServerInfo_PlayerProperties; deckList: string; /** Zones keyed by zone name (e.g. "hand", "deck", "table"). */ zones: { [zoneName: string]: ZoneEntry }; /** Player-level counters (e.g. life) keyed by counter id. */ - counters: { [counterId: number]: CounterInfo }; + counters: { [counterId: number]: ServerInfo_Counter }; /** Arrows keyed by arrow id. */ - arrows: { [arrowId: number]: ArrowInfo }; + arrows: { [arrowId: number]: ServerInfo_Arrow }; } /** Normalized from ServerInfo_Zone — card list is an ordered array matching proto. */ @@ -48,7 +51,7 @@ export interface ZoneEntry { /** Authoritative card count (used for hidden zones where cardList may be empty). */ cardCount: number; /** Ordered card list; may be empty for hidden zones with no dump active. */ - cards: CardInfo[]; + cards: ServerInfo_Card[]; alwaysRevealTopCard: boolean; alwaysLookAtTopCard: boolean; } diff --git a/webclient/src/store/game/game.reducer.spec.ts b/webclient/src/store/game/game.reducer.spec.ts index 6435e7bd1..bda9cae90 100644 --- a/webclient/src/store/game/game.reducer.spec.ts +++ b/webclient/src/store/game/game.reducer.spec.ts @@ -1,5 +1,6 @@ import { create } from '@bufbuild/protobuf'; -import { CardAttribute, PlayerInfo } from 'types'; +import { CardAttribute } from 'generated/proto/card_attributes_pb'; +import type { ServerInfo_Player } from 'generated/proto/serverinfo_player_pb'; import { gamesReducer } from './game.reducer'; import { Types } from './game.types'; import { @@ -68,7 +69,7 @@ describe('2B: Game state & player management', () => { const card = makeCard({ id: 5 }); const counter = makeCounter({ id: 2 }); const arrow = makeArrow({ id: 3 }); - const playerList: PlayerInfo[] = [ + const playerList: ServerInfo_Player[] = [ create(ServerInfo_PlayerSchema, { properties: makePlayerProperties({ playerId: 7 }), deckList: 'some deck', diff --git a/webclient/src/store/game/game.reducer.ts b/webclient/src/store/game/game.reducer.ts index 1f164210c..451b75fd2 100644 --- a/webclient/src/store/game/game.reducer.ts +++ b/webclient/src/store/game/game.reducer.ts @@ -1,12 +1,9 @@ -import { - ArrowInfo, - CardAttribute, - CardCounterInfo, - CardInfo, - CounterInfo, - PlayerInfo, - PlayerProperties, -} from 'types'; +import { CardAttribute } from 'generated/proto/card_attributes_pb'; +import type { ServerInfo_CardCounter } from 'generated/proto/serverinfo_cardcounter_pb'; +import type { ServerInfo_Card } from 'generated/proto/serverinfo_card_pb'; +import type { ServerInfo_Counter } from 'generated/proto/serverinfo_counter_pb'; +import type { ServerInfo_Arrow } from 'generated/proto/serverinfo_arrow_pb'; +import type { ServerInfo_Player } from 'generated/proto/serverinfo_player_pb'; import { create } from '@bufbuild/protobuf'; import { ServerInfo_CardSchema } from 'generated/proto/serverinfo_card_pb'; import { ServerInfo_CardCounterSchema } from 'generated/proto/serverinfo_cardcounter_pb'; @@ -77,7 +74,7 @@ function removeGame(state: GamesState, gameId: number): GamesState { } /** Converts the proto PlayerInfo[] array into the keyed PlayerEntry map used in the store. */ -function normalizePlayers(playerList: PlayerInfo[]): { [playerId: number]: PlayerEntry } { +function normalizePlayers(playerList: ServerInfo_Player[]): { [playerId: number]: PlayerEntry } { const players: { [playerId: number]: PlayerEntry } = {}; for (const player of playerList) { const playerId = player.properties.playerId; @@ -95,12 +92,12 @@ function normalizePlayers(playerList: PlayerInfo[]): { [playerId: number]: Playe }; } - const counters: { [counterId: number]: CounterInfo } = {}; + const counters: { [counterId: number]: ServerInfo_Counter } = {}; for (const counter of player.counterList) { counters[counter.id] = counter; } - const arrows: { [arrowId: number]: ArrowInfo } = {}; + const arrows: { [arrowId: number]: ServerInfo_Arrow } = {}; for (const arrow of player.arrowList) { arrows[arrow.id] = arrow; } @@ -123,7 +120,7 @@ function buildEmptyCard( y: number, faceDown: boolean, providerId: string -): CardInfo { +): ServerInfo_Card { return create(ServerInfo_CardSchema, { id, name, @@ -209,7 +206,7 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio return state; } const newPlayer: PlayerEntry = { - properties: playerProperties as PlayerProperties, + properties: playerProperties, deckList: '', zones: {}, counters: {}, @@ -269,8 +266,8 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio } // Locate card in source zone (by id for visible zones, by position for hidden) - let removedCard: CardInfo | undefined; - let newSourceCards: CardInfo[]; + let removedCard: ServerInfo_Card | undefined; + let newSourceCards: ServerInfo_Card[]; if (cardId >= 0) { removedCard = sourceZoneEntry.cards.find(c => c.id === cardId); newSourceCards = sourceZoneEntry.cards.filter(c => c.id !== cardId); @@ -283,7 +280,7 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio } const effectiveNewId = newCardId >= 0 ? newCardId : (removedCard?.id ?? -1); - const movedCard: CardInfo = removedCard + const movedCard: ServerInfo_Card = removedCard ? { ...removedCard, id: effectiveNewId, @@ -426,7 +423,7 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio return state; } - const newCard: CardInfo = create(ServerInfo_CardSchema, { + const newCard: ServerInfo_Card = create(ServerInfo_CardSchema, { id: cardId, name: cardName, x, @@ -472,7 +469,7 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio return state; } - const attrPatch: Partial = {}; + const attrPatch: Partial = {}; switch (attribute as CardAttribute) { case CardAttribute.AttrTapped: attrPatch.tapped = attrValue === '1'; break; case CardAttribute.AttrAttacking: attrPatch.attacking = attrValue === '1'; break; @@ -510,7 +507,7 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio } const card = zone.cards[cardIdx]; - let newCounterList: CardCounterInfo[]; + let newCounterList: ServerInfo_CardCounter[]; if (counterValue <= 0) { newCounterList = card.counterList.filter(c => c.id !== counterId); } else { diff --git a/webclient/src/store/game/game.selectors.ts b/webclient/src/store/game/game.selectors.ts index 1e9b55e70..acd653039 100644 --- a/webclient/src/store/game/game.selectors.ts +++ b/webclient/src/store/game/game.selectors.ts @@ -1,12 +1,12 @@ import { createSelector } from '@reduxjs/toolkit'; -import { CardInfo } from 'types'; +import type { ServerInfo_Card } from 'generated/proto/serverinfo_card_pb'; import { GamesState, GameEntry, PlayerEntry, ZoneEntry } from './game.interfaces'; interface State { games: GamesState; } -const EMPTY_ARRAY: CardInfo[] = []; +const EMPTY_ARRAY: ServerInfo_Card[] = []; const EMPTY_OBJECT = {} as Record; export const Selectors = { diff --git a/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts b/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts index d0dc23bab..311980a98 100644 --- a/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts +++ b/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts @@ -5,16 +5,16 @@ import { ProtoInit, Room, SortDirection, - User, UserSortField, } from 'types'; +import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; import { create } from '@bufbuild/protobuf'; import { ServerInfo_UserSchema } from 'generated/proto/serverinfo_user_pb'; import { ServerInfo_GameSchema } from 'generated/proto/serverinfo_game_pb'; import { ServerInfo_RoomSchema } from 'generated/proto/serverinfo_room_pb'; import { RoomsState } from '../rooms.interfaces'; -export function makeUser(overrides: ProtoInit = {}): User { +export function makeUser(overrides: ProtoInit = {}): ServerInfo_User { return create(ServerInfo_UserSchema, { name: 'TestUser', accountageSecs: 0n, diff --git a/webclient/src/store/rooms/rooms.actions.tsx b/webclient/src/store/rooms/rooms.actions.tsx index 18106ffd7..40dfa7559 100644 --- a/webclient/src/store/rooms/rooms.actions.tsx +++ b/webclient/src/store/rooms/rooms.actions.tsx @@ -1,4 +1,5 @@ -import { GameSortField, Message, SortDirection, User } from 'types'; +import { GameSortField, Message, SortDirection } from 'types'; +import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; @@ -36,7 +37,7 @@ export const Actions = { games, }), - userJoined: (roomId: number, user: User) => ({ + userJoined: (roomId: number, user: ServerInfo_User) => ({ type: Types.USER_JOINED, roomId, user, diff --git a/webclient/src/store/rooms/rooms.dispatch.tsx b/webclient/src/store/rooms/rooms.dispatch.tsx index 969aeaff9..a1c653f12 100644 --- a/webclient/src/store/rooms/rooms.dispatch.tsx +++ b/webclient/src/store/rooms/rooms.dispatch.tsx @@ -1,4 +1,5 @@ -import { GameSortField, Message, SortDirection, User } from 'types'; +import { GameSortField, Message, SortDirection } from 'types'; +import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; @@ -31,7 +32,7 @@ export const Dispatch = { store.dispatch(Actions.updateGames(roomId, games)); }, - userJoined: (roomId: number, user: User) => { + userJoined: (roomId: number, user: ServerInfo_User) => { store.dispatch(Actions.userJoined(roomId, user)); }, diff --git a/webclient/src/store/server/__mocks__/server-fixtures.ts b/webclient/src/store/server/__mocks__/server-fixtures.ts index 330ad5159..7a5cf20fb 100644 --- a/webclient/src/store/server/__mocks__/server-fixtures.ts +++ b/webclient/src/store/server/__mocks__/server-fixtures.ts @@ -1,19 +1,19 @@ import { - BanHistoryItem, - DeckList, - DeckStorageTreeItem, Game, - LogItem, ProtoInit, - ReplayMatch, SortDirection, StatusEnum, - User, UserSortField, WebSocketConnectOptions, - WarnHistoryItem, - WarnListItem, } from 'types'; +import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; +import type { ServerInfo_Ban } from 'generated/proto/serverinfo_ban_pb'; +import type { ServerInfo_Warning } from 'generated/proto/serverinfo_warning_pb'; +import type { Response_WarnList } from 'generated/proto/response_warn_list_pb'; +import type { ServerInfo_ReplayMatch } from 'generated/proto/serverinfo_replay_match_pb'; +import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb'; +import type { Response_DeckList } from 'generated/proto/response_deck_list_pb'; +import type { ServerInfo_DeckStorage_TreeItem } from 'generated/proto/serverinfo_deckstorage_pb'; import { create } from '@bufbuild/protobuf'; import { ServerInfo_GameSchema } from 'generated/proto/serverinfo_game_pb'; import { ServerInfo_UserSchema } from 'generated/proto/serverinfo_user_pb'; @@ -26,7 +26,7 @@ import { ServerInfo_DeckStorage_TreeItemSchema, ServerInfo_DeckStorage_FolderSch import { Response_DeckListSchema } from 'generated/proto/response_deck_list_pb'; import { ServerState } from '../server.interfaces'; -export function makeUser(overrides: ProtoInit = {}): User { +export function makeUser(overrides: ProtoInit = {}): ServerInfo_User { return create(ServerInfo_UserSchema, { name: 'TestUser', accountageSecs: 0n, @@ -36,7 +36,7 @@ export function makeUser(overrides: ProtoInit = {}): User { }); } -export function makeLogItem(overrides: ProtoInit = {}): LogItem { +export function makeLogItem(overrides: ProtoInit = {}): ServerInfo_ChatMessage { return create(ServerInfo_ChatMessageSchema, { message: '', senderId: '', @@ -50,7 +50,7 @@ export function makeLogItem(overrides: ProtoInit = {}): LogItem { }); } -export function makeBanHistoryItem(overrides: ProtoInit = {}): BanHistoryItem { +export function makeBanHistoryItem(overrides: ProtoInit = {}): ServerInfo_Ban { return create(ServerInfo_BanSchema, { adminId: '', adminName: '', @@ -62,7 +62,7 @@ export function makeBanHistoryItem(overrides: ProtoInit = {}): B }); } -export function makeWarnHistoryItem(overrides: ProtoInit = {}): WarnHistoryItem { +export function makeWarnHistoryItem(overrides: ProtoInit = {}): ServerInfo_Warning { return create(ServerInfo_WarningSchema, { userName: '', adminName: '', @@ -72,7 +72,7 @@ export function makeWarnHistoryItem(overrides: ProtoInit = {}): }); } -export function makeWarnListItem(overrides: ProtoInit = {}): WarnListItem { +export function makeWarnListItem(overrides: ProtoInit = {}): Response_WarnList { return create(Response_WarnListSchema, { warning: [], userName: '', @@ -81,7 +81,7 @@ export function makeWarnListItem(overrides: ProtoInit = {}): WarnL }); } -export function makeDeckTreeItem(overrides: ProtoInit = {}): DeckStorageTreeItem { +export function makeDeckTreeItem(overrides: ProtoInit = {}): ServerInfo_DeckStorage_TreeItem { return create(ServerInfo_DeckStorage_TreeItemSchema, { id: 1, name: 'item', @@ -89,14 +89,14 @@ export function makeDeckTreeItem(overrides: ProtoInit = {}) }); } -export function makeDeckList(overrides: ProtoInit = {}): DeckList { +export function makeDeckList(overrides: ProtoInit = {}): Response_DeckList { return create(Response_DeckListSchema, { root: create(ServerInfo_DeckStorage_FolderSchema, { items: [] }), ...overrides, }); } -export function makeReplayMatch(overrides: ProtoInit = {}): ReplayMatch { +export function makeReplayMatch(overrides: ProtoInit = {}): ServerInfo_ReplayMatch { return create(ServerInfo_ReplayMatchSchema, { gameId: 1, roomName: 'Test Room', diff --git a/webclient/src/store/server/server.actions.ts b/webclient/src/store/server/server.actions.ts index e0c5fbc84..a29f4d49d 100644 --- a/webclient/src/store/server/server.actions.ts +++ b/webclient/src/store/server/server.actions.ts @@ -1,7 +1,14 @@ import { - BanHistoryItem, DeckList, DeckStorageTreeItem, GametypeMap, LogItem, ReplayMatch, - User, WebSocketConnectOptions, WarnHistoryItem, WarnListItem + GametypeMap, WebSocketConnectOptions } from 'types'; +import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; +import type { ServerInfo_Ban } from 'generated/proto/serverinfo_ban_pb'; +import type { ServerInfo_Warning } from 'generated/proto/serverinfo_warning_pb'; +import type { Response_WarnList } from 'generated/proto/response_warn_list_pb'; +import type { ServerInfo_ReplayMatch } from 'generated/proto/serverinfo_replay_match_pb'; +import type { Response_DeckList } from 'generated/proto/response_deck_list_pb'; +import type { ServerInfo_DeckStorage_TreeItem } from 'generated/proto/serverinfo_deckstorage_pb'; +import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb'; import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; import { NotifyUserData, ServerShutdownData, UserMessageData } from 'websocket/events/session/interfaces'; import { ServerStateStatus } from './server.interfaces'; @@ -41,11 +48,11 @@ export const Actions = { type: Types.SERVER_MESSAGE, message }), - updateBuddyList: (buddyList: User[]) => ({ + updateBuddyList: (buddyList: ServerInfo_User[]) => ({ type: Types.UPDATE_BUDDY_LIST, buddyList }), - addToBuddyList: (user: User) => ({ + addToBuddyList: (user: ServerInfo_User) => ({ type: Types.ADD_TO_BUDDY_LIST, user }), @@ -53,11 +60,11 @@ export const Actions = { type: Types.REMOVE_FROM_BUDDY_LIST, userName }), - updateIgnoreList: (ignoreList: User[]) => ({ + updateIgnoreList: (ignoreList: ServerInfo_User[]) => ({ type: Types.UPDATE_IGNORE_LIST, ignoreList }), - addToIgnoreList: (user: User) => ({ + addToIgnoreList: (user: ServerInfo_User) => ({ type: Types.ADD_TO_IGNORE_LIST, user }), @@ -73,15 +80,15 @@ export const Actions = { type: Types.UPDATE_STATUS, status }), - updateUser: (user: User) => ({ + updateUser: (user: ServerInfo_User) => ({ type: Types.UPDATE_USER, user }), - updateUsers: (users: User[]) => ({ + updateUsers: (users: ServerInfo_User[]) => ({ type: Types.UPDATE_USERS, users }), - userJoined: (user: User) => ({ + userJoined: (user: ServerInfo_User) => ({ type: Types.USER_JOINED, user }), @@ -89,7 +96,7 @@ export const Actions = { type: Types.USER_LEFT, name }), - viewLogs: (logs: LogItem[]) => ({ + viewLogs: (logs: ServerInfo_ChatMessage[]) => ({ type: Types.VIEW_LOGS, logs }), @@ -162,15 +169,15 @@ export const Actions = { accountPasswordChange: () => ({ type: Types.ACCOUNT_PASSWORD_CHANGE, }), - accountEditChanged: (user: Partial) => ({ + accountEditChanged: (user: Partial) => ({ type: Types.ACCOUNT_EDIT_CHANGED, user, }), - accountImageChanged: (user: Partial) => ({ + accountImageChanged: (user: Partial) => ({ type: Types.ACCOUNT_IMAGE_CHANGED, user, }), - getUserInfo: (userInfo: User) => ({ + getUserInfo: (userInfo: ServerInfo_User) => ({ type: Types.GET_USER_INFO, userInfo, }), @@ -200,17 +207,17 @@ export const Actions = { type: Types.BAN_FROM_SERVER, userName, }), - banHistory: (userName: string, banHistory: BanHistoryItem[]) => ({ + banHistory: (userName: string, banHistory: ServerInfo_Ban[]) => ({ type: Types.BAN_HISTORY, userName, banHistory, }), - warnHistory: (userName: string, warnHistory: WarnHistoryItem[]) => ({ + warnHistory: (userName: string, warnHistory: ServerInfo_Warning[]) => ({ type: Types.WARN_HISTORY, userName, warnHistory, }), - warnListOptions: (warnList: WarnListItem[]) => ({ + warnListOptions: (warnList: Response_WarnList[]) => ({ type: Types.WARN_LIST_OPTIONS, warnList, }), @@ -238,14 +245,14 @@ export const Actions = { userName, notes, }), - replayList: (matchList: ReplayMatch[]) => ({ type: Types.REPLAY_LIST, matchList }), - replayAdded: (matchInfo: ReplayMatch) => ({ type: Types.REPLAY_ADDED, matchInfo }), + replayList: (matchList: ServerInfo_ReplayMatch[]) => ({ type: Types.REPLAY_LIST, matchList }), + replayAdded: (matchInfo: ServerInfo_ReplayMatch) => ({ type: Types.REPLAY_ADDED, matchInfo }), replayModifyMatch: (gameId: number, doNotHide: boolean) => ({ type: Types.REPLAY_MODIFY_MATCH, gameId, doNotHide }), replayDeleteMatch: (gameId: number) => ({ type: Types.REPLAY_DELETE_MATCH, gameId }), - backendDecks: (deckList: DeckList) => ({ type: Types.BACKEND_DECKS, deckList }), + backendDecks: (deckList: Response_DeckList) => ({ type: Types.BACKEND_DECKS, deckList }), deckNewDir: (path: string, dirName: string) => ({ type: Types.DECK_NEW_DIR, path, dirName }), deckDelDir: (path: string) => ({ type: Types.DECK_DEL_DIR, path }), - deckUpload: (path: string, treeItem: DeckStorageTreeItem) => ({ type: Types.DECK_UPLOAD, path, treeItem }), + deckUpload: (path: string, treeItem: ServerInfo_DeckStorage_TreeItem) => ({ type: Types.DECK_UPLOAD, path, treeItem }), deckDelete: (deckId: number) => ({ type: Types.DECK_DELETE, deckId }), gamesOfUser: (userName: string, games: ServerInfo_Game[], gametypeMap: GametypeMap) => ({ type: Types.GAMES_OF_USER, userName, games, gametypeMap }), diff --git a/webclient/src/store/server/server.dispatch.ts b/webclient/src/store/server/server.dispatch.ts index 2030a4dad..19db7ced1 100644 --- a/webclient/src/store/server/server.dispatch.ts +++ b/webclient/src/store/server/server.dispatch.ts @@ -1,9 +1,16 @@ import { Actions } from './server.actions'; import { store } from 'store'; import { - BanHistoryItem, DeckList, DeckStorageTreeItem, GametypeMap, LogItem, ReplayMatch, - User, WarnHistoryItem, WarnListItem, WebSocketConnectOptions + GametypeMap, WebSocketConnectOptions } from 'types'; +import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; +import type { ServerInfo_Ban } from 'generated/proto/serverinfo_ban_pb'; +import type { ServerInfo_Warning } from 'generated/proto/serverinfo_warning_pb'; +import type { Response_WarnList } from 'generated/proto/response_warn_list_pb'; +import type { ServerInfo_ReplayMatch } from 'generated/proto/serverinfo_replay_match_pb'; +import type { Response_DeckList } from 'generated/proto/response_deck_list_pb'; +import type { ServerInfo_DeckStorage_TreeItem } from 'generated/proto/serverinfo_deckstorage_pb'; +import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb'; import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; import { NotifyUserData, ServerShutdownData, UserMessageData } from 'websocket/events/session/interfaces'; @@ -35,19 +42,19 @@ export const Dispatch = { testConnectionFailed: () => { store.dispatch(Actions.testConnectionFailed()); }, - updateBuddyList: (buddyList: User[]) => { + updateBuddyList: (buddyList: ServerInfo_User[]) => { store.dispatch(Actions.updateBuddyList(buddyList)); }, - addToBuddyList: (user: User) => { + addToBuddyList: (user: ServerInfo_User) => { store.dispatch(Actions.addToBuddyList(user)); }, removeFromBuddyList: (userName: string) => { store.dispatch(Actions.removeFromBuddyList(userName)); }, - updateIgnoreList: (ignoreList: User[]) => { + updateIgnoreList: (ignoreList: ServerInfo_User[]) => { store.dispatch(Actions.updateIgnoreList(ignoreList)); }, - addToIgnoreList: (user: User) => { + addToIgnoreList: (user: ServerInfo_User) => { store.dispatch(Actions.addToIgnoreList(user)); }, removeFromIgnoreList: (userName: string) => { @@ -65,19 +72,19 @@ export const Dispatch = { description })); }, - updateUser: (user: User) => { + updateUser: (user: ServerInfo_User) => { store.dispatch(Actions.updateUser(user)); }, - updateUsers: (users: User[]) => { + updateUsers: (users: ServerInfo_User[]) => { store.dispatch(Actions.updateUsers(users)); }, - userJoined: (user: User) => { + userJoined: (user: ServerInfo_User) => { store.dispatch(Actions.userJoined(user)); }, userLeft: (name: string) => { store.dispatch(Actions.userLeft(name)); }, - viewLogs: (logs: LogItem[]) => { + viewLogs: (logs: ServerInfo_ChatMessage[]) => { store.dispatch(Actions.viewLogs(logs)); }, clearLogs: () => { @@ -143,13 +150,13 @@ export const Dispatch = { accountPasswordChange: () => { store.dispatch(Actions.accountPasswordChange()); }, - accountEditChanged: (user: Partial) => { + accountEditChanged: (user: Partial) => { store.dispatch(Actions.accountEditChanged(user)); }, - accountImageChanged: (user: Partial) => { + accountImageChanged: (user: Partial) => { store.dispatch(Actions.accountImageChanged(user)); }, - getUserInfo: (userInfo: User) => { + getUserInfo: (userInfo: ServerInfo_User) => { store.dispatch(Actions.getUserInfo(userInfo)); }, notifyUser: (notification: NotifyUserData) => { @@ -170,13 +177,13 @@ export const Dispatch = { banFromServer: (userName: string) => { store.dispatch(Actions.banFromServer(userName)); }, - banHistory: (userName: string, banHistory: BanHistoryItem[]) => { + banHistory: (userName: string, banHistory: ServerInfo_Ban[]) => { store.dispatch(Actions.banHistory(userName, banHistory)) }, - warnHistory: (userName: string, warnHistory: WarnHistoryItem[]) => { + warnHistory: (userName: string, warnHistory: ServerInfo_Warning[]) => { store.dispatch(Actions.warnHistory(userName, warnHistory)) }, - warnListOptions: (warnList: WarnListItem[]) => { + warnListOptions: (warnList: Response_WarnList[]) => { store.dispatch(Actions.warnListOptions(warnList)) }, warnUser: (userName: string) => { @@ -194,10 +201,10 @@ export const Dispatch = { updateAdminNotes: (userName: string, notes: string) => { store.dispatch(Actions.updateAdminNotes(userName, notes)); }, - replayList: (matchList: ReplayMatch[]) => { + replayList: (matchList: ServerInfo_ReplayMatch[]) => { store.dispatch(Actions.replayList(matchList)); }, - replayAdded: (matchInfo: ReplayMatch) => { + replayAdded: (matchInfo: ServerInfo_ReplayMatch) => { store.dispatch(Actions.replayAdded(matchInfo)); }, replayModifyMatch: (gameId: number, doNotHide: boolean) => { @@ -206,7 +213,7 @@ export const Dispatch = { replayDeleteMatch: (gameId: number) => { store.dispatch(Actions.replayDeleteMatch(gameId)); }, - backendDecks: (deckList: DeckList) => { + backendDecks: (deckList: Response_DeckList) => { store.dispatch(Actions.backendDecks(deckList)); }, deckNewDir: (path: string, dirName: string) => { @@ -215,7 +222,7 @@ export const Dispatch = { deckDelDir: (path: string) => { store.dispatch(Actions.deckDelDir(path)); }, - deckUpload: (path: string, treeItem: DeckStorageTreeItem) => { + deckUpload: (path: string, treeItem: ServerInfo_DeckStorage_TreeItem) => { store.dispatch(Actions.deckUpload(path, treeItem)); }, deckDelete: (deckId: number) => { diff --git a/webclient/src/store/server/server.interfaces.ts b/webclient/src/store/server/server.interfaces.ts index e4cc101fa..3791c3509 100644 --- a/webclient/src/store/server/server.interfaces.ts +++ b/webclient/src/store/server/server.interfaces.ts @@ -1,6 +1,13 @@ import { - WarnHistoryItem, BanHistoryItem, DeckList, Game, LogItem, ReplayMatch, SortBy, User, UserSortField, WarnListItem + Game, SortBy, UserSortField } from 'types'; +import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; +import type { ServerInfo_Ban } from 'generated/proto/serverinfo_ban_pb'; +import type { ServerInfo_Warning } from 'generated/proto/serverinfo_warning_pb'; +import type { Response_WarnList } from 'generated/proto/response_warn_list_pb'; +import type { ServerInfo_ReplayMatch } from 'generated/proto/serverinfo_replay_match_pb'; +import type { Response_DeckList } from 'generated/proto/response_deck_list_pb'; +import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb'; import { NotifyUserData, ServerShutdownData, UserMessageData } from 'websocket/events/session/interfaces'; export interface ServerConnectParams { @@ -43,34 +50,34 @@ export interface AccountActivationParams extends ServerRegisterParams { export interface ServerState { initialized: boolean; - buddyList: User[]; - ignoreList: User[]; + buddyList: ServerInfo_User[]; + ignoreList: ServerInfo_User[]; info: ServerStateInfo; status: ServerStateStatus; logs: ServerStateLogs; - user: User; - users: User[]; + user: ServerInfo_User; + users: ServerInfo_User[]; sortUsersBy: ServerStateSortUsersBy; messages: { [userName: string]: UserMessageData[]; } userInfo: { - [userName: string]: User; + [userName: string]: ServerInfo_User; } notifications: NotifyUserData[]; serverShutdown: ServerShutdownData; banUser: string; banHistory: { - [userName: string]: BanHistoryItem[]; + [userName: string]: ServerInfo_Ban[]; }; warnHistory: { - [userName: string]: WarnHistoryItem[]; + [userName: string]: ServerInfo_Warning[]; }; - warnListOptions: WarnListItem[]; + warnListOptions: Response_WarnList[]; warnUser: string; adminNotes: { [userName: string]: string }; - replays: ReplayMatch[]; - backendDecks: DeckList | null; + replays: ServerInfo_ReplayMatch[]; + backendDecks: Response_DeckList | null; gamesOfUser: { [userName: string]: Game[] }; registrationError: string | null; } @@ -88,9 +95,9 @@ export interface ServerStateInfo { } export interface ServerStateLogs { - room: LogItem[]; - game: LogItem[]; - chat: LogItem[]; + room: ServerInfo_ChatMessage[]; + game: ServerInfo_ChatMessage[]; + chat: ServerInfo_ChatMessage[]; } export interface ServerStateSortUsersBy extends SortBy { diff --git a/webclient/src/store/server/server.reducer.spec.ts b/webclient/src/store/server/server.reducer.spec.ts index f3ec5bbde..573683a76 100644 --- a/webclient/src/store/server/server.reducer.spec.ts +++ b/webclient/src/store/server/server.reducer.spec.ts @@ -1,4 +1,5 @@ -import { StatusEnum, UserLevelFlag } from 'types'; +import { StatusEnum } from 'types'; +import { ServerInfo_User_UserLevelFlag as UserLevelFlag } from 'generated/proto/serverinfo_user_pb'; import { create } from '@bufbuild/protobuf'; import { Event_UserMessageSchema } from 'generated/proto/event_user_message_pb'; import { ServerInfo_DeckStorage_FolderSchema, ServerInfo_DeckStorage_TreeItemSchema } from 'generated/proto/serverinfo_deckstorage_pb'; diff --git a/webclient/src/store/server/server.reducer.ts b/webclient/src/store/server/server.reducer.ts index 17440339d..44276bce6 100644 --- a/webclient/src/store/server/server.reducer.ts +++ b/webclient/src/store/server/server.reducer.ts @@ -1,4 +1,6 @@ -import { DeckStorageFolder, DeckStorageTreeItem, SortDirection, StatusEnum, UserLevelFlag, UserSortField } from 'types'; +import { SortDirection, StatusEnum, UserSortField } from 'types'; +import { ServerInfo_User_UserLevelFlag } from 'generated/proto/serverinfo_user_pb'; +import type { ServerInfo_DeckStorage_Folder, ServerInfo_DeckStorage_TreeItem } from 'generated/proto/serverinfo_deckstorage_pb'; import { create } from '@bufbuild/protobuf'; import { Response_DeckListSchema } from 'generated/proto/response_deck_list_pb'; import { ServerInfo_DeckStorage_FolderSchema, ServerInfo_DeckStorage_TreeItemSchema } from 'generated/proto/serverinfo_deckstorage_pb'; @@ -13,7 +15,11 @@ function splitPath(path: string): string[] { return path ? path.split('/') : []; } -function insertAtPath(folder: DeckStorageFolder, pathSegments: string[], item: DeckStorageTreeItem): DeckStorageFolder { +function insertAtPath( + folder: ServerInfo_DeckStorage_Folder, + pathSegments: string[], + item: ServerInfo_DeckStorage_TreeItem, +): ServerInfo_DeckStorage_Folder { if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === '')) { return create(ServerInfo_DeckStorage_FolderSchema, { items: [...folder.items, item] }); } @@ -28,13 +34,13 @@ function insertAtPath(folder: DeckStorageFolder, pathSegments: string[], item: D ), }); } - const created: DeckStorageTreeItem = create(ServerInfo_DeckStorage_TreeItemSchema, { + const created: ServerInfo_DeckStorage_TreeItem = create(ServerInfo_DeckStorage_TreeItemSchema, { id: 0, name: head, folder: insertAtPath(create(ServerInfo_DeckStorage_FolderSchema, { items: [] }), tail, item) }); return create(ServerInfo_DeckStorage_FolderSchema, { items: [...folder.items, created] }); } -function removeById(folder: DeckStorageFolder, id: number): DeckStorageFolder { +function removeById(folder: ServerInfo_DeckStorage_Folder, id: number): ServerInfo_DeckStorage_Folder { return create(ServerInfo_DeckStorage_FolderSchema, { items: folder.items .filter(item => item.id !== id) @@ -44,7 +50,7 @@ function removeById(folder: DeckStorageFolder, id: number): DeckStorageFolder { }); } -function removeByPath(folder: DeckStorageFolder, pathSegments: string[]): DeckStorageFolder { +function removeByPath(folder: ServerInfo_DeckStorage_Folder, pathSegments: string[]): ServerInfo_DeckStorage_Folder { if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === '')) { return folder; } @@ -410,8 +416,12 @@ export const serverReducer = (state = initialState, action: ServerAction) => { return user; } let newLevel = user.userLevel; - newLevel = shouldBeMod ? (newLevel | UserLevelFlag.IsModerator) : (newLevel & ~UserLevelFlag.IsModerator); - newLevel = shouldBeJudge ? (newLevel | UserLevelFlag.IsJudge) : (newLevel & ~UserLevelFlag.IsJudge); + newLevel = shouldBeMod + ? (newLevel | ServerInfo_User_UserLevelFlag.IsModerator) + : (newLevel & ~ServerInfo_User_UserLevelFlag.IsModerator); + newLevel = shouldBeJudge + ? (newLevel | ServerInfo_User_UserLevelFlag.IsJudge) + : (newLevel & ~ServerInfo_User_UserLevelFlag.IsJudge); return { ...user, userLevel: newLevel, @@ -465,7 +475,7 @@ export const serverReducer = (state = initialState, action: ServerAction) => { if (!state.backendDecks?.root) { return state; } - const newFolder: DeckStorageTreeItem = create(ServerInfo_DeckStorage_TreeItemSchema, { + const newFolder: ServerInfo_DeckStorage_TreeItem = create(ServerInfo_DeckStorage_TreeItemSchema, { id: 0, name: action.dirName, folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] }) }); return { diff --git a/webclient/src/types/deckList.ts b/webclient/src/types/deckList.ts deleted file mode 100644 index 63ef28f13..000000000 --- a/webclient/src/types/deckList.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { - ServerInfo_DeckStorage_File, ServerInfo_DeckStorage_Folder, ServerInfo_DeckStorage_TreeItem -} from 'generated/proto/serverinfo_deckstorage_pb'; -import type { Response_DeckList } from 'generated/proto/response_deck_list_pb'; - -export type DeckList = Response_DeckList; -export type DeckStorageFolder = ServerInfo_DeckStorage_Folder; -export type DeckStorageFile = ServerInfo_DeckStorage_File; -export type DeckStorageTreeItem = ServerInfo_DeckStorage_TreeItem; diff --git a/webclient/src/types/game.ts b/webclient/src/types/game.ts index 120073f58..8cffb3f85 100644 --- a/webclient/src/types/game.ts +++ b/webclient/src/types/game.ts @@ -1,7 +1,8 @@ // ── Imports from generated proto files ─────────────────────────────────────── +import type { ProtoInit } from './utilities'; import type { GameEventContext } from 'generated/proto/game_event_context_pb'; -import type { CardToMove, Command_MoveCard } from 'generated/proto/command_move_card_pb'; +import type { Command_MoveCard } from 'generated/proto/command_move_card_pb'; import type { Command_DrawCards } from 'generated/proto/command_draw_cards_pb'; import type { Command_RollDie } from 'generated/proto/command_roll_die_pb'; import type { Command_Shuffle } from 'generated/proto/command_shuffle_pb'; @@ -24,59 +25,11 @@ import type { Command_KickFromGame } from 'generated/proto/command_kick_from_gam import type { Command_ReadyStart } from 'generated/proto/command_ready_start_pb'; import type { Command_Mulligan } from 'generated/proto/command_mulligan_pb'; import type { Command_DeckSelect } from 'generated/proto/command_deck_select_pb'; -import type { MoveCard_ToZone } from 'generated/proto/move_card_to_zone_pb'; import type { Command_SetSideboardPlan } from 'generated/proto/command_set_sideboard_plan_pb'; import type { Command_SetSideboardLock } from 'generated/proto/command_set_sideboard_lock_pb'; import type { Command_SetActivePhase } from 'generated/proto/command_set_active_phase_pb'; import type { Command_GameSay } from 'generated/proto/command_game_say_pb'; -import type { Event_GameStateChanged } from 'generated/proto/event_game_state_changed_pb'; -import type { Event_GameSay } from 'generated/proto/event_game_say_pb'; -import type { Event_MoveCard } from 'generated/proto/event_move_card_pb'; -import type { Event_FlipCard } from 'generated/proto/event_flip_card_pb'; -import type { Event_DestroyCard } from 'generated/proto/event_destroy_card_pb'; -import type { Event_AttachCard } from 'generated/proto/event_attach_card_pb'; -import type { Event_CreateToken } from 'generated/proto/event_create_token_pb'; -import type { Event_SetCardAttr } from 'generated/proto/event_set_card_attr_pb'; -import type { Event_SetCardCounter } from 'generated/proto/event_set_card_counter_pb'; -import type { Event_CreateArrow } from 'generated/proto/event_create_arrow_pb'; -import type { Event_DeleteArrow } from 'generated/proto/event_delete_arrow_pb'; -import type { Event_CreateCounter } from 'generated/proto/event_create_counter_pb'; -import type { Event_SetCounter } from 'generated/proto/event_set_counter_pb'; -import type { Event_DelCounter } from 'generated/proto/event_del_counter_pb'; -import type { Event_DrawCards } from 'generated/proto/event_draw_cards_pb'; -import type { Event_RevealCards } from 'generated/proto/event_reveal_cards_pb'; -import type { Event_Shuffle } from 'generated/proto/event_shuffle_pb'; -import type { Event_RollDie } from 'generated/proto/event_roll_die_pb'; -import type { Event_DumpZone } from 'generated/proto/event_dump_zone_pb'; -import type { Event_ChangeZoneProperties } from 'generated/proto/event_change_zone_properties_pb'; -import type { Event_SetActivePlayer } from 'generated/proto/event_set_active_player_pb'; -import type { Event_SetActivePhase } from 'generated/proto/event_set_active_phase_pb'; -import type { Event_ReverseTurn } from 'generated/proto/event_reverse_turn_pb'; import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; -import type { color } from 'generated/proto/color_pb'; -import type { ServerInfo_CardCounter } from 'generated/proto/serverinfo_cardcounter_pb'; -import type { ServerInfo_Card } from 'generated/proto/serverinfo_card_pb'; -import type { ServerInfo_Zone } from 'generated/proto/serverinfo_zone_pb'; -import type { ServerInfo_Counter } from 'generated/proto/serverinfo_counter_pb'; -import type { ServerInfo_Arrow } from 'generated/proto/serverinfo_arrow_pb'; -import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb'; -import type { ServerInfo_Player } from 'generated/proto/serverinfo_player_pb'; - -// ── Enum re-exports from generated proto files ──────────────────────────────── - -export { CardAttribute } from 'generated/proto/card_attributes_pb'; -export { ServerInfo_Zone_ZoneType as ZoneType } from 'generated/proto/serverinfo_zone_pb'; - -// ── Proto utility types ─────────────────────────────────────────────────────── - -/** - * Init shape for constructing protobuf messages via create(). - * Strips $typeName and $unknown branding, making all fields optional. - * Use for function parameters that feed into create(). - */ -export type ProtoInit = { - [K in keyof T as K extends '$typeName' | '$unknown' ? never : K]?: T[K]; -}; // ── UI types (not proto mirrors) ────────────────────────────────────────────── @@ -120,46 +73,7 @@ export enum LeaveGameReason { USER_DISCONNECTED = 4 } -// ── Type aliases for generated state mirror types ───────────────────────────── - -export type Color = color; -export type CardCounterInfo = ServerInfo_CardCounter; -export type CardInfo = ServerInfo_Card; -export type ZoneInfo = ServerInfo_Zone; -export type CounterInfo = ServerInfo_Counter; -export type ArrowInfo = ServerInfo_Arrow; -export type PlayerProperties = ServerInfo_PlayerProperties; -export type PlayerInfo = ServerInfo_Player; - -// ── Type aliases for generated event data types ─────────────────────────────── - -export type GameStateChangedData = Event_GameStateChanged; -export type GameSayData = Event_GameSay; -export type MoveCardData = Event_MoveCard; -export type FlipCardData = Event_FlipCard; -export type DestroyCardData = Event_DestroyCard; -export type AttachCardData = Event_AttachCard; -export type CreateTokenData = Event_CreateToken; -export type SetCardAttrData = Event_SetCardAttr; -export type SetCardCounterData = Event_SetCardCounter; -export type CreateArrowData = Event_CreateArrow; -export type DeleteArrowData = Event_DeleteArrow; -export type CreateCounterData = Event_CreateCounter; -export type SetCounterData = Event_SetCounter; -export type DelCounterData = Event_DelCounter; -export type DrawCardsData = Event_DrawCards; -export type RevealCardsData = Event_RevealCards; -export type ShuffleData = Event_Shuffle; -export type RollDieData = Event_RollDie; -export type DumpZoneData = Event_DumpZone; -export type ChangeZonePropertiesData = Event_ChangeZoneProperties; -export type SetActivePlayerData = Event_SetActivePlayer; -export type SetActivePhaseData = Event_SetActivePhase; -export type ReverseTurnData = Event_ReverseTurn; - -// ── GameEventContext (re-export of generated type) ──────────────────────────── - -export type { GameEventContext }; +// ── GameEventContext (imported for use in GameEventMeta below) ─────────────── /** * Passed to every game event handler alongside the event payload. @@ -180,7 +94,6 @@ export interface GameEventMeta { // These use ProtoInit<> because callers construct plain objects; // the command functions internally call create(Schema, params). -export type { CardToMove }; export type MoveCardParams = ProtoInit; export type DrawCardsParams = ProtoInit; export type RollDieParams = ProtoInit; @@ -204,7 +117,6 @@ export type KickFromGameParams = ProtoInit; export type ReadyStartParams = ProtoInit; export type MulliganParams = ProtoInit; export type DeckSelectParams = ProtoInit; -export type MoveCardToZone = MoveCard_ToZone; export type SetSideboardPlanParams = ProtoInit; export type SetSideboardLockParams = ProtoInit; export type SetActivePhaseParams = ProtoInit; diff --git a/webclient/src/types/index.ts b/webclient/src/types/index.ts index c23171132..b542d7937 100644 --- a/webclient/src/types/index.ts +++ b/webclient/src/types/index.ts @@ -11,7 +11,4 @@ export * from './message'; export * from './settings'; export * from './languages'; export * from './logs'; -export * from './session'; -export * from './deckList'; -export * from './moderator'; -export * from './replay'; +export * from './utilities'; diff --git a/webclient/src/types/moderator.ts b/webclient/src/types/moderator.ts deleted file mode 100644 index 0b6009b84..000000000 --- a/webclient/src/types/moderator.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { ServerInfo_Ban } from 'generated/proto/serverinfo_ban_pb'; -import type { ServerInfo_Warning } from 'generated/proto/serverinfo_warning_pb'; -import type { Response_WarnList } from 'generated/proto/response_warn_list_pb'; - -export type BanHistoryItem = ServerInfo_Ban; -export type WarnHistoryItem = ServerInfo_Warning; -export type WarnListItem = Response_WarnList; diff --git a/webclient/src/types/replay.ts b/webclient/src/types/replay.ts deleted file mode 100644 index 5f2e19217..000000000 --- a/webclient/src/types/replay.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { ServerInfo_Replay } from 'generated/proto/serverinfo_replay_pb'; -import type { ServerInfo_ReplayMatch } from 'generated/proto/serverinfo_replay_match_pb'; - -export type Replay = ServerInfo_Replay; -export type ReplayMatch = ServerInfo_ReplayMatch; diff --git a/webclient/src/types/server.ts b/webclient/src/types/server.ts index 4b74c75c1..8c42b1165 100644 --- a/webclient/src/types/server.ts +++ b/webclient/src/types/server.ts @@ -111,10 +111,8 @@ export const KnownHosts = { [KnownHost.TETRARCH]: { port: 443, host: 'mtg.tetrarch.co/servatrice' }, } -export type LogItem = ServerInfo_ChatMessage; - export interface LogGroups { - room: LogItem[]; - game: LogItem[]; - chat: LogItem[]; + room: ServerInfo_ChatMessage[]; + game: ServerInfo_ChatMessage[]; + chat: ServerInfo_ChatMessage[]; } diff --git a/webclient/src/types/session.ts b/webclient/src/types/session.ts deleted file mode 100644 index a7a615c4a..000000000 --- a/webclient/src/types/session.ts +++ /dev/null @@ -1 +0,0 @@ -export { Event_NotifyUser_NotificationType as NotificationType } from 'generated/proto/event_notify_user_pb'; diff --git a/webclient/src/types/user.ts b/webclient/src/types/user.ts index f3db25849..3034b977c 100644 --- a/webclient/src/types/user.ts +++ b/webclient/src/types/user.ts @@ -1,8 +1,3 @@ -import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; - -export type User = ServerInfo_User; -export { ServerInfo_User_UserLevelFlag as UserLevelFlag } from 'generated/proto/serverinfo_user_pb'; - export enum UserSortField { NAME = 'name' } diff --git a/webclient/src/types/utilities.ts b/webclient/src/types/utilities.ts new file mode 100644 index 000000000..9a113c264 --- /dev/null +++ b/webclient/src/types/utilities.ts @@ -0,0 +1,8 @@ +/** + * Init shape for constructing protobuf messages via create(). + * Strips $typeName and $unknown branding, making all fields optional. + * Use for function parameters that feed into create(). + */ +export type ProtoInit = { + [K in keyof T as K extends '$typeName' | '$unknown' ? never : K]?: T[K]; +}; diff --git a/webclient/src/websocket/events/game/attachCard.ts b/webclient/src/websocket/events/game/attachCard.ts index 612d8e0bc..14c6e0354 100644 --- a/webclient/src/websocket/events/game/attachCard.ts +++ b/webclient/src/websocket/events/game/attachCard.ts @@ -1,6 +1,7 @@ -import { AttachCardData, GameEventMeta } from 'types'; +import type { Event_AttachCard } from 'generated/proto/event_attach_card_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function attachCard(data: AttachCardData, meta: GameEventMeta): void { +export function attachCard(data: Event_AttachCard, meta: GameEventMeta): void { GamePersistence.cardAttached(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/changeZoneProperties.ts b/webclient/src/websocket/events/game/changeZoneProperties.ts index d18008d46..8454cb254 100644 --- a/webclient/src/websocket/events/game/changeZoneProperties.ts +++ b/webclient/src/websocket/events/game/changeZoneProperties.ts @@ -1,6 +1,7 @@ -import { ChangeZonePropertiesData, GameEventMeta } from 'types'; +import type { Event_ChangeZoneProperties } from 'generated/proto/event_change_zone_properties_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function changeZoneProperties(data: ChangeZonePropertiesData, meta: GameEventMeta): void { +export function changeZoneProperties(data: Event_ChangeZoneProperties, meta: GameEventMeta): void { GamePersistence.zonePropertiesChanged(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/createArrow.ts b/webclient/src/websocket/events/game/createArrow.ts index 188b308d5..4685621d0 100644 --- a/webclient/src/websocket/events/game/createArrow.ts +++ b/webclient/src/websocket/events/game/createArrow.ts @@ -1,6 +1,7 @@ -import { CreateArrowData, GameEventMeta } from 'types'; +import type { Event_CreateArrow } from 'generated/proto/event_create_arrow_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function createArrow(data: CreateArrowData, meta: GameEventMeta): void { +export function createArrow(data: Event_CreateArrow, meta: GameEventMeta): void { GamePersistence.arrowCreated(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/createCounter.ts b/webclient/src/websocket/events/game/createCounter.ts index aa9c9ed5a..f7237ac06 100644 --- a/webclient/src/websocket/events/game/createCounter.ts +++ b/webclient/src/websocket/events/game/createCounter.ts @@ -1,6 +1,7 @@ -import { CreateCounterData, GameEventMeta } from 'types'; +import type { Event_CreateCounter } from 'generated/proto/event_create_counter_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function createCounter(data: CreateCounterData, meta: GameEventMeta): void { +export function createCounter(data: Event_CreateCounter, meta: GameEventMeta): void { GamePersistence.counterCreated(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/createToken.ts b/webclient/src/websocket/events/game/createToken.ts index d2389e98a..f4935661b 100644 --- a/webclient/src/websocket/events/game/createToken.ts +++ b/webclient/src/websocket/events/game/createToken.ts @@ -1,6 +1,7 @@ -import { CreateTokenData, GameEventMeta } from 'types'; +import type { Event_CreateToken } from 'generated/proto/event_create_token_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function createToken(data: CreateTokenData, meta: GameEventMeta): void { +export function createToken(data: Event_CreateToken, meta: GameEventMeta): void { GamePersistence.tokenCreated(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/delCounter.ts b/webclient/src/websocket/events/game/delCounter.ts index 431e64696..533425d03 100644 --- a/webclient/src/websocket/events/game/delCounter.ts +++ b/webclient/src/websocket/events/game/delCounter.ts @@ -1,6 +1,7 @@ -import { DelCounterData, GameEventMeta } from 'types'; +import type { Event_DelCounter } from 'generated/proto/event_del_counter_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function delCounter(data: DelCounterData, meta: GameEventMeta): void { +export function delCounter(data: Event_DelCounter, meta: GameEventMeta): void { GamePersistence.counterDeleted(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/deleteArrow.ts b/webclient/src/websocket/events/game/deleteArrow.ts index 30710e9ec..5461fb2f1 100644 --- a/webclient/src/websocket/events/game/deleteArrow.ts +++ b/webclient/src/websocket/events/game/deleteArrow.ts @@ -1,6 +1,7 @@ -import { DeleteArrowData, GameEventMeta } from 'types'; +import type { Event_DeleteArrow } from 'generated/proto/event_delete_arrow_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function deleteArrow(data: DeleteArrowData, meta: GameEventMeta): void { +export function deleteArrow(data: Event_DeleteArrow, meta: GameEventMeta): void { GamePersistence.arrowDeleted(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/destroyCard.ts b/webclient/src/websocket/events/game/destroyCard.ts index 79e4a536a..1c7c69de9 100644 --- a/webclient/src/websocket/events/game/destroyCard.ts +++ b/webclient/src/websocket/events/game/destroyCard.ts @@ -1,6 +1,7 @@ -import { DestroyCardData, GameEventMeta } from 'types'; +import type { Event_DestroyCard } from 'generated/proto/event_destroy_card_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function destroyCard(data: DestroyCardData, meta: GameEventMeta): void { +export function destroyCard(data: Event_DestroyCard, meta: GameEventMeta): void { GamePersistence.cardDestroyed(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/drawCards.ts b/webclient/src/websocket/events/game/drawCards.ts index 7946fed92..e4c43f9f1 100644 --- a/webclient/src/websocket/events/game/drawCards.ts +++ b/webclient/src/websocket/events/game/drawCards.ts @@ -1,6 +1,7 @@ -import { DrawCardsData, GameEventMeta } from 'types'; +import type { Event_DrawCards } from 'generated/proto/event_draw_cards_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function drawCards(data: DrawCardsData, meta: GameEventMeta): void { +export function drawCards(data: Event_DrawCards, meta: GameEventMeta): void { GamePersistence.cardsDrawn(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/dumpZone.ts b/webclient/src/websocket/events/game/dumpZone.ts index 39e195288..9ee513240 100644 --- a/webclient/src/websocket/events/game/dumpZone.ts +++ b/webclient/src/websocket/events/game/dumpZone.ts @@ -1,6 +1,7 @@ -import { DumpZoneData, GameEventMeta } from 'types'; +import type { Event_DumpZone } from 'generated/proto/event_dump_zone_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function dumpZone(data: DumpZoneData, meta: GameEventMeta): void { +export function dumpZone(data: Event_DumpZone, meta: GameEventMeta): void { GamePersistence.zoneDumped(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/flipCard.ts b/webclient/src/websocket/events/game/flipCard.ts index 040445ebc..51ac479b3 100644 --- a/webclient/src/websocket/events/game/flipCard.ts +++ b/webclient/src/websocket/events/game/flipCard.ts @@ -1,6 +1,7 @@ -import { FlipCardData, GameEventMeta } from 'types'; +import type { Event_FlipCard } from 'generated/proto/event_flip_card_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function flipCard(data: FlipCardData, meta: GameEventMeta): void { +export function flipCard(data: Event_FlipCard, meta: GameEventMeta): void { GamePersistence.cardFlipped(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/gameSay.ts b/webclient/src/websocket/events/game/gameSay.ts index 1f649a2fa..66cfc06f9 100644 --- a/webclient/src/websocket/events/game/gameSay.ts +++ b/webclient/src/websocket/events/game/gameSay.ts @@ -1,6 +1,7 @@ -import { GameEventMeta, GameSayData } from 'types'; +import type { Event_GameSay } from 'generated/proto/event_game_say_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function gameSay(data: GameSayData, meta: GameEventMeta): void { +export function gameSay(data: Event_GameSay, meta: GameEventMeta): void { GamePersistence.gameSay(meta.gameId, meta.playerId, data.message); } diff --git a/webclient/src/websocket/events/game/gameStateChanged.ts b/webclient/src/websocket/events/game/gameStateChanged.ts index 94ce9a136..16f35b136 100644 --- a/webclient/src/websocket/events/game/gameStateChanged.ts +++ b/webclient/src/websocket/events/game/gameStateChanged.ts @@ -1,6 +1,7 @@ -import { GameEventMeta, GameStateChangedData } from 'types'; +import type { Event_GameStateChanged } from 'generated/proto/event_game_state_changed_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function gameStateChanged(data: GameStateChangedData, meta: GameEventMeta): void { +export function gameStateChanged(data: Event_GameStateChanged, meta: GameEventMeta): void { GamePersistence.gameStateChanged(meta.gameId, data); } diff --git a/webclient/src/websocket/events/game/joinGame.ts b/webclient/src/websocket/events/game/joinGame.ts index b0150937b..640e83e96 100644 --- a/webclient/src/websocket/events/game/joinGame.ts +++ b/webclient/src/websocket/events/game/joinGame.ts @@ -1,6 +1,7 @@ import { GamePersistence } from '../../persistence'; -import { GameEventMeta, PlayerProperties } from 'types'; +import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb'; +import type { GameEventMeta } from 'types'; -export function joinGame(data: { playerProperties: PlayerProperties }, meta: GameEventMeta): void { +export function joinGame(data: { playerProperties: ServerInfo_PlayerProperties }, meta: GameEventMeta): void { GamePersistence.playerJoined(meta.gameId, data.playerProperties); } diff --git a/webclient/src/websocket/events/game/moveCard.ts b/webclient/src/websocket/events/game/moveCard.ts index 8a0f3d167..a15666d57 100644 --- a/webclient/src/websocket/events/game/moveCard.ts +++ b/webclient/src/websocket/events/game/moveCard.ts @@ -1,6 +1,7 @@ -import { GameEventMeta, MoveCardData } from 'types'; +import type { Event_MoveCard } from 'generated/proto/event_move_card_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function moveCard(data: MoveCardData, meta: GameEventMeta): void { +export function moveCard(data: Event_MoveCard, meta: GameEventMeta): void { GamePersistence.cardMoved(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/playerPropertiesChanged.ts b/webclient/src/websocket/events/game/playerPropertiesChanged.ts index e55f05251..0dbe440ed 100644 --- a/webclient/src/websocket/events/game/playerPropertiesChanged.ts +++ b/webclient/src/websocket/events/game/playerPropertiesChanged.ts @@ -1,6 +1,7 @@ -import { GameEventMeta, PlayerProperties } from 'types'; +import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function playerPropertiesChanged(data: { playerProperties: PlayerProperties }, meta: GameEventMeta): void { +export function playerPropertiesChanged(data: { playerProperties: ServerInfo_PlayerProperties }, meta: GameEventMeta): void { GamePersistence.playerPropertiesChanged(meta.gameId, meta.playerId, data.playerProperties); } diff --git a/webclient/src/websocket/events/game/revealCards.ts b/webclient/src/websocket/events/game/revealCards.ts index 1402f3b87..3837786f5 100644 --- a/webclient/src/websocket/events/game/revealCards.ts +++ b/webclient/src/websocket/events/game/revealCards.ts @@ -1,6 +1,7 @@ -import { GameEventMeta, RevealCardsData } from 'types'; +import type { Event_RevealCards } from 'generated/proto/event_reveal_cards_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function revealCards(data: RevealCardsData, meta: GameEventMeta): void { +export function revealCards(data: Event_RevealCards, meta: GameEventMeta): void { GamePersistence.cardsRevealed(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/reverseTurn.ts b/webclient/src/websocket/events/game/reverseTurn.ts index d7194890a..8716612e3 100644 --- a/webclient/src/websocket/events/game/reverseTurn.ts +++ b/webclient/src/websocket/events/game/reverseTurn.ts @@ -1,6 +1,7 @@ -import { GameEventMeta, ReverseTurnData } from 'types'; +import type { Event_ReverseTurn } from 'generated/proto/event_reverse_turn_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function reverseTurn(data: ReverseTurnData, meta: GameEventMeta): void { +export function reverseTurn(data: Event_ReverseTurn, meta: GameEventMeta): void { GamePersistence.turnReversed(meta.gameId, data.reversed); } diff --git a/webclient/src/websocket/events/game/rollDie.ts b/webclient/src/websocket/events/game/rollDie.ts index 27b00d395..4da34387d 100644 --- a/webclient/src/websocket/events/game/rollDie.ts +++ b/webclient/src/websocket/events/game/rollDie.ts @@ -1,6 +1,7 @@ -import { GameEventMeta, RollDieData } from 'types'; +import type { Event_RollDie } from 'generated/proto/event_roll_die_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function rollDie(data: RollDieData, meta: GameEventMeta): void { +export function rollDie(data: Event_RollDie, meta: GameEventMeta): void { GamePersistence.dieRolled(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/setActivePhase.ts b/webclient/src/websocket/events/game/setActivePhase.ts index 013250088..5981b97bc 100644 --- a/webclient/src/websocket/events/game/setActivePhase.ts +++ b/webclient/src/websocket/events/game/setActivePhase.ts @@ -1,6 +1,7 @@ -import { GameEventMeta, SetActivePhaseData } from 'types'; +import type { Event_SetActivePhase } from 'generated/proto/event_set_active_phase_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function setActivePhase(data: SetActivePhaseData, meta: GameEventMeta): void { +export function setActivePhase(data: Event_SetActivePhase, meta: GameEventMeta): void { GamePersistence.activePhaseSet(meta.gameId, data.phase); } diff --git a/webclient/src/websocket/events/game/setActivePlayer.ts b/webclient/src/websocket/events/game/setActivePlayer.ts index 878fab9f6..0f529b6e4 100644 --- a/webclient/src/websocket/events/game/setActivePlayer.ts +++ b/webclient/src/websocket/events/game/setActivePlayer.ts @@ -1,6 +1,7 @@ -import { GameEventMeta, SetActivePlayerData } from 'types'; +import type { Event_SetActivePlayer } from 'generated/proto/event_set_active_player_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function setActivePlayer(data: SetActivePlayerData, meta: GameEventMeta): void { +export function setActivePlayer(data: Event_SetActivePlayer, meta: GameEventMeta): void { GamePersistence.activePlayerSet(meta.gameId, data.activePlayerId); } diff --git a/webclient/src/websocket/events/game/setCardAttr.ts b/webclient/src/websocket/events/game/setCardAttr.ts index 3461f9cc0..aa15043fb 100644 --- a/webclient/src/websocket/events/game/setCardAttr.ts +++ b/webclient/src/websocket/events/game/setCardAttr.ts @@ -1,6 +1,7 @@ -import { GameEventMeta, SetCardAttrData } from 'types'; +import type { Event_SetCardAttr } from 'generated/proto/event_set_card_attr_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function setCardAttr(data: SetCardAttrData, meta: GameEventMeta): void { +export function setCardAttr(data: Event_SetCardAttr, meta: GameEventMeta): void { GamePersistence.cardAttrChanged(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/setCardCounter.ts b/webclient/src/websocket/events/game/setCardCounter.ts index 364a30585..e595b65a7 100644 --- a/webclient/src/websocket/events/game/setCardCounter.ts +++ b/webclient/src/websocket/events/game/setCardCounter.ts @@ -1,6 +1,7 @@ -import { GameEventMeta, SetCardCounterData } from 'types'; +import type { Event_SetCardCounter } from 'generated/proto/event_set_card_counter_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function setCardCounter(data: SetCardCounterData, meta: GameEventMeta): void { +export function setCardCounter(data: Event_SetCardCounter, meta: GameEventMeta): void { GamePersistence.cardCounterChanged(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/setCounter.ts b/webclient/src/websocket/events/game/setCounter.ts index b729c1718..a7e6863ae 100644 --- a/webclient/src/websocket/events/game/setCounter.ts +++ b/webclient/src/websocket/events/game/setCounter.ts @@ -1,6 +1,7 @@ -import { GameEventMeta, SetCounterData } from 'types'; +import type { Event_SetCounter } from 'generated/proto/event_set_counter_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function setCounter(data: SetCounterData, meta: GameEventMeta): void { +export function setCounter(data: Event_SetCounter, meta: GameEventMeta): void { GamePersistence.counterSet(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/shuffle.ts b/webclient/src/websocket/events/game/shuffle.ts index f94703555..a1ec8ba05 100644 --- a/webclient/src/websocket/events/game/shuffle.ts +++ b/webclient/src/websocket/events/game/shuffle.ts @@ -1,6 +1,7 @@ -import { GameEventMeta, ShuffleData } from 'types'; +import type { Event_Shuffle } from 'generated/proto/event_shuffle_pb'; +import type { GameEventMeta } from 'types'; import { GamePersistence } from '../../persistence'; -export function shuffle(data: ShuffleData, meta: GameEventMeta): void { +export function shuffle(data: Event_Shuffle, meta: GameEventMeta): void { GamePersistence.zoneShuffled(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/persistence/GamePersistence.ts b/webclient/src/websocket/persistence/GamePersistence.ts index 16eced49e..e21a9db45 100644 --- a/webclient/src/websocket/persistence/GamePersistence.ts +++ b/webclient/src/websocket/persistence/GamePersistence.ts @@ -1,33 +1,31 @@ import { GameDispatch } from 'store'; -import { - AttachCardData, - ChangeZonePropertiesData, - CreateArrowData, - CreateCounterData, - CreateTokenData, - DelCounterData, - DeleteArrowData, - DestroyCardData, - DrawCardsData, - DumpZoneData, - FlipCardData, - GameStateChangedData, - MoveCardData, - PlayerProperties, - RevealCardsData, - RollDieData, - SetCardAttrData, - SetCardCounterData, - SetCounterData, - ShuffleData, -} from 'types'; +import type { Event_AttachCard } from 'generated/proto/event_attach_card_pb'; +import type { Event_ChangeZoneProperties } from 'generated/proto/event_change_zone_properties_pb'; +import type { Event_CreateArrow } from 'generated/proto/event_create_arrow_pb'; +import type { Event_CreateCounter } from 'generated/proto/event_create_counter_pb'; +import type { Event_CreateToken } from 'generated/proto/event_create_token_pb'; +import type { Event_DelCounter } from 'generated/proto/event_del_counter_pb'; +import type { Event_DeleteArrow } from 'generated/proto/event_delete_arrow_pb'; +import type { Event_DestroyCard } from 'generated/proto/event_destroy_card_pb'; +import type { Event_DrawCards } from 'generated/proto/event_draw_cards_pb'; +import type { Event_DumpZone } from 'generated/proto/event_dump_zone_pb'; +import type { Event_FlipCard } from 'generated/proto/event_flip_card_pb'; +import type { Event_GameStateChanged } from 'generated/proto/event_game_state_changed_pb'; +import type { Event_MoveCard } from 'generated/proto/event_move_card_pb'; +import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb'; +import type { Event_RevealCards } from 'generated/proto/event_reveal_cards_pb'; +import type { Event_RollDie } from 'generated/proto/event_roll_die_pb'; +import type { Event_SetCardAttr } from 'generated/proto/event_set_card_attr_pb'; +import type { Event_SetCardCounter } from 'generated/proto/event_set_card_counter_pb'; +import type { Event_SetCounter } from 'generated/proto/event_set_counter_pb'; +import type { Event_Shuffle } from 'generated/proto/event_shuffle_pb'; export class GamePersistence { - static gameStateChanged(gameId: number, data: GameStateChangedData): void { + static gameStateChanged(gameId: number, data: Event_GameStateChanged): void { GameDispatch.gameStateChanged(gameId, data); } - static playerJoined(gameId: number, playerProperties: PlayerProperties): void { + static playerJoined(gameId: number, playerProperties: ServerInfo_PlayerProperties): void { GameDispatch.playerJoined(gameId, playerProperties); } @@ -35,7 +33,7 @@ export class GamePersistence { GameDispatch.playerLeft(gameId, playerId, reason); } - static playerPropertiesChanged(gameId: number, playerId: number, properties: PlayerProperties): void { + static playerPropertiesChanged(gameId: number, playerId: number, properties: ServerInfo_PlayerProperties): void { GameDispatch.playerPropertiesChanged(gameId, playerId, properties); } @@ -55,67 +53,67 @@ export class GamePersistence { GameDispatch.gameSay(gameId, playerId, message); } - static cardMoved(gameId: number, playerId: number, data: MoveCardData): void { + static cardMoved(gameId: number, playerId: number, data: Event_MoveCard): void { GameDispatch.cardMoved(gameId, playerId, data); } - static cardFlipped(gameId: number, playerId: number, data: FlipCardData): void { + static cardFlipped(gameId: number, playerId: number, data: Event_FlipCard): void { GameDispatch.cardFlipped(gameId, playerId, data); } - static cardDestroyed(gameId: number, playerId: number, data: DestroyCardData): void { + static cardDestroyed(gameId: number, playerId: number, data: Event_DestroyCard): void { GameDispatch.cardDestroyed(gameId, playerId, data); } - static cardAttached(gameId: number, playerId: number, data: AttachCardData): void { + static cardAttached(gameId: number, playerId: number, data: Event_AttachCard): void { GameDispatch.cardAttached(gameId, playerId, data); } - static tokenCreated(gameId: number, playerId: number, data: CreateTokenData): void { + static tokenCreated(gameId: number, playerId: number, data: Event_CreateToken): void { GameDispatch.tokenCreated(gameId, playerId, data); } - static cardAttrChanged(gameId: number, playerId: number, data: SetCardAttrData): void { + static cardAttrChanged(gameId: number, playerId: number, data: Event_SetCardAttr): void { GameDispatch.cardAttrChanged(gameId, playerId, data); } - static cardCounterChanged(gameId: number, playerId: number, data: SetCardCounterData): void { + static cardCounterChanged(gameId: number, playerId: number, data: Event_SetCardCounter): void { GameDispatch.cardCounterChanged(gameId, playerId, data); } - static arrowCreated(gameId: number, playerId: number, data: CreateArrowData): void { + static arrowCreated(gameId: number, playerId: number, data: Event_CreateArrow): void { GameDispatch.arrowCreated(gameId, playerId, data); } - static arrowDeleted(gameId: number, playerId: number, data: DeleteArrowData): void { + static arrowDeleted(gameId: number, playerId: number, data: Event_DeleteArrow): void { GameDispatch.arrowDeleted(gameId, playerId, data); } - static counterCreated(gameId: number, playerId: number, data: CreateCounterData): void { + static counterCreated(gameId: number, playerId: number, data: Event_CreateCounter): void { GameDispatch.counterCreated(gameId, playerId, data); } - static counterSet(gameId: number, playerId: number, data: SetCounterData): void { + static counterSet(gameId: number, playerId: number, data: Event_SetCounter): void { GameDispatch.counterSet(gameId, playerId, data); } - static counterDeleted(gameId: number, playerId: number, data: DelCounterData): void { + static counterDeleted(gameId: number, playerId: number, data: Event_DelCounter): void { GameDispatch.counterDeleted(gameId, playerId, data); } - static cardsDrawn(gameId: number, playerId: number, data: DrawCardsData): void { + static cardsDrawn(gameId: number, playerId: number, data: Event_DrawCards): void { GameDispatch.cardsDrawn(gameId, playerId, data); } - static cardsRevealed(gameId: number, playerId: number, data: RevealCardsData): void { + static cardsRevealed(gameId: number, playerId: number, data: Event_RevealCards): void { GameDispatch.cardsRevealed(gameId, playerId, data); } - static zoneShuffled(gameId: number, playerId: number, data: ShuffleData): void { + static zoneShuffled(gameId: number, playerId: number, data: Event_Shuffle): void { GameDispatch.zoneShuffled(gameId, playerId, data); } - static dieRolled(gameId: number, playerId: number, data: RollDieData): void { + static dieRolled(gameId: number, playerId: number, data: Event_RollDie): void { GameDispatch.dieRolled(gameId, playerId, data); } @@ -131,11 +129,11 @@ export class GamePersistence { GameDispatch.turnReversed(gameId, reversed); } - static zoneDumped(gameId: number, playerId: number, data: DumpZoneData): void { + static zoneDumped(gameId: number, playerId: number, data: Event_DumpZone): void { GameDispatch.zoneDumped(gameId, playerId, data); } - static zonePropertiesChanged(gameId: number, playerId: number, data: ChangeZonePropertiesData): void { + static zonePropertiesChanged(gameId: number, playerId: number, data: Event_ChangeZoneProperties): void { GameDispatch.zonePropertiesChanged(gameId, playerId, data); } } diff --git a/webclient/src/websocket/persistence/ModeratorPersistence.ts b/webclient/src/websocket/persistence/ModeratorPersistence.ts index f8993451a..bb50fe726 100644 --- a/webclient/src/websocket/persistence/ModeratorPersistence.ts +++ b/webclient/src/websocket/persistence/ModeratorPersistence.ts @@ -1,24 +1,27 @@ import { ServerDispatch } from 'store'; -import { BanHistoryItem, LogItem, WarnHistoryItem, WarnListItem } from 'types'; +import type { ServerInfo_Ban } from 'generated/proto/serverinfo_ban_pb'; +import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb'; +import type { ServerInfo_Warning } from 'generated/proto/serverinfo_warning_pb'; +import type { Response_WarnList } from 'generated/proto/response_warn_list_pb'; export class ModeratorPersistence { static banFromServer(userName: string): void { ServerDispatch.banFromServer(userName); } - static banHistory(userName: string, banHistory: BanHistoryItem[]): void { + static banHistory(userName: string, banHistory: ServerInfo_Ban[]): void { ServerDispatch.banHistory(userName, banHistory); } - static viewLogs(logs: LogItem[]): void { + static viewLogs(logs: ServerInfo_ChatMessage[]): void { ServerDispatch.viewLogs(logs); } - static warnHistory(userName: string, warnHistory: WarnHistoryItem[]): void { + static warnHistory(userName: string, warnHistory: ServerInfo_Warning[]): void { ServerDispatch.warnHistory(userName, warnHistory); } - static warnListOptions(warnList: WarnListItem[]): void { + static warnListOptions(warnList: Response_WarnList[]): void { ServerDispatch.warnListOptions(warnList); } diff --git a/webclient/src/websocket/persistence/RoomPersistence.ts b/webclient/src/websocket/persistence/RoomPersistence.ts index e3774e0ae..6325bc20a 100644 --- a/webclient/src/websocket/persistence/RoomPersistence.ts +++ b/webclient/src/websocket/persistence/RoomPersistence.ts @@ -1,5 +1,6 @@ import { RoomsDispatch } from 'store'; -import { Message, User } from 'types'; +import { Message } from 'types'; +import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; @@ -35,7 +36,7 @@ export class RoomPersistence { RoomsDispatch.addMessage(roomId, message); } - static userJoined(roomId: number, user: User) { + static userJoined(roomId: number, user: ServerInfo_User) { RoomsDispatch.userJoined(roomId, user); } diff --git a/webclient/src/websocket/persistence/SessionPersistence.ts b/webclient/src/websocket/persistence/SessionPersistence.ts index fcf670163..84b5f7b1c 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.ts @@ -1,5 +1,9 @@ import { GameDispatch, ServerDispatch } from 'store'; -import { DeckList, DeckStorageTreeItem, ReplayMatch, StatusEnum, User, WebSocketConnectOptions } from 'types'; +import { StatusEnum, WebSocketConnectOptions } from 'types'; +import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; +import type { Response_DeckList } from 'generated/proto/response_deck_list_pb'; +import type { ServerInfo_DeckStorage_TreeItem } from 'generated/proto/serverinfo_deckstorage_pb'; +import type { ServerInfo_ReplayMatch } from 'generated/proto/serverinfo_replay_match_pb'; import { GameEntry } from 'store/game/game.interfaces'; import { sanitizeHtml } from 'websocket/utils'; import { @@ -51,11 +55,11 @@ export class SessionPersistence { ServerDispatch.testConnectionFailed(); } - static updateBuddyList(buddyList: User[]) { + static updateBuddyList(buddyList: ServerInfo_User[]) { ServerDispatch.updateBuddyList(buddyList); } - static addToBuddyList(user: User) { + static addToBuddyList(user: ServerInfo_User) { ServerDispatch.addToBuddyList(user); } @@ -63,11 +67,11 @@ export class SessionPersistence { ServerDispatch.removeFromBuddyList(userName); } - static updateIgnoreList(ignoreList: User[]) { + static updateIgnoreList(ignoreList: ServerInfo_User[]) { ServerDispatch.updateIgnoreList(ignoreList); } - static addToIgnoreList(user: User) { + static addToIgnoreList(user: ServerInfo_User) { ServerDispatch.addToIgnoreList(user); } @@ -87,15 +91,15 @@ export class SessionPersistence { } } - static updateUser(user: User) { + static updateUser(user: ServerInfo_User) { ServerDispatch.updateUser(user); } - static updateUsers(users: User[]) { + static updateUsers(users: ServerInfo_User[]) { ServerDispatch.updateUsers(users); } - static userJoined(user: User) { + static userJoined(user: ServerInfo_User) { ServerDispatch.userJoined(user); } @@ -171,7 +175,7 @@ export class SessionPersistence { ServerDispatch.accountImageChanged({ avatarBmp }); } - static getUserInfo(userInfo: User) { + static getUserInfo(userInfo: ServerInfo_User) { ServerDispatch.getUserInfo(userInfo); } @@ -238,11 +242,11 @@ export class SessionPersistence { ServerDispatch.deckDelete(deckId); } - static updateServerDecks(deckList: DeckList): void { + static updateServerDecks(deckList: Response_DeckList): void { ServerDispatch.backendDecks(deckList); } - static uploadServerDeck(path: string, treeItem: DeckStorageTreeItem): void { + static uploadServerDeck(path: string, treeItem: ServerInfo_DeckStorage_TreeItem): void { ServerDispatch.deckUpload(path, treeItem); } @@ -254,11 +258,11 @@ export class SessionPersistence { ServerDispatch.deckDelDir(path); } - static replayList(matchList: ReplayMatch[]): void { + static replayList(matchList: ServerInfo_ReplayMatch[]): void { ServerDispatch.replayList(matchList); } - static replayAdded(matchInfo: ReplayMatch): void { + static replayAdded(matchInfo: ServerInfo_ReplayMatch): void { ServerDispatch.replayAdded(matchInfo); } From c62c336a110b26d6221ef3778a1af511e09e94dc Mon Sep 17 00:00:00 2001 From: seavor Date: Wed, 15 Apr 2026 15:46:17 -0500 Subject: [PATCH 14/38] refactor typescript wiring --- webclient/.gitignore | 3 + webclient/CLAUDE.md | 170 ++++++++++ webclient/buf.gen.plugin.mjs | 99 ++++++ webclient/buf.gen.yaml | 4 + webclient/src/api/AdminService.spec.ts | 6 +- webclient/src/api/AdminService.tsx | 2 +- .../src/api/AuthenticationService.spec.ts | 110 ++++-- webclient/src/api/AuthenticationService.tsx | 39 ++- webclient/src/api/ModeratorService.spec.ts | 10 +- webclient/src/api/ModeratorService.tsx | 6 +- webclient/src/api/RoomsService.spec.ts | 6 +- webclient/src/api/RoomsService.tsx | 2 +- webclient/src/api/SessionService.spec.ts | 6 +- webclient/src/api/SessionService.tsx | 2 +- webclient/src/components/Card/Card.tsx | 2 +- .../components/CardDetails/CardDetails.tsx | 2 +- .../CountryDropdown/CountryDropdown.tsx | 8 +- webclient/src/components/Guard/AuthGuard.tsx | 10 +- webclient/src/components/Guard/ModGuard.tsx | 10 +- .../components/InputAction/InputAction.tsx | 2 +- .../src/components/KnownHosts/KnownHosts.tsx | 74 +++-- .../LanguageDropdown/LanguageDropdown.tsx | 14 +- .../src/components/Message/CardCallout.tsx | 2 +- webclient/src/components/Message/Message.tsx | 33 +- webclient/src/components/Token/Token.tsx | 2 +- .../components/TokenDetails/TokenDetails.tsx | 2 +- .../components/UserDisplay/UserDisplay.tsx | 17 +- webclient/src/components/index.ts | 3 + webclient/src/containers/Account/Account.tsx | 10 +- .../src/containers/Account/AddToBuddies.tsx | 2 +- .../src/containers/Account/AddToIgnore.tsx | 2 +- webclient/src/containers/App/AppShell.tsx | 4 +- .../src/containers/App/AppShellRoutes.tsx | 22 +- .../src/containers/App/FeatureDetection.tsx | 6 +- webclient/src/containers/Decks/Decks.tsx | 4 +- webclient/src/containers/Game/Game.tsx | 4 +- .../src/containers/Initialize/Initialize.tsx | 12 +- webclient/src/containers/Layout/LeftNav.tsx | 26 +- webclient/src/containers/Login/Login.tsx | 49 +-- webclient/src/containers/Logs/Logs.tsx | 14 +- webclient/src/containers/Player/Player.tsx | 4 +- webclient/src/containers/Room/Games.tsx | 6 +- webclient/src/containers/Room/Messages.tsx | 2 +- webclient/src/containers/Room/OpenGames.tsx | 6 +- webclient/src/containers/Room/Room.tsx | 14 +- webclient/src/containers/Room/SayMessage.tsx | 2 +- webclient/src/containers/Server/Rooms.tsx | 6 +- webclient/src/containers/Server/Server.tsx | 14 +- .../containers/Unsupported/Unsupported.tsx | 2 +- .../AccountActivationDialog.tsx | 2 +- .../CardImportDialog/CardImportDialog.tsx | 2 +- .../KnownHostDialog/KnownHostDialog.tsx | 2 +- .../RegistrationDialog/RegistrationDialog.tsx | 2 +- .../RequestPasswordResetDialog.tsx | 2 +- .../ResetPasswordDialog.tsx | 2 +- .../AccountActivationForm.tsx | 6 +- .../forms/CardImportForm/CardImportForm.tsx | 4 +- .../src/forms/KnownHostForm/KnownHostForm.tsx | 2 +- webclient/src/forms/LoginForm/LoginForm.tsx | 13 +- .../src/forms/RegisterForm/RegisterForm.tsx | 8 +- .../RequestPasswordResetForm.tsx | 6 +- .../ResetPasswordForm/ResetPasswordForm.tsx | 6 +- webclient/src/forms/SearchForm/SearchForm.tsx | 2 +- webclient/src/hooks/useAutoConnect.ts | 8 +- webclient/src/i18n-backend.ts | 6 +- webclient/src/i18n.ts | 6 +- webclient/src/index.tsx | 6 +- webclient/src/polyfills.ts | 19 ++ .../src/services/dexie/DexieDTOs/CardDTO.ts | 4 +- .../src/services/dexie/DexieDTOs/HostDTO.ts | 8 +- .../src/services/dexie/DexieDTOs/SetDTO.ts | 4 +- .../services/dexie/DexieDTOs/SettingDTO.ts | 4 +- .../src/services/dexie/DexieDTOs/TokenDTO.ts | 4 +- webclient/src/setupTests.ts | 37 ++- webclient/src/store/common/SortUtil.spec.ts | 65 ++-- webclient/src/store/common/SortUtil.ts | 31 +- .../src/store/common/normalizers.spec.ts | 33 +- webclient/src/store/common/normalizers.ts | 22 +- .../src/store/game/__mocks__/fixtures.ts | 34 +- webclient/src/store/game/game.actions.spec.ts | 63 ++-- webclient/src/store/game/game.actions.ts | 69 ++-- .../src/store/game/game.dispatch.spec.ts | 71 ++-- webclient/src/store/game/game.dispatch.ts | 70 ++-- webclient/src/store/game/game.interfaces.ts | 13 +- webclient/src/store/game/game.reducer.spec.ts | 37 ++- webclient/src/store/game/game.reducer.ts | 73 ++-- webclient/src/store/game/game.selectors.ts | 4 +- webclient/src/store/index.ts | 8 +- .../store/rooms/__mocks__/rooms-fixtures.ts | 42 +-- .../src/store/rooms/rooms.actions.spec.ts | 8 +- webclient/src/store/rooms/rooms.actions.tsx | 17 +- .../src/store/rooms/rooms.dispatch.spec.ts | 12 +- webclient/src/store/rooms/rooms.dispatch.tsx | 19 +- .../src/store/rooms/rooms.interfaces.tsx | 16 +- .../src/store/rooms/rooms.reducer.spec.ts | 13 +- webclient/src/store/rooms/rooms.reducer.tsx | 22 +- .../store/server/__mocks__/server-fixtures.ts | 102 +++--- .../src/store/server/server.actions.spec.ts | 31 +- webclient/src/store/server/server.actions.ts | 70 ++-- .../src/store/server/server.dispatch.spec.ts | 42 +-- webclient/src/store/server/server.dispatch.ts | 71 ++-- .../src/store/server/server.interfaces.ts | 98 ++---- .../src/store/server/server.reducer.spec.ts | 96 +++--- webclient/src/store/server/server.reducer.ts | 82 ++--- .../src/store/server/server.selectors.spec.ts | 10 +- webclient/src/store/server/server.types.ts | 1 - webclient/src/types/app.ts | 8 + webclient/src/types/data.ts | 1 + webclient/src/types/enriched.ts | 145 ++++++++ webclient/src/types/game.ts | 123 ------- webclient/src/types/index.ts | 17 +- webclient/src/types/logs.ts | 10 - webclient/src/types/message.ts | 5 - webclient/src/types/room.ts | 10 - webclient/src/types/server.ts | 25 -- webclient/src/types/sort.ts | 12 +- webclient/src/types/user.ts | 3 - webclient/src/types/utilities.ts | 8 - webclient/src/websocket/WebClient.spec.ts | 33 +- webclient/src/websocket/WebClient.ts | 16 +- .../src/websocket/commands/admin/adjustMod.ts | 16 +- .../commands/admin/adminCommands.spec.ts | 2 - .../websocket/commands/admin/reloadConfig.ts | 4 +- .../commands/admin/shutdownServer.ts | 4 +- .../commands/admin/updateServerMessage.ts | 4 +- .../src/websocket/commands/game/attachCard.ts | 8 +- .../commands/game/changeZoneProperties.ts | 12 +- .../src/websocket/commands/game/concede.ts | 4 +- .../websocket/commands/game/createArrow.ts | 8 +- .../websocket/commands/game/createCounter.ts | 8 +- .../websocket/commands/game/createToken.ts | 8 +- .../src/websocket/commands/game/deckSelect.ts | 8 +- .../src/websocket/commands/game/delCounter.ts | 8 +- .../websocket/commands/game/deleteArrow.ts | 8 +- .../src/websocket/commands/game/drawCards.ts | 8 +- .../src/websocket/commands/game/dumpZone.ts | 8 +- .../src/websocket/commands/game/flipCard.ts | 8 +- .../commands/game/gameCommands.spec.ts | 114 +++---- .../src/websocket/commands/game/gameSay.ts | 8 +- .../websocket/commands/game/incCardCounter.ts | 8 +- .../src/websocket/commands/game/incCounter.ts | 8 +- .../src/websocket/commands/game/judge.ts | 7 +- .../websocket/commands/game/kickFromGame.ts | 8 +- .../src/websocket/commands/game/leaveGame.ts | 4 +- .../src/websocket/commands/game/moveCard.ts | 8 +- .../src/websocket/commands/game/mulligan.ts | 8 +- .../src/websocket/commands/game/nextTurn.ts | 4 +- .../src/websocket/commands/game/readyStart.ts | 8 +- .../websocket/commands/game/revealCards.ts | 8 +- .../websocket/commands/game/reverseTurn.ts | 4 +- .../websocket/commands/game/setActivePhase.ts | 8 +- .../websocket/commands/game/setCardAttr.ts | 8 +- .../websocket/commands/game/setCardCounter.ts | 8 +- .../src/websocket/commands/game/setCounter.ts | 8 +- .../commands/game/setSideboardLock.ts | 8 +- .../commands/game/setSideboardPlan.ts | 8 +- .../src/websocket/commands/game/shuffle.ts | 8 +- .../src/websocket/commands/game/unconcede.ts | 4 +- .../src/websocket/commands/game/undoDraw.ts | 4 +- .../commands/moderator/banFromServer.ts | 5 +- .../commands/moderator/forceActivateUser.ts | 9 +- .../commands/moderator/getAdminNotes.ts | 8 +- .../commands/moderator/getBanHistory.ts | 8 +- .../commands/moderator/getWarnHistory.ts | 8 +- .../commands/moderator/getWarnList.ts | 20 +- .../commands/moderator/grantReplayAccess.ts | 9 +- .../moderator/moderatorCommands.spec.ts | 59 ++-- .../commands/moderator/updateAdminNotes.ts | 19 +- .../commands/moderator/viewLogHistory.ts | 14 +- .../websocket/commands/moderator/warnUser.ts | 7 +- .../src/websocket/commands/room/createGame.ts | 10 +- .../src/websocket/commands/room/joinGame.ts | 10 +- .../src/websocket/commands/room/leaveRoom.ts | 5 +- .../commands/room/roomCommands.spec.ts | 23 +- .../src/websocket/commands/room/roomSay.ts | 4 +- .../websocket/commands/session/accountEdit.ts | 7 +- .../commands/session/accountImage.ts | 5 +- .../commands/session/accountPassword.ts | 7 +- .../websocket/commands/session/activate.ts | 23 +- .../websocket/commands/session/addToList.ts | 5 +- .../src/websocket/commands/session/connect.ts | 35 +- .../src/websocket/commands/session/deckDel.ts | 5 +- .../websocket/commands/session/deckDelDir.ts | 5 +- .../websocket/commands/session/deckList.ts | 8 +- .../websocket/commands/session/deckNewDir.ts | 5 +- .../websocket/commands/session/deckUpload.ts | 8 +- .../session/forgotPasswordChallenge.ts | 17 +- .../commands/session/forgotPasswordRequest.ts | 22 +- .../commands/session/forgotPasswordReset.ts | 23 +- .../commands/session/getGamesOfUser.ts | 8 +- .../websocket/commands/session/getUserInfo.ts | 8 +- .../websocket/commands/session/joinRoom.ts | 8 +- .../websocket/commands/session/listRooms.ts | 4 +- .../websocket/commands/session/listUsers.ts | 8 +- .../src/websocket/commands/session/login.ts | 46 +-- .../src/websocket/commands/session/message.ts | 4 +- .../src/websocket/commands/session/ping.ts | 4 +- .../websocket/commands/session/register.ts | 55 +-- .../commands/session/removeFromList.ts | 5 +- .../commands/session/replayDeleteMatch.ts | 5 +- .../commands/session/replayGetCode.ts | 7 +- .../websocket/commands/session/replayList.ts | 8 +- .../commands/session/replayModifyMatch.ts | 17 +- .../commands/session/replaySubmitCode.ts | 4 +- .../commands/session/requestPasswordSalt.ts | 36 +- .../session/sessionCommands-complex.spec.ts | 313 ++++++++++-------- .../session/sessionCommands-simple.spec.ts | 147 ++------ .../commands/session/updateStatus.ts | 4 +- .../src/websocket/events/common/index.ts | 2 +- .../src/websocket/events/game/attachCard.ts | 5 +- .../events/game/changeZoneProperties.ts | 5 +- .../src/websocket/events/game/createArrow.ts | 5 +- .../websocket/events/game/createCounter.ts | 5 +- .../src/websocket/events/game/createToken.ts | 5 +- .../src/websocket/events/game/delCounter.ts | 5 +- .../src/websocket/events/game/deleteArrow.ts | 5 +- .../src/websocket/events/game/destroyCard.ts | 5 +- .../src/websocket/events/game/drawCards.ts | 5 +- .../src/websocket/events/game/dumpZone.ts | 5 +- .../src/websocket/events/game/flipCard.ts | 5 +- .../src/websocket/events/game/gameClosed.ts | 4 +- .../websocket/events/game/gameEvents.spec.ts | 59 ++-- .../websocket/events/game/gameHostChanged.ts | 4 +- .../src/websocket/events/game/gameSay.ts | 5 +- .../websocket/events/game/gameStateChanged.ts | 5 +- webclient/src/websocket/events/game/index.ts | 102 +++--- .../src/websocket/events/game/joinGame.ts | 5 +- webclient/src/websocket/events/game/kicked.ts | 4 +- .../src/websocket/events/game/leaveGame.ts | 4 +- .../src/websocket/events/game/moveCard.ts | 5 +- .../events/game/playerPropertiesChanged.ts | 5 +- .../src/websocket/events/game/revealCards.ts | 5 +- .../src/websocket/events/game/reverseTurn.ts | 5 +- .../src/websocket/events/game/rollDie.ts | 5 +- .../websocket/events/game/setActivePhase.ts | 5 +- .../websocket/events/game/setActivePlayer.ts | 5 +- .../src/websocket/events/game/setCardAttr.ts | 5 +- .../websocket/events/game/setCardCounter.ts | 5 +- .../src/websocket/events/game/setCounter.ts | 5 +- .../src/websocket/events/game/shuffle.ts | 5 +- webclient/src/websocket/events/room/index.ts | 29 +- .../src/websocket/events/room/interfaces.ts | 13 - .../src/websocket/events/room/joinRoom.ts | 4 +- .../src/websocket/events/room/leaveRoom.ts | 4 +- .../src/websocket/events/room/listGames.ts | 4 +- .../websocket/events/room/removeMessages.ts | 4 +- .../websocket/events/room/roomEvents.spec.ts | 21 +- .../src/websocket/events/room/roomSay.ts | 8 +- .../src/websocket/events/session/addToList.ts | 4 +- .../events/session/connectionClosed.ts | 24 +- .../websocket/events/session/gameJoined.ts | 4 +- .../src/websocket/events/session/index.ts | 57 ++-- .../websocket/events/session/interfaces.ts | 31 -- .../src/websocket/events/session/listRooms.ts | 4 +- .../websocket/events/session/notifyUser.ts | 5 +- .../events/session/removeFromList.ts | 4 +- .../websocket/events/session/replayAdded.ts | 4 +- .../events/session/serverCompleteList.ts | 4 +- .../events/session/serverIdentification.ts | 104 +++--- .../websocket/events/session/serverMessage.ts | 4 +- .../events/session/serverShutdown.ts | 5 +- .../events/session/sessionEvents.spec.ts | 192 ++++++----- .../websocket/events/session/userJoined.ts | 4 +- .../src/websocket/events/session/userLeft.ts | 4 +- .../websocket/events/session/userMessage.ts | 6 +- .../persistence/AdminPersistence.spec.ts | 8 +- .../websocket/persistence/AdminPersistence.ts | 2 +- .../persistence/GamePersistence.spec.ts | 69 ++-- .../websocket/persistence/GamePersistence.ts | 65 ++-- .../persistence/ModeratorPersistence.spec.ts | 12 +- .../persistence/ModeratorPersistence.ts | 15 +- .../persistence/RoomPersistence.spec.ts | 26 +- .../websocket/persistence/RoomPersistence.ts | 24 +- .../persistence/SessionPersistence.spec.ts | 107 +++--- .../persistence/SessionPersistence.ts | 101 ++---- .../services/KeepAliveService.spec.ts | 11 +- .../services/ProtobufService.spec.ts | 251 +++++++------- .../src/websocket/services/ProtobufService.ts | 69 ++-- .../services/WebSocketService.spec.ts | 56 ++-- .../websocket/services/WebSocketService.ts | 14 +- .../services/command-options.spec.ts | 33 +- .../src/websocket/services/command-options.ts | 16 +- .../src/websocket/services/protobuf-types.ts | 44 --- .../websocket/utils/passwordHasher.spec.ts | 2 +- .../src/websocket/utils/passwordHasher.ts | 4 +- webclient/tsconfig.json | 18 +- 286 files changed, 2999 insertions(+), 3053 deletions(-) create mode 100644 webclient/CLAUDE.md create mode 100644 webclient/buf.gen.plugin.mjs create mode 100644 webclient/src/polyfills.ts create mode 100644 webclient/src/types/app.ts create mode 100644 webclient/src/types/data.ts create mode 100644 webclient/src/types/enriched.ts delete mode 100644 webclient/src/types/game.ts delete mode 100644 webclient/src/types/logs.ts delete mode 100644 webclient/src/types/message.ts delete mode 100644 webclient/src/types/room.ts delete mode 100644 webclient/src/types/user.ts delete mode 100644 webclient/src/types/utilities.ts delete mode 100644 webclient/src/websocket/events/room/interfaces.ts delete mode 100644 webclient/src/websocket/events/session/interfaces.ts delete mode 100644 webclient/src/websocket/services/protobuf-types.ts diff --git a/webclient/.gitignore b/webclient/.gitignore index d0eec90c8..ed0268c3b 100644 --- a/webclient/.gitignore +++ b/webclient/.gitignore @@ -5,6 +5,9 @@ /.pnp .pnp.js +# AI generated docs +/plans + # generated ./src files /src/proto-files.json /src/server-props.json diff --git a/webclient/CLAUDE.md b/webclient/CLAUDE.md new file mode 100644 index 000000000..8c1b59078 --- /dev/null +++ b/webclient/CLAUDE.md @@ -0,0 +1,170 @@ +# CLAUDE.md + +Guidance for Claude Code when working inside `webclient/` — the React/TypeScript SPA (Webatrice) that connects to a Servatrice server over a WebSocket. It is a self-contained application; the only thing it shares with the rest of the repo (C++ desktop/server stack) is the protobuf protocol in `../libcockatrice_protocol/`. Anything outside `webclient/` is out-of-scope unless a task explicitly touches the protocol. + +All commands below are run from this directory. + +## Commands + +```bash +npm start # Vite dev server (runs proto:generate + prebuild.js first) +npm run build # production build (same prebuild hooks) +npm test # vitest run (one-shot) +npm run test:watch # vitest watch +npm run lint # eslint src/ +npm run lint:fix +npm run golden # lint + test — the CI-equivalent gate to run before declaring work done +npm run proto:generate # `npx buf generate` — regenerates TS bindings into src/generated/proto +``` + +Single test file: `npx vitest run path/to/file.spec.ts`. Filter by name: `npx vitest run -t "partial test name"`. + +The dev server has `server.open: true`, so `npm start` pops a browser tab automatically. + +## Architecture + +The webclient is a Redux Toolkit + RxJS app. Its defining abstraction is a layered WebSocket client that speaks the Cockatrice protobuf protocol to Servatrice. Understanding the layering is essential before editing anything under `src/websocket/`, `src/api/`, or `src/store/`. + +### Protocol layer + +- **`src/generated/proto/`** — auto-generated from `../libcockatrice_protocol/**/*.proto` by `buf` (see `buf.gen.yaml`). Never edit by hand. Runtime is `@bufbuild/protobuf` (Protobuf-ES); the codebase was recently migrated off the older `protobufjs`, so if you find any stray references to the old runtime, they're bugs. +- **`src/types/` is the only public surface for generated code.** `src/types/data.ts` hand-rolls an `export *` barrel over every proto file that consumers use, and `src/types/index.ts` re-exports it as `Data`, plus `Enriched` (protocol types extended with client-only fields) and `App` (pure client types). Import as `import { Data, Enriched } from '@app/types'` and use `Data.Command_Login`, `Data.ServerInfo_User`, etc. **Never import directly from `@app/generated/proto/*` outside `src/types/`.** When a new proto file starts being consumed, add an `export *` line to `src/types/data.ts` — there is a standing TODO to replace this rollup with a protobuf-es plugin. + +### WebSocket layer (`src/websocket/`) + +A strict inbound/outbound split sits on top of a transport core: + +- **`services/`** — transport: `WebSocketService` (socket lifecycle), `ProtobufService` (encode/decode + request/response correlation), `KeepAliveService` (ping/pong), `command-options` (per-command response config). This layer has no knowledge of Redux. +- **`commands/`** — *outbound*. Organised by scope (`session/`, `room/`, `game/`, `admin/`, `moderator/`). Each command builds a protobuf request and hands it to `ProtobufService.send{Session,Room,Game,Admin,Moderator}Command` along with a `CommandOptions` describing how to handle the response. +- **`events/`** — *inbound*. Handlers for server-pushed events, same scopes. They translate protobuf events into calls on the persistence layer. +- **`persistence/`** — the **only** bridge from the websocket layer into app state. `SessionPersistence`, `RoomPersistence`, `GamePersistence`, `AdminPersistence`, `ModeratorPersistence` dispatch Redux actions and/or write to Dexie. +- **`WebClient.ts`** — singleton facade that wires the services, commands, events, and persistence together. + +**Layering invariant (enforced on this branch, not aspirational):** + +1. Containers and components call `src/api/*` services — never `src/websocket/*` directly. +2. Commands and event handlers call `*Persistence` methods — never `store.dispatch` directly. +3. Only `*.dispatch.ts` helpers inside `src/store/` and persistence code may touch the Redux store. + +If you find yourself wanting to skip a layer (dispatching from an event handler, calling a command from a container, reaching into `src/generated/proto/` from a component), stop — the refactor on `webclient-websocket-layer` exists precisely to eliminate those shortcuts. There are currently zero violations; keep it that way. + +### ProtobufService: request/response correlation + +- Every outbound `CommandContainer` gets a monotonically increasing `cmdId` (cast to `BigInt` for the proto field). A `Map` stores the response handler keyed by that ID; when `ServerMessage.RESPONSE` arrives, `processServerResponse` looks up and invokes the callback, then deletes the entry. +- There is **no timeout or retry**. `resetCommands()` (called on reconnect) zeros `cmdId` and clears the pending map, silently dropping any in-flight callbacks. Code that needs reconnection resilience has to handle it at a higher layer. +- `sendCommand` is a no-op write if the transport isn't open — it still registers the callback, so a stale pending entry can accumulate until the next reset. +- Inbound event dispatch is extension-based: `processRoomEvent` / `processSessionEvent` / `processGameEvent` iterate `RoomEvents` / `SessionEvents` / `GameEvents` (tuples of `[extension, handler]`) and invoke the first handler whose extension is set on the message. Adding a new event handler means appending to those arrays. + +### command-options contract (`src/websocket/services/command-options.ts`) + +Every `send*Command` call accepts an optional `CommandOptions`: + +- `responseExt?: GenExtension` — the response payload extension to unwrap on success. +- `onSuccess?: (response: R, raw: Response) => void` — called when `responseCode === RespOk`. If `responseExt` is absent, the overload becomes `() => void`. +- `onResponseCode?: { [code: number]: (raw: Response) => void }` — per-error-code handlers. +- `onError?: (code: number, raw: Response) => void` — fallback for codes not in `onResponseCode`. +- `onResponse?: (raw: Response) => void` — if set, it handles the raw response and bypasses every other hook. Use this when you need the full response object regardless of code. + +If none of the hooks fire for a non-OK response, `handleResponse` logs the failure via `console.error` with the command's proto type name. The practical rule: `onSuccess` funnels into persistence, `onError` funnels into persistence (usually to flip connection state or show a toast), and `onResponse` is rare. + +### Public API for UI (`src/api/`) + +Thin service wrappers (`AuthenticationService`, `SessionService`, `RoomsService`, `GameService`, `ModeratorService`, `AdminService`) that expose websocket commands to UI code. A few things to know: + +- **All command methods are `static` and return `void`.** They're fire-and-forget — the response flows back through the `command-options` callbacks plumbed inside the command itself, into persistence, into the store. Don't try to await them. +- A handful of methods return `boolean` (e.g. `AuthenticationService.isConnected`, `isModerator`) — those are pure sync predicates, not command sends. +- Files use the `.tsx` extension even though they contain no JSX. That's a leftover convention; don't "fix" it. + +### State (`src/store/`) + +Redux Toolkit store (`store.ts`, `rootReducer.ts`) split by feature. Each slice follows the same file layout: + +- `*.actions.ts` — action creators +- `*.reducer.ts` — slice reducer +- `*.selectors.ts` — selectors (mostly plain getters; `createSelector` only for derived lists) +- `*.dispatch.ts` — dispatch helpers called by the persistence layer +- `*.interfaces.ts` / `*.types.ts` — state shape and enums + +Slices: `server/`, `rooms/`, `game/`, plus shared `actions/` and `common/` helpers (`SortUtil`, `normalizers`). Consumers import through the `@app/store` barrel — `GameSelectors`, `GameDispatch`, `GameTypes`, and the same prefixed set for `Server`/`Rooms`. **Don't deep-import from `src/store/game/game.selectors.ts` etc.** — go through `@app/store`. + +Shape notes worth knowing before you touch a reducer: + +- `game/` is deeply normalized: `games[gameId].players[playerId].zones[zoneName].cards`. Selectors are plain getters so lookups stay O(1); `createSelector` is reserved for the few that build derived lists (e.g. `getActiveGameIds`). +- Selectors return module-scope `EMPTY_ARRAY` / `EMPTY_OBJECT` constants for missing data to preserve referential equality and avoid spurious re-renders. +- `rooms/` is *partially* normalized: rooms are keyed by ID, but each room also carries denormalized `gameList` / `userList` arrays. Server updates often omit those lists, so the reducer merges new metadata while preserving the existing arrays. There is a standing TODO to clean this up. +- `server/` is mostly flat maps keyed by username (`messages`, `userInfo`, buddy/ignore lists) plus connection state. + +### Local persistence (`src/services/dexie/`) + +IndexedDB storage via Dexie for cards, sets, tokens, known hosts, and settings. DTOs live in `DexieDTOs/`. This is separate from the Redux store — used for data that should survive a reload (card database, user settings, host list). Dexie is not mocked in unit tests; code that writes to Dexie is typically exercised only in integration paths. + +### UI + +- **`containers/`** — route-level, Redux-connected. Top-level routes: `App`, `Initialize`, `Login`, `Server`, `Room`, `Game`, `Player`, `Decks`, `Account`, `Logs`, `Layout`, `Unsupported`. Routing lives in `containers/App/AppShellRoutes.tsx`. +- **`components/`** — presentational, mostly unconnected. +- **`forms/`** — `react-final-form` forms (e.g. `LoginForm`). +- **`dialogs/`** — MUI dialogs. +- **`hooks/`** — shared hooks (e.g. `useAutoConnect`). +- **`i18n.ts` / `i18n-backend.ts`** — `react-i18next` + ICU; translations managed via Transifex. +- UI kit: MUI v7 (`@mui/material`, `@emotion`). + +### Path aliases + +`tsconfig.json` defines the following (resolved at build time by `vite-tsconfig-paths`): + +``` +@app/api @app/components @app/containers @app/dialogs +@app/forms @app/hooks @app/images @app/services +@app/store @app/types @app/websocket @app/generated/* +``` + +Prefer these in new code over relative imports when crossing top-level directory boundaries. Deep paths into a barrel target (e.g. `@app/store/game/...`) are a smell — add the symbol to the relevant `index.ts` barrel instead. + +### End-to-end data flow + +User action in a container → `src/api/*Service` → `src/websocket/commands/*` → `ProtobufService.send*Command` → socket. +Server reply/event → `src/websocket/events/*` (or the `command-options` callback on the original command) → `src/websocket/persistence/*` → `*.dispatch.ts` helpers → Redux / Dexie → selectors → container re-render. + +## Build pipeline and generated files + +`npm start` and `npm run build` both run `prestart`/`prebuild` hooks that invoke `proto:generate` and then `node prebuild.js`. `prebuild.js` does three things: + +1. Copies shared country flag assets from `../cockatrice/resources/countries` into `src/images/countries`. +2. Writes `src/server-props.json` containing `REACT_APP_VERSION` = current `git rev-parse HEAD`. +3. Walks `src/**/*.i18n.json`, merges them into `src/i18n-default.json`, and **throws on duplicate keys** (`i18n key collision: ${key}`). Namespace your i18n keys — collisions fail the build. + +Files you should never edit by hand (all auto-generated, all committed): + +- `src/generated/proto/**` +- `src/i18n-default.json` +- `src/server-props.json` + +If `npm start` seems to be ignoring a new `.i18n.json` file or a fresh proto, run `npm run proto:generate && node prebuild.js` directly — the hooks only fire on `start`/`build`, not on `test` or `lint`. + +`.env.development`, `.env.production`, and `.env.test` exist but are empty. There is currently no env-var configuration surface; server URLs and the like are resolved through the login UI / `server-props.json`, not `import.meta.env`. + +## Testing + +Vitest + Testing Library + jsdom; `setupTests.ts` registers jest-dom matchers. + +**Vitest runs with `test.isolate: false`.** Every spec file in a worker shares the same module graph, so `vi.mock(...)` factories and any mocks they create persist across tests. Consequences: + +- The global `afterEach` in `setupTests.ts` calls `vi.clearAllMocks()` + `vi.restoreAllMocks()` + `vi.useRealTimers()`. It deliberately does **not** call `vi.resetAllMocks()`, because that would reset the implementations of `vi.fn()` instances created inside `vi.mock(...)` factories and break every spec that mocks `store.dispatch` once at file load. +- A test that installs a custom `mockReturnValue` / `mockImplementation` should not assume the next test resets it — either overwrite it or rely on `clearAllMocks` wiping only call histories. +- Always use real timers at the end of a test that switched to fake ones; the global teardown will catch leaks, but relying on it is fragile across files. + +Other conventions: + +- **Fixtures.** Store slices have co-located `__mocks__/fixtures.ts` files (notably `src/store/game/__mocks__/fixtures.ts`) exposing factories like `makeCard`, `makeGameEntry`, `makePlayerProperties`, `makeState`. They build protobuf messages via `create(Schema, overrides)`. Reuse them in new tests instead of hand-rolling proto objects. +- **Websocket mocks.** `src/websocket/__mocks__/` holds shared mock builders (e.g. `makeMockWebSocket`, `makeWebClientMock`, `makeSessionPersistenceMock`). Command and event specs install these with `vi.mock(...)` at the top of the file. +- **Slice tests are per-concern.** Each slice ships parallel `*.actions.spec.ts`, `*.reducer.spec.ts`, `*.selectors.spec.ts`, and `*.dispatch.spec.ts` files; tests don't cross concerns. + +`npm run golden` (lint + test) is the CI gate — run it before declaring work done. + +## Protocol changes + +When a task requires editing `.proto` files in `../libcockatrice_protocol/`, run `npm run proto:generate` afterwards, and: + +1. If the change introduces a new proto *file* that code outside `src/types/` needs to consume, add an `export *` line for it in `src/types/data.ts`. +2. Update any command/event/persistence code that consumes the changed messages. +3. Commit the regenerated files under `src/generated/proto/`. diff --git a/webclient/buf.gen.plugin.mjs b/webclient/buf.gen.plugin.mjs new file mode 100644 index 000000000..b618761db --- /dev/null +++ b/webclient/buf.gen.plugin.mjs @@ -0,0 +1,99 @@ +// @ts-check +/** + * Custom protoc-gen-es sibling plugin. Emits `src/generated/index.ts`, a + * single rollup that re-exports every generated `_pb` module and adds + * `MessageInitShape` param aliases for every `Command_*` message. + * + * Wired into `buf.gen.yaml` as a second local plugin. Runs with the same + * descriptor set protoc-gen-es consumes, so output always tracks the protos. + */ +import { createEcmaScriptPlugin, runNodeJs } from '@bufbuild/protoplugin'; + +const HEADER = [ + '// @generated by protoc-gen-data. DO NOT EDIT.', + '// Rollup of all proto modules + MessageInitShape param aliases for every Command_*.', + '/* eslint-disable */', + '', + '', +].join('\n'); + +const inner = createEcmaScriptPlugin({ + name: 'protoc-gen-data', + version: 'v0.1.0', + generateTs(schema) { + const f = schema.generateFile('index.ts'); + + const MessageInitShape = f.import('MessageInitShape', '@bufbuild/protobuf', true); + const MessageType = f.import('Message', '@bufbuild/protobuf', true); + const GenExtensionType = f.import('GenExtension', '@bufbuild/protobuf/codegenv2', true); + + const sortedFiles = [...schema.files].sort((a, b) => a.name.localeCompare(b.name)); + + for (const file of sortedFiles) { + f.print('export * from ', f.string(`./proto/${file.name}_pb`), ';'); + } + f.print(); + + const commandMessages = []; + for (const file of sortedFiles) { + for (const msg of file.messages) { + if (msg.name.startsWith('Command_')) { + commandMessages.push(msg); + } + } + } + commandMessages.sort((a, b) => a.name.localeCompare(b.name)); + + // importSchema() resolves paths relative to this plugin's `out` dir, which + // yields `./_pb` — but the _pb files live under ./proto/ (protoc-gen-es's + // out). Build the import path explicitly so it points inside the proto subdir. + for (const msg of commandMessages) { + const alias = msg.name.slice('Command_'.length) + 'Params'; + const schemaName = `${msg.name}Schema`; + const schemaSym = f.import(schemaName, `./proto/${msg.file.name}_pb`, true); + f.print('export type ', alias, ' = ', MessageInitShape, ';'); + } + f.print(); + + // Generic extension registry infrastructure. Consolidates the three + // near-duplicate registry types and helpers that used to live in + // src/websocket/services/protobuf-types.ts into one generic pair. + // Specialised aliases (Session/Room/Game) still live in protobuf-types.ts + // because GameExtensionRegistry needs GameEventMeta — a hand-written + // domain type whose import would create a generated/ ↔ types/ cycle. + f.print('export type RegistryEntry = ['); + f.print(' ', GenExtensionType, ','); + f.print(' (value: V, meta: M) => void,'); + f.print('];'); + f.print(); + // Return type widens V to `unknown` so the heterogeneous entries that + // callers build can be stored in a homogeneous `RegistryEntry[]` + // array. This is the actual value-add over a bare tuple literal. + f.print('export function makeEntry('); + f.print(' ext: ', GenExtensionType, ','); + f.print(' handler: (value: V, meta: M) => void,'); + f.print('): RegistryEntry {'); + f.print(' return [ext, handler] as unknown as RegistryEntry;'); + f.print('}'); + }, +}); + +// Skip f.preamble() above and inject a custom rollup-aware header here instead — +// preamble() would write "@generated from file X.proto" which is misleading for +// a rollup file built from every input proto. +/** @type {import('@bufbuild/protoplugin').Plugin} */ +const plugin = { + name: inner.name, + version: inner.version, + run(request) { + const response = inner.run(request); + for (const file of response.file) { + if (file.name === 'index.ts' && typeof file.content === 'string') { + file.content = HEADER + file.content; + } + } + return response; + }, +}; + +runNodeJs(plugin); diff --git a/webclient/buf.gen.yaml b/webclient/buf.gen.yaml index eba5ea4ad..641ffe58a 100644 --- a/webclient/buf.gen.yaml +++ b/webclient/buf.gen.yaml @@ -6,3 +6,7 @@ plugins: out: src/generated/proto opt: - target=ts + - local: [node, buf.gen.plugin.mjs] + out: src/generated + opt: + - target=ts diff --git a/webclient/src/api/AdminService.spec.ts b/webclient/src/api/AdminService.spec.ts index d14207629..1964a8368 100644 --- a/webclient/src/api/AdminService.spec.ts +++ b/webclient/src/api/AdminService.spec.ts @@ -1,4 +1,4 @@ -vi.mock('websocket', () => ({ +vi.mock('@app/websocket', () => ({ AdminCommands: { adjustMod: vi.fn(), reloadConfig: vi.fn(), @@ -8,9 +8,7 @@ vi.mock('websocket', () => ({ })); import { AdminService } from './AdminService'; -import { AdminCommands } from 'websocket'; - -beforeEach(() => vi.clearAllMocks()); +import { AdminCommands } from '@app/websocket'; describe('AdminService', () => { describe('adjustMod', () => { diff --git a/webclient/src/api/AdminService.tsx b/webclient/src/api/AdminService.tsx index 623ad546a..c280fca7b 100644 --- a/webclient/src/api/AdminService.tsx +++ b/webclient/src/api/AdminService.tsx @@ -1,4 +1,4 @@ -import { AdminCommands } from 'websocket'; +import { AdminCommands } from '@app/websocket'; export class AdminService { static adjustMod(userName: string, shouldBeMod?: boolean, shouldBeJudge?: boolean): void { diff --git a/webclient/src/api/AuthenticationService.spec.ts b/webclient/src/api/AuthenticationService.spec.ts index 9d9dfcd79..0b2dfbc1c 100644 --- a/webclient/src/api/AuthenticationService.spec.ts +++ b/webclient/src/api/AuthenticationService.spec.ts @@ -1,71 +1,113 @@ -vi.mock('websocket', () => ({ +vi.mock('@app/websocket', () => ({ SessionCommands: { connect: vi.fn(), disconnect: vi.fn(), }, })); -vi.mock('generated/proto/serverinfo_user_pb', () => ({ - ServerInfo_User_UserLevelFlag: { - IsModerator: 4, - }, -})); +vi.mock('../generated/proto/serverinfo_user_pb', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + ServerInfo_User_UserLevelFlag: { + IsModerator: 4, + }, + }; +}); import { AuthenticationService } from './AuthenticationService'; -import { SessionCommands } from 'websocket'; -import { StatusEnum, WebSocketConnectOptions, WebSocketConnectReason } from 'types'; +import { SessionCommands } from '@app/websocket'; +import { App, Data } from '@app/types'; +import { create } from '@bufbuild/protobuf'; -const testOptions: WebSocketConnectOptions = { host: 'localhost', port: '4748', userName: 'user', password: 'pw' }; - -beforeEach(() => vi.clearAllMocks()); +const baseTransport = { host: 'localhost', port: '4748' }; describe('AuthenticationService', () => { describe('login', () => { it('calls SessionCommands.connect with LOGIN reason', () => { - AuthenticationService.login(testOptions); - expect(SessionCommands.connect).toHaveBeenCalledWith(testOptions, WebSocketConnectReason.LOGIN); + AuthenticationService.login({ ...baseTransport, userName: 'user', password: 'pw' }); + expect(SessionCommands.connect).toHaveBeenCalledWith( + expect.objectContaining({ + ...baseTransport, + userName: 'user', + password: 'pw', + reason: App.WebSocketConnectReason.LOGIN, + }) + ); }); }); describe('testConnection', () => { it('calls SessionCommands.connect with TEST_CONNECTION reason', () => { - AuthenticationService.testConnection(testOptions); - expect(SessionCommands.connect).toHaveBeenCalledWith(testOptions, WebSocketConnectReason.TEST_CONNECTION); + AuthenticationService.testConnection(baseTransport); + expect(SessionCommands.connect).toHaveBeenCalledWith( + expect.objectContaining({ ...baseTransport, reason: App.WebSocketConnectReason.TEST_CONNECTION }) + ); }); }); describe('register', () => { it('calls SessionCommands.connect with REGISTER reason', () => { - AuthenticationService.register(testOptions); - expect(SessionCommands.connect).toHaveBeenCalledWith(testOptions, WebSocketConnectReason.REGISTER); + AuthenticationService.register({ + ...baseTransport, + userName: 'user', + password: 'pw', + email: 'a@b.com', + country: 'US', + realName: 'User', + }); + expect(SessionCommands.connect).toHaveBeenCalledWith( + expect.objectContaining({ userName: 'user', reason: App.WebSocketConnectReason.REGISTER }) + ); }); }); describe('activateAccount', () => { it('calls SessionCommands.connect with ACTIVATE_ACCOUNT reason', () => { - AuthenticationService.activateAccount(testOptions); - expect(SessionCommands.connect).toHaveBeenCalledWith(testOptions, WebSocketConnectReason.ACTIVATE_ACCOUNT); + AuthenticationService.activateAccount({ + ...baseTransport, + userName: 'user', + token: 'tok', + }); + expect(SessionCommands.connect).toHaveBeenCalledWith( + expect.objectContaining({ token: 'tok', reason: App.WebSocketConnectReason.ACTIVATE_ACCOUNT }) + ); }); }); describe('resetPasswordRequest', () => { it('calls SessionCommands.connect with PASSWORD_RESET_REQUEST reason', () => { - AuthenticationService.resetPasswordRequest(testOptions); - expect(SessionCommands.connect).toHaveBeenCalledWith(testOptions, WebSocketConnectReason.PASSWORD_RESET_REQUEST); + AuthenticationService.resetPasswordRequest({ ...baseTransport, userName: 'user' }); + expect(SessionCommands.connect).toHaveBeenCalledWith( + expect.objectContaining({ userName: 'user', reason: App.WebSocketConnectReason.PASSWORD_RESET_REQUEST }) + ); }); }); describe('resetPasswordChallenge', () => { it('calls SessionCommands.connect with PASSWORD_RESET_CHALLENGE reason', () => { - AuthenticationService.resetPasswordChallenge(testOptions); - expect(SessionCommands.connect).toHaveBeenCalledWith(testOptions, WebSocketConnectReason.PASSWORD_RESET_CHALLENGE); + AuthenticationService.resetPasswordChallenge({ + ...baseTransport, + userName: 'user', + email: 'a@b.com', + }); + expect(SessionCommands.connect).toHaveBeenCalledWith( + expect.objectContaining({ email: 'a@b.com', reason: App.WebSocketConnectReason.PASSWORD_RESET_CHALLENGE }) + ); }); }); describe('resetPassword', () => { it('calls SessionCommands.connect with PASSWORD_RESET reason', () => { - AuthenticationService.resetPassword(testOptions); - expect(SessionCommands.connect).toHaveBeenCalledWith(testOptions, WebSocketConnectReason.PASSWORD_RESET); + AuthenticationService.resetPassword({ + ...baseTransport, + userName: 'user', + token: 'tok', + newPassword: 'newpw', + }); + expect(SessionCommands.connect).toHaveBeenCalledWith( + expect.objectContaining({ newPassword: 'newpw', reason: App.WebSocketConnectReason.PASSWORD_RESET }) + ); }); }); @@ -78,41 +120,41 @@ describe('AuthenticationService', () => { describe('isConnected', () => { it('returns true when state is LOGGED_IN', () => { - expect(AuthenticationService.isConnected(StatusEnum.LOGGED_IN)).toBe(true); + expect(AuthenticationService.isConnected(App.StatusEnum.LOGGED_IN)).toBe(true); }); it('returns false when state is DISCONNECTED', () => { - expect(AuthenticationService.isConnected(StatusEnum.DISCONNECTED)).toBe(false); + expect(AuthenticationService.isConnected(App.StatusEnum.DISCONNECTED)).toBe(false); }); it('returns false when state is CONNECTING', () => { - expect(AuthenticationService.isConnected(StatusEnum.CONNECTING)).toBe(false); + expect(AuthenticationService.isConnected(App.StatusEnum.CONNECTING)).toBe(false); }); it('returns false when state is CONNECTED', () => { - expect(AuthenticationService.isConnected(StatusEnum.CONNECTED)).toBe(false); + expect(AuthenticationService.isConnected(App.StatusEnum.CONNECTED)).toBe(false); }); it('returns false when state is LOGGING_IN', () => { - expect(AuthenticationService.isConnected(StatusEnum.LOGGING_IN)).toBe(false); + expect(AuthenticationService.isConnected(App.StatusEnum.LOGGING_IN)).toBe(false); }); }); describe('isModerator', () => { it('returns true when userLevel has the IsModerator bit set', () => { - expect(AuthenticationService.isModerator({ userLevel: 4 } as any)).toBe(true); + expect(AuthenticationService.isModerator(create(Data.ServerInfo_UserSchema, { userLevel: 4 }))).toBe(true); }); it('returns true when userLevel has IsModerator and other bits set', () => { - expect(AuthenticationService.isModerator({ userLevel: 7 } as any)).toBe(true); + expect(AuthenticationService.isModerator(create(Data.ServerInfo_UserSchema, { userLevel: 7 }))).toBe(true); }); it('returns false when userLevel does not have the IsModerator bit', () => { - expect(AuthenticationService.isModerator({ userLevel: 1 } as any)).toBe(false); + expect(AuthenticationService.isModerator(create(Data.ServerInfo_UserSchema, { userLevel: 1 }))).toBe(false); }); it('returns false for admin-only userLevel without moderator bit', () => { - expect(AuthenticationService.isModerator({ userLevel: 8 } as any)).toBe(false); + expect(AuthenticationService.isModerator(create(Data.ServerInfo_UserSchema, { userLevel: 8 }))).toBe(false); }); }); diff --git a/webclient/src/api/AuthenticationService.tsx b/webclient/src/api/AuthenticationService.tsx index e88226dca..bacc8e350 100644 --- a/webclient/src/api/AuthenticationService.tsx +++ b/webclient/src/api/AuthenticationService.tsx @@ -1,34 +1,33 @@ -import { StatusEnum, WebSocketConnectReason, WebSocketConnectOptions } from 'types'; -import { SessionCommands } from 'websocket'; -import { ServerInfo_User, ServerInfo_User_UserLevelFlag } from 'generated/proto/serverinfo_user_pb'; +import { App, Data, Enriched } from '@app/types'; +import { SessionCommands } from '@app/websocket'; export class AuthenticationService { - static login(options: WebSocketConnectOptions): void { - SessionCommands.connect(options, WebSocketConnectReason.LOGIN); + static login(options: Omit): void { + SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.LOGIN }); } - static testConnection(options: WebSocketConnectOptions): void { - SessionCommands.connect(options, WebSocketConnectReason.TEST_CONNECTION); + static testConnection(options: Omit): void { + SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.TEST_CONNECTION }); } - static register(options: WebSocketConnectOptions): void { - SessionCommands.connect(options, WebSocketConnectReason.REGISTER); + static register(options: Omit): void { + SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.REGISTER }); } - static activateAccount(options: WebSocketConnectOptions): void { - SessionCommands.connect(options, WebSocketConnectReason.ACTIVATE_ACCOUNT); + static activateAccount(options: Omit): void { + SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.ACTIVATE_ACCOUNT }); } - static resetPasswordRequest(options: WebSocketConnectOptions): void { - SessionCommands.connect(options, WebSocketConnectReason.PASSWORD_RESET_REQUEST); + static resetPasswordRequest(options: Omit): void { + SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.PASSWORD_RESET_REQUEST }); } - static resetPasswordChallenge(options: WebSocketConnectOptions): void { - SessionCommands.connect(options, WebSocketConnectReason.PASSWORD_RESET_CHALLENGE); + static resetPasswordChallenge(options: Omit): void { + SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.PASSWORD_RESET_CHALLENGE }); } - static resetPassword(options: WebSocketConnectOptions): void { - SessionCommands.connect(options, WebSocketConnectReason.PASSWORD_RESET); + static resetPassword(options: Omit): void { + SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.PASSWORD_RESET }); } static disconnect(): void { @@ -36,11 +35,11 @@ export class AuthenticationService { } static isConnected(state: number): boolean { - return state === StatusEnum.LOGGED_IN; + return state === App.StatusEnum.LOGGED_IN; } - static isModerator(user: ServerInfo_User): boolean { - const moderatorLevel = ServerInfo_User_UserLevelFlag.IsModerator; + static isModerator(user: Data.ServerInfo_User): boolean { + const moderatorLevel = Data.ServerInfo_User_UserLevelFlag.IsModerator; // @TODO tell cockatrice not to do this so shittily return (user.userLevel & moderatorLevel) === moderatorLevel; } diff --git a/webclient/src/api/ModeratorService.spec.ts b/webclient/src/api/ModeratorService.spec.ts index 00c92d447..f32a58d09 100644 --- a/webclient/src/api/ModeratorService.spec.ts +++ b/webclient/src/api/ModeratorService.spec.ts @@ -1,4 +1,4 @@ -vi.mock('websocket', () => ({ +vi.mock('@app/websocket', () => ({ ModeratorCommands: { banFromServer: vi.fn(), getBanHistory: vi.fn(), @@ -10,10 +10,8 @@ vi.mock('websocket', () => ({ })); import { ModeratorService } from './ModeratorService'; -import { ModeratorCommands } from 'websocket'; -import { LogFilters } from 'types'; - -beforeEach(() => vi.clearAllMocks()); +import { ModeratorCommands } from '@app/websocket'; +import { Data } from '@app/types'; describe('ModeratorService', () => { describe('banFromServer', () => { @@ -55,7 +53,7 @@ describe('ModeratorService', () => { describe('viewLogHistory', () => { it('delegates to ModeratorCommands.viewLogHistory', () => { - const filters: LogFilters = { dateRange: 7, userName: 'alice' }; + const filters: Data.ViewLogHistoryParams = { dateRange: 7, userName: 'alice' }; ModeratorService.viewLogHistory(filters); expect(ModeratorCommands.viewLogHistory).toHaveBeenCalledWith(filters); }); diff --git a/webclient/src/api/ModeratorService.tsx b/webclient/src/api/ModeratorService.tsx index 6c22ee55e..2fa9e9019 100644 --- a/webclient/src/api/ModeratorService.tsx +++ b/webclient/src/api/ModeratorService.tsx @@ -1,5 +1,5 @@ -import { ModeratorCommands } from 'websocket'; -import { LogFilters } from 'types'; +import { ModeratorCommands } from '@app/websocket'; +import { Data } from '@app/types'; export class ModeratorService { static banFromServer(minutes: number, userName?: string, address?: string, reason?: string, @@ -19,7 +19,7 @@ export class ModeratorService { ModeratorCommands.getWarnList(modName, userName, userClientid); } - static viewLogHistory(filters: LogFilters): void { + static viewLogHistory(filters: Data.ViewLogHistoryParams): void { ModeratorCommands.viewLogHistory(filters); } diff --git a/webclient/src/api/RoomsService.spec.ts b/webclient/src/api/RoomsService.spec.ts index 541efb904..80ff8d4cd 100644 --- a/webclient/src/api/RoomsService.spec.ts +++ b/webclient/src/api/RoomsService.spec.ts @@ -1,4 +1,4 @@ -vi.mock('websocket', () => ({ +vi.mock('@app/websocket', () => ({ SessionCommands: { joinRoom: vi.fn(), }, @@ -9,9 +9,7 @@ vi.mock('websocket', () => ({ })); import { RoomsService } from './RoomsService'; -import { RoomCommands, SessionCommands } from 'websocket'; - -beforeEach(() => vi.clearAllMocks()); +import { RoomCommands, SessionCommands } from '@app/websocket'; describe('RoomsService', () => { describe('joinRoom', () => { diff --git a/webclient/src/api/RoomsService.tsx b/webclient/src/api/RoomsService.tsx index bfb63d92a..b2f9ddc4e 100644 --- a/webclient/src/api/RoomsService.tsx +++ b/webclient/src/api/RoomsService.tsx @@ -1,4 +1,4 @@ -import { RoomCommands, SessionCommands } from 'websocket'; +import { RoomCommands, SessionCommands } from '@app/websocket'; export class RoomsService { static joinRoom(roomId: number): void { diff --git a/webclient/src/api/SessionService.spec.ts b/webclient/src/api/SessionService.spec.ts index 8e67219bc..879d4c3ef 100644 --- a/webclient/src/api/SessionService.spec.ts +++ b/webclient/src/api/SessionService.spec.ts @@ -1,4 +1,4 @@ -vi.mock('websocket', () => ({ +vi.mock('@app/websocket', () => ({ SessionCommands: { addToBuddyList: vi.fn(), removeFromBuddyList: vi.fn(), @@ -14,9 +14,7 @@ vi.mock('websocket', () => ({ })); import { SessionService } from './SessionService'; -import { SessionCommands } from 'websocket'; - -beforeEach(() => vi.clearAllMocks()); +import { SessionCommands } from '@app/websocket'; describe('SessionService', () => { describe('addToBuddyList', () => { diff --git a/webclient/src/api/SessionService.tsx b/webclient/src/api/SessionService.tsx index 2787f098d..129cdfc58 100644 --- a/webclient/src/api/SessionService.tsx +++ b/webclient/src/api/SessionService.tsx @@ -1,4 +1,4 @@ -import { SessionCommands } from 'websocket'; +import { SessionCommands } from '@app/websocket'; export class SessionService { static addToBuddyList(userName: string) { diff --git a/webclient/src/components/Card/Card.tsx b/webclient/src/components/Card/Card.tsx index f89622c3b..83af90e1f 100644 --- a/webclient/src/components/Card/Card.tsx +++ b/webclient/src/components/Card/Card.tsx @@ -1,7 +1,7 @@ // eslint-disable-next-line import React, { useMemo, useState } from 'react'; -import { CardDTO } from 'services'; +import { CardDTO } from '@app/services'; import './Card.css'; diff --git a/webclient/src/components/CardDetails/CardDetails.tsx b/webclient/src/components/CardDetails/CardDetails.tsx index 84196fda6..81a5c7fa1 100644 --- a/webclient/src/components/CardDetails/CardDetails.tsx +++ b/webclient/src/components/CardDetails/CardDetails.tsx @@ -1,7 +1,7 @@ // eslint-disable-next-line import React, { useMemo, useState } from 'react'; -import { CardDTO } from 'services'; +import { CardDTO } from '@app/services'; import Card from '../Card/Card'; diff --git a/webclient/src/components/CountryDropdown/CountryDropdown.tsx b/webclient/src/components/CountryDropdown/CountryDropdown.tsx index 09b1cf71e..5a7d519bf 100644 --- a/webclient/src/components/CountryDropdown/CountryDropdown.tsx +++ b/webclient/src/components/CountryDropdown/CountryDropdown.tsx @@ -4,9 +4,9 @@ import FormControl from '@mui/material/FormControl'; import InputLabel from '@mui/material/InputLabel'; import { useTranslation } from 'react-i18next'; -import { useLocaleSort } from 'hooks'; -import { Images } from 'images/Images'; -import { countryCodes } from 'types'; +import { useLocaleSort } from '@app/hooks'; +import { Images } from '@app/images'; +import { App } from '@app/types'; import './CountryDropdown.css'; @@ -18,7 +18,7 @@ const CountryDropdown = ({ input: { onChange } }) => { useEffect(() => onChange(value), [value]); const translateCountry = country => t(`Common.countries.${country}`); - const sortedCountries = useLocaleSort(countryCodes, translateCountry); + const sortedCountries = useLocaleSort(App.countryCodes, translateCountry); return ( diff --git a/webclient/src/components/Guard/AuthGuard.tsx b/webclient/src/components/Guard/AuthGuard.tsx index 3b0790a62..bf1ff1876 100644 --- a/webclient/src/components/Guard/AuthGuard.tsx +++ b/webclient/src/components/Guard/AuthGuard.tsx @@ -1,15 +1,15 @@ import React from 'react'; import { Navigate } from 'react-router-dom'; -import { ServerSelectors } from 'store'; -import { RouteEnum } from 'types'; -import { useAppSelector } from 'store/store'; -import { AuthenticationService } from 'api'; +import { ServerSelectors } from '@app/store'; +import { App } from '@app/types'; +import { useAppSelector } from '@app/store'; +import { AuthenticationService } from '@app/api'; const AuthGuard = () => { const state = useAppSelector(s => ServerSelectors.getState(s)); return !AuthenticationService.isConnected(state) - ? + ? :
; }; diff --git a/webclient/src/components/Guard/ModGuard.tsx b/webclient/src/components/Guard/ModGuard.tsx index f8f629898..18b68adf1 100644 --- a/webclient/src/components/Guard/ModGuard.tsx +++ b/webclient/src/components/Guard/ModGuard.tsx @@ -1,15 +1,15 @@ import React from 'react'; import { Navigate } from 'react-router-dom'; -import { ServerSelectors } from 'store'; -import { AuthenticationService } from 'api'; -import { RouteEnum } from 'types'; -import { useAppSelector } from 'store/store'; +import { ServerSelectors } from '@app/store'; +import { AuthenticationService } from '@app/api'; +import { App } from '@app/types'; +import { useAppSelector } from '@app/store'; const ModGuard = () => { const user = useAppSelector(state => ServerSelectors.getUser(state)); return !AuthenticationService.isModerator(user) - ? + ? : <>; }; diff --git a/webclient/src/components/InputAction/InputAction.tsx b/webclient/src/components/InputAction/InputAction.tsx index 6c5e61474..7326c0a35 100644 --- a/webclient/src/components/InputAction/InputAction.tsx +++ b/webclient/src/components/InputAction/InputAction.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Field } from 'react-final-form' import Button from '@mui/material/Button'; -import { InputField } from 'components'; +import { InputField } from '..'; import './InputAction.css'; diff --git a/webclient/src/components/KnownHosts/KnownHosts.tsx b/webclient/src/components/KnownHosts/KnownHosts.tsx index cb19d9f56..096a8bece 100644 --- a/webclient/src/components/KnownHosts/KnownHosts.tsx +++ b/webclient/src/components/KnownHosts/KnownHosts.tsx @@ -13,13 +13,13 @@ import AddIcon from '@mui/icons-material/Add'; import EditRoundedIcon from '@mui/icons-material/Edit'; import ErrorOutlinedIcon from '@mui/icons-material/ErrorOutlined'; -import { AuthenticationService } from 'api'; -import { KnownHostDialog } from 'dialogs'; -import { useReduxEffect } from 'hooks'; -import { HostDTO } from 'services'; -import { ServerTypes } from 'store'; -import { DefaultHosts, Host, getHostPort } from 'types'; -import Toast from 'components/Toast/Toast'; +import { AuthenticationService } from '@app/api'; +import { KnownHostDialog } from '@app/dialogs'; +import { useReduxEffect } from '@app/hooks'; +import { HostDTO } from '@app/services'; +import { ServerTypes } from '@app/store'; +import { App } from '@app/types'; +import Toast from '../Toast/Toast'; import './KnownHosts.css'; @@ -86,7 +86,7 @@ const KnownHosts = (props) => { if (!hosts?.length) { // @TODO: find a better pattern to seeding default data in indexedDB - await HostDTO.bulkAdd(DefaultHosts); + await HostDTO.bulkAdd(App.DefaultHosts); loadKnownHosts(); } else { const selectedHost = hosts.find(({ lastSelected }) => lastSelected) || hosts[0]; @@ -159,7 +159,7 @@ const KnownHosts = (props) => { })); setShowEditToast(true) } else { - const newHost: Host = { name, host, port, editable: true }; + const newHost: App.Host = { name, host, port, editable: true }; newHost.id = await HostDTO.add(newHost) as number; setHostsState(s => ({ @@ -196,7 +196,7 @@ const KnownHosts = (props) => { const testConnection = () => { setTestingConnection(TestConnection.TESTING); - const options = { ...getHostPort(hostsState.selectedHost) }; + const options = { ...App.getHostPort(hostsState.selectedHost) }; AuthenticationService.testConnection(options); } @@ -236,34 +236,38 @@ const KnownHosts = (props) => { { - hostsState.hosts.map((host, index) => ( - -
-
-
- { - testingConnection === TestConnection.FAILED - ? - : - } + hostsState.hosts.map((host, index) => { + const hostPort = App.getHostPort(hostsState.hosts[index]); + + return ( + +
+
+
+ { + testingConnection === TestConnection.FAILED + ? + : + } +
+ +
+ + {host.name} ({ hostPort.host }:{hostPort.port}) +
-
- - {host.name} ({ getHostPort(hostsState.hosts[index]).host }:{getHostPort(hostsState.hosts[index]).port}) -
+ { host.editable && ( + { + openEditKnownHostDialog(hostsState.hosts[index]); + }}> + + + ) }
- - { host.editable && ( - { - openEditKnownHostDialog(hostsState.hosts[index]); - }}> - - - ) } -
- - )) + + ); + }) } diff --git a/webclient/src/components/LanguageDropdown/LanguageDropdown.tsx b/webclient/src/components/LanguageDropdown/LanguageDropdown.tsx index 0cd0ccbe1..d48aa2e7b 100644 --- a/webclient/src/components/LanguageDropdown/LanguageDropdown.tsx +++ b/webclient/src/components/LanguageDropdown/LanguageDropdown.tsx @@ -4,8 +4,8 @@ import { useTranslation } from 'react-i18next'; import { Select, MenuItem } from '@mui/material'; import FormControl from '@mui/material/FormControl'; -import { Images } from 'images/Images'; -import { Language, LanguageCountry, LanguageNative } from 'types'; +import { Images } from '@app/images'; +import { App } from '@app/types'; import './LanguageDropdown.css'; @@ -26,19 +26,19 @@ const LanguageDropdown = () => { margin='dense' value={language} fullWidth={true} - onChange={e => setLanguage(e.target.value as Language)} + onChange={e => setLanguage(e.target.value as App.Language)} > { - Object.keys(Language).map((lang) => { - const country = LanguageCountry[lang]; + Object.keys(App.Language).map((lang) => { + const country = App.LanguageCountry[lang]; return (
- {LanguageNative[lang]} { - LanguageNative[lang] !== t(`Common.languages.${lang}`) && ( + {App.LanguageNative[lang]} { + App.LanguageNative[lang] !== t(`Common.languages.${lang}`) && ( <>({ t(`Common.languages.${lang}`) }) ) } diff --git a/webclient/src/components/Message/CardCallout.tsx b/webclient/src/components/Message/CardCallout.tsx index 9db660a30..3872ae17f 100644 --- a/webclient/src/components/Message/CardCallout.tsx +++ b/webclient/src/components/Message/CardCallout.tsx @@ -3,7 +3,7 @@ import React, { useMemo, useState } from 'react'; import { styled } from '@mui/material/styles'; import Popover from '@mui/material/Popover'; -import { CardDTO, TokenDTO } from 'services'; +import { CardDTO, TokenDTO } from '@app/services'; import CardDetails from '../CardDetails/CardDetails'; import TokenDetails from '../TokenDetails/TokenDetails'; diff --git a/webclient/src/components/Message/Message.tsx b/webclient/src/components/Message/Message.tsx index b7d433d18..bc9d640ec 100644 --- a/webclient/src/components/Message/Message.tsx +++ b/webclient/src/components/Message/Message.tsx @@ -3,14 +3,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import { NavLink, generatePath } from 'react-router-dom'; -import { - RouteEnum, - URL_REGEX, - MESSAGE_SENDER_REGEX, - MENTION_REGEX, - CARD_CALLOUT_REGEX, - CALLOUT_BOUNDARY_REGEX, -} from 'types'; +import { App } from '@app/types'; import CardCallout from './CardCallout'; import './Message.css'; @@ -28,7 +21,7 @@ const ParsedMessage = ({ message }) => { const [name, setName] = useState(null); useMemo(() => { - const name = message.match(MESSAGE_SENDER_REGEX); + const name = message.match(App.MESSAGE_SENDER_REGEX); if (name) { setName(name[1]); @@ -46,29 +39,29 @@ const ParsedMessage = ({ message }) => { }; const PlayerLink = ({ name, label = name }) => ( - + {label} ); function parseMessage(message) { - return message.replace(MESSAGE_SENDER_REGEX, '') - .split(CARD_CALLOUT_REGEX) + return message.replace(App.MESSAGE_SENDER_REGEX, '') + .split(App.CARD_CALLOUT_REGEX) .filter(chunk => !!chunk) .map(parseChunks); } function parseChunks(chunk, index) { - if (chunk.match(CARD_CALLOUT_REGEX)) { - const name = chunk.replace(CALLOUT_BOUNDARY_REGEX, '').trim(); + if (chunk.match(App.CARD_CALLOUT_REGEX)) { + const name = chunk.replace(App.CALLOUT_BOUNDARY_REGEX, '').trim(); return (); } - if (chunk.match(URL_REGEX)) { + if (chunk.match(App.URL_REGEX)) { return parseUrlChunk(chunk); } - if (chunk.match(MENTION_REGEX)) { + if (chunk.match(App.MENTION_REGEX)) { return parseMentionChunk(chunk); } @@ -76,10 +69,10 @@ function parseChunks(chunk, index) { } function parseUrlChunk(chunk) { - return chunk.split(URL_REGEX) + return chunk.split(App.URL_REGEX) .filter(urlChunk => !!urlChunk) .map((urlChunk, index) => { - if (urlChunk.match(URL_REGEX)) { + if (urlChunk.match(App.URL_REGEX)) { return ({urlChunk}); } @@ -88,10 +81,10 @@ function parseUrlChunk(chunk) { } function parseMentionChunk(chunk) { - return chunk.split(MENTION_REGEX) + return chunk.split(App.MENTION_REGEX) .filter(mentionChunk => !!mentionChunk) .map((mentionChunk, index) => { - const mention = mentionChunk.match(MENTION_REGEX); + const mention = mentionChunk.match(App.MENTION_REGEX); if (mention) { const name = mention[0].substr(1); diff --git a/webclient/src/components/Token/Token.tsx b/webclient/src/components/Token/Token.tsx index 29b39ecc5..9a9b4768d 100644 --- a/webclient/src/components/Token/Token.tsx +++ b/webclient/src/components/Token/Token.tsx @@ -1,7 +1,7 @@ // eslint-disable-next-line import React, { useMemo, useState } from 'react'; -import { TokenDTO } from 'services'; +import { TokenDTO } from '@app/services'; import './Token.css'; diff --git a/webclient/src/components/TokenDetails/TokenDetails.tsx b/webclient/src/components/TokenDetails/TokenDetails.tsx index f3d8aed96..9166a554f 100644 --- a/webclient/src/components/TokenDetails/TokenDetails.tsx +++ b/webclient/src/components/TokenDetails/TokenDetails.tsx @@ -1,7 +1,7 @@ // eslint-disable-next-line import React, { useMemo, useState } from 'react'; -import { TokenDTO } from 'services'; +import { TokenDTO } from '@app/services'; import Token from '../Token/Token'; diff --git a/webclient/src/components/UserDisplay/UserDisplay.tsx b/webclient/src/components/UserDisplay/UserDisplay.tsx index 74a3acfe0..a19bc9912 100644 --- a/webclient/src/components/UserDisplay/UserDisplay.tsx +++ b/webclient/src/components/UserDisplay/UserDisplay.tsx @@ -5,12 +5,11 @@ import { NavLink, generatePath } from 'react-router-dom'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; -import { Images } from 'images/Images'; -import { SessionService } from 'api'; -import { ServerSelectors } from 'store'; -import { RouteEnum } from 'types'; -import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; -import { useAppSelector } from 'store/store'; +import { Images } from '@app/images'; +import { SessionService } from '@app/api'; +import { ServerSelectors } from '@app/store'; +import { App, Data } from '@app/types'; +import { useAppSelector } from '@app/store'; import './UserDisplay.css'; @@ -51,7 +50,7 @@ const UserDisplay = ({ user }: UserDisplayProps) => { return (
- +
{country}
{name}
@@ -68,7 +67,7 @@ const UserDisplay = ({ user }: UserDisplayProps) => { : undefined } > - + Chat { @@ -88,7 +87,7 @@ const UserDisplay = ({ user }: UserDisplayProps) => { }; interface UserDisplayProps { - user: ServerInfo_User; + user: Data.ServerInfo_User; } export default UserDisplay; diff --git a/webclient/src/components/index.ts b/webclient/src/components/index.ts index 2d67b3003..5337b3b10 100644 --- a/webclient/src/components/index.ts +++ b/webclient/src/components/index.ts @@ -17,3 +17,6 @@ export { default as ScrollToBottomOnChanges } from './ScrollToBottomOnChanges/Sc // Guards export { default as AuthGuard } from './Guard/AuthGuard'; export { default as ModGuard } from './Guard/ModGuard'; + +// Toast +export { default as Toast, useToast, ToastProvider } from './Toast'; diff --git a/webclient/src/containers/Account/Account.tsx b/webclient/src/containers/Account/Account.tsx index e25f59b72..8e0088d06 100644 --- a/webclient/src/containers/Account/Account.tsx +++ b/webclient/src/containers/Account/Account.tsx @@ -6,11 +6,11 @@ import Button from '@mui/material/Button'; import ListItemButton from '@mui/material/ListItemButton'; import Paper from '@mui/material/Paper'; -import { UserDisplay, VirtualList, AuthGuard, LanguageDropdown } from 'components'; -import { AuthenticationService, SessionService } from 'api'; -import { ServerSelectors } from 'store'; -import Layout from 'containers/Layout/Layout'; -import { useAppSelector } from 'store/store'; +import { UserDisplay, VirtualList, AuthGuard, LanguageDropdown } from '@app/components'; +import { AuthenticationService, SessionService } from '@app/api'; +import { ServerSelectors } from '@app/store'; +import Layout from '../Layout/Layout'; +import { useAppSelector } from '@app/store'; import AddToBuddies from './AddToBuddies'; import AddToIgnore from './AddToIgnore'; diff --git a/webclient/src/containers/Account/AddToBuddies.tsx b/webclient/src/containers/Account/AddToBuddies.tsx index 3aa5489ad..7fb05859d 100644 --- a/webclient/src/containers/Account/AddToBuddies.tsx +++ b/webclient/src/containers/Account/AddToBuddies.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Form } from 'react-final-form' -import { InputAction } from 'components'; +import { InputAction } from '@app/components'; const AddToBuddies = ({ onSubmit }) => ( onSubmit(values)}> diff --git a/webclient/src/containers/Account/AddToIgnore.tsx b/webclient/src/containers/Account/AddToIgnore.tsx index 270036946..5149de0f5 100644 --- a/webclient/src/containers/Account/AddToIgnore.tsx +++ b/webclient/src/containers/Account/AddToIgnore.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Form } from 'react-final-form' -import { InputAction } from 'components'; +import { InputAction } from '@app/components'; const AddToIgnore = ({ onSubmit }) => ( onSubmit(values)}> diff --git a/webclient/src/containers/App/AppShell.tsx b/webclient/src/containers/App/AppShell.tsx index c28d20067..9476d76cd 100644 --- a/webclient/src/containers/App/AppShell.tsx +++ b/webclient/src/containers/App/AppShell.tsx @@ -2,13 +2,13 @@ import { Component, Suspense } from 'react'; import { Provider } from 'react-redux'; import { MemoryRouter as Router } from 'react-router-dom'; import CssBaseline from '@mui/material/CssBaseline'; -import { store } from 'store'; +import { store } from '@app/store'; import Routes from './AppShellRoutes'; import FeatureDetection from './FeatureDetection'; import './AppShell.css'; -import { ToastProvider } from 'components/Toast' +import { ToastProvider } from '@app/components' class AppShell extends Component { componentDidMount() { diff --git a/webclient/src/containers/App/AppShellRoutes.tsx b/webclient/src/containers/App/AppShellRoutes.tsx index b07961eef..fc097ffd4 100644 --- a/webclient/src/containers/App/AppShellRoutes.tsx +++ b/webclient/src/containers/App/AppShellRoutes.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Route, Routes } from 'react-router-dom'; -import { RouteEnum } from 'types'; +import { App } from '@app/types'; import { Account, Decks, @@ -13,22 +13,22 @@ import { Logs, Initialize, Unsupported -} from 'containers'; +} from '..'; const AppShellRoutes = () => (
} /> - } /> - } /> - } /> - } /> - } /> - {} />} - } /> - } /> - } /> + } /> + } /> + } /> + } /> + } /> + {} />} + } /> + } /> + } />
); diff --git a/webclient/src/containers/App/FeatureDetection.tsx b/webclient/src/containers/App/FeatureDetection.tsx index d7ef75aeb..8ac37f321 100644 --- a/webclient/src/containers/App/FeatureDetection.tsx +++ b/webclient/src/containers/App/FeatureDetection.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { Navigate } from 'react-router-dom'; -import { dexieService } from 'services'; -import { RouteEnum } from 'types'; +import { dexieService } from '@app/services'; +import { App } from '@app/types'; const FeatureDetection = () => { const [unsupported, setUnsupported] = useState(false); @@ -15,7 +15,7 @@ const FeatureDetection = () => { }, []); return unsupported - ? + ? : <>; function detectIndexedDB() { diff --git a/webclient/src/containers/Decks/Decks.tsx b/webclient/src/containers/Decks/Decks.tsx index 190196dc9..f37d67261 100644 --- a/webclient/src/containers/Decks/Decks.tsx +++ b/webclient/src/containers/Decks/Decks.tsx @@ -1,8 +1,8 @@ // eslint-disable-next-line import React, { Component } from "react"; -import { AuthGuard } from 'components/index'; -import Layout from 'containers/Layout/Layout'; +import { AuthGuard } from '@app/components'; +import Layout from '../Layout/Layout'; import './Decks.css'; diff --git a/webclient/src/containers/Game/Game.tsx b/webclient/src/containers/Game/Game.tsx index 694ffb46d..6be3ab1ca 100644 --- a/webclient/src/containers/Game/Game.tsx +++ b/webclient/src/containers/Game/Game.tsx @@ -1,8 +1,8 @@ // eslint-disable-next-line import React, { Component } from "react"; -import { AuthGuard } from 'components'; -import Layout from 'containers/Layout/Layout'; +import { AuthGuard } from '@app/components'; +import Layout from '../Layout/Layout'; import './Game.css'; diff --git a/webclient/src/containers/Initialize/Initialize.tsx b/webclient/src/containers/Initialize/Initialize.tsx index 92f8223e7..9e32c1925 100644 --- a/webclient/src/containers/Initialize/Initialize.tsx +++ b/webclient/src/containers/Initialize/Initialize.tsx @@ -3,11 +3,11 @@ import { useTranslation, Trans } from 'react-i18next'; import { Navigate } from 'react-router-dom'; import Typography from '@mui/material/Typography'; -import { Images } from 'images'; -import { ServerSelectors } from 'store'; -import { RouteEnum } from 'types'; -import Layout from 'containers/Layout/Layout'; -import { useAppSelector } from 'store/store'; +import { Images } from '@app/images'; +import { ServerSelectors } from '@app/store'; +import { App } from '@app/types'; +import Layout from '../Layout/Layout'; +import { useAppSelector } from '@app/store'; import './Initialize.css'; @@ -34,7 +34,7 @@ const Initialize = () => { const { t } = useTranslation(); return initialized - ? + ? : ( diff --git a/webclient/src/containers/Layout/LeftNav.tsx b/webclient/src/containers/Layout/LeftNav.tsx index 196087d7a..db4227b36 100644 --- a/webclient/src/containers/Layout/LeftNav.tsx +++ b/webclient/src/containers/Layout/LeftNav.tsx @@ -8,12 +8,12 @@ import CloseIcon from '@mui/icons-material/Close'; import MailOutlineRoundedIcon from '@mui/icons-material/MailOutline'; import MenuRoundedIcon from '@mui/icons-material/MenuRounded'; -import { AuthenticationService, RoomsService } from 'api'; -import { CardImportDialog } from 'dialogs'; -import { Images } from 'images'; -import { RoomsSelectors, ServerSelectors } from 'store'; -import { RouteEnum } from 'types'; -import { useAppSelector } from 'store/store'; +import { AuthenticationService, RoomsService } from '@app/api'; +import { CardImportDialog } from '@app/dialogs'; +import { Images } from '@app/images'; +import { RoomsSelectors, ServerSelectors } from '@app/store'; +import { App } from '@app/types'; +import { useAppSelector } from '@app/store'; import './LeftNav.css'; @@ -82,7 +82,7 @@ const LeftNav = () => {
- + logo { AuthenticationService.isConnected(serverState) && ( @@ -98,8 +98,8 @@ const LeftNav = () => { className="LeftNav-nav__link-btn" to={ joinedRooms.length - ? generatePath(RouteEnum.ROOM, { roomId: joinedRooms[0].roomId.toString() }) - : RouteEnum.SERVER + ? generatePath(App.RouteEnum.ROOM, { roomId: joinedRooms[0].roomId.toString() }) + : App.RouteEnum.SERVER } > Rooms @@ -108,7 +108,9 @@ const LeftNav = () => {
{joinedRooms.map(({ name, roomId }) => (
- + {name} leaveRoom(event, roomId)}> @@ -120,13 +122,13 @@ const LeftNav = () => {
- + Games
- + Decks diff --git a/webclient/src/containers/Login/Login.tsx b/webclient/src/containers/Login/Login.tsx index d15332564..d0668fa11 100644 --- a/webclient/src/containers/Login/Login.tsx +++ b/webclient/src/containers/Login/Login.tsx @@ -6,20 +6,20 @@ import Button from '@mui/material/Button'; import Paper from '@mui/material/Paper'; import Typography from '@mui/material/Typography'; -import { AuthenticationService } from 'api'; -import { RegistrationDialog, RequestPasswordResetDialog, ResetPasswordDialog, AccountActivationDialog } from 'dialogs'; -import { LanguageDropdown } from 'components'; -import { LoginForm } from 'forms'; -import { useReduxEffect, useFireOnce } from 'hooks'; -import { Images } from 'images'; -import { HostDTO, serverProps } from 'services'; -import { RouteEnum, WebSocketConnectOptions, getHostPort } from 'types'; -import { ServerSelectors, ServerTypes } from 'store'; -import Layout from 'containers/Layout/Layout'; -import { useAppSelector } from 'store/store'; +import { AuthenticationService } from '@app/api'; +import { RegistrationDialog, RequestPasswordResetDialog, ResetPasswordDialog, AccountActivationDialog } from '@app/dialogs'; +import { LanguageDropdown } from '@app/components'; +import { LoginForm } from '@app/forms'; +import { useReduxEffect, useFireOnce } from '@app/hooks'; +import { Images } from '@app/images'; +import { HostDTO, serverProps } from '@app/services'; +import { App, Enriched } from '@app/types'; +import { ServerSelectors, ServerTypes } from '@app/store'; +import Layout from '../Layout/Layout'; +import { useAppSelector } from '@app/store'; import './Login.css'; -import { useToast } from 'components/Toast'; +import { useToast } from '@app/components'; const PREFIX = 'Login'; @@ -71,7 +71,7 @@ const Login = () => { const isConnected = AuthenticationService.isConnected(state); - const [pendingActivationOptions, setPendingActivationOptions] = useState(null); + const [pendingActivationOptions, setPendingActivationOptions] = useState(null); const [rememberLogin, setRememberLogin] = useState(null); const [dialogState, setDialogState] = useState({ @@ -126,17 +126,17 @@ const Login = () => { setRememberLogin(loginForm); const { userName, password, selectedHost, remember } = loginForm; - const options: WebSocketConnectOptions = { - ...getHostPort(selectedHost), + const options: Omit = { + ...App.getHostPort(selectedHost), userName, - password + password, }; if (remember && !password) { options.hashedPassword = selectedHost.hashedPassword; } - AuthenticationService.login(options as WebSocketConnectOptions); + AuthenticationService.login(options); }, []); const [submitButtonDisabled, resetSubmitButton, handleLogin] = useFireOnce(onSubmitLogin); @@ -156,7 +156,7 @@ const Login = () => { const { userName, password, email, country, realName, selectedHost } = registerForm; AuthenticationService.register({ - ...getHostPort(selectedHost), + ...App.getHostPort(selectedHost), userName, password, email, @@ -166,15 +166,20 @@ const Login = () => { }; const handleAccountActivationDialogSubmit = ({ token }) => { + if (!pendingActivationOptions) { + return; + } AuthenticationService.activateAccount({ - ...pendingActivationOptions, + host: pendingActivationOptions.host, + port: pendingActivationOptions.port, + userName: pendingActivationOptions.userName, token, }); }; const handleRequestPasswordResetDialogSubmit = (form) => { const { userName, email, selectedHost } = form; - const { host, port } = getHostPort(selectedHost); + const { host, port } = App.getHostPort(selectedHost); if (email) { AuthenticationService.resetPasswordChallenge({ userName, email, host, port }); @@ -185,7 +190,7 @@ const Login = () => { }; const handleResetPasswordDialogSubmit = ({ userName, token, newPassword, selectedHost }) => { - const { host, port } = getHostPort(selectedHost); + const { host, port } = App.getHostPort(selectedHost); AuthenticationService.resetPassword({ userName, token, newPassword, host, port }); }; @@ -234,7 +239,7 @@ const Login = () => { return ( - { isConnected && } + { isConnected && }
diff --git a/webclient/src/containers/Logs/Logs.tsx b/webclient/src/containers/Logs/Logs.tsx index 37fa4d27a..317a04d14 100644 --- a/webclient/src/containers/Logs/Logs.tsx +++ b/webclient/src/containers/Logs/Logs.tsx @@ -2,12 +2,12 @@ import React, { useEffect } from "react"; import * as _ from 'lodash'; -import { ModeratorService } from 'api'; -import { AuthGuard, ModGuard } from 'components'; -import { SearchForm } from 'forms'; -import { ServerDispatch, ServerSelectors } from 'store'; -import { LogFilters } from 'types'; -import { useAppSelector } from 'store/store'; +import { ModeratorService } from '@app/api'; +import { AuthGuard, ModGuard } from '@app/components'; +import { SearchForm } from '@app/forms'; +import { ServerDispatch, ServerSelectors } from '@app/store'; +import { Data } from '@app/types'; +import { useAppSelector } from '@app/store'; import LogResults from './LogResults'; import './Logs.css'; @@ -43,7 +43,7 @@ const Logs = () => { }, []); }; - const onSubmit = (fields: LogFilters) => { + const onSubmit = (fields: Data.ViewLogHistoryParams) => { const trimmedFields: any = trimFields(fields); const { userName, ipAddress, gameName, gameId, message, logLocation } = trimmedFields; diff --git a/webclient/src/containers/Player/Player.tsx b/webclient/src/containers/Player/Player.tsx index 74feff8bd..58f5403cf 100644 --- a/webclient/src/containers/Player/Player.tsx +++ b/webclient/src/containers/Player/Player.tsx @@ -1,8 +1,8 @@ // eslint-disable-next-line import React, { Component } from "react"; -import Layout from 'containers/Layout/Layout'; +import Layout from '../Layout/Layout'; -import { AuthGuard } from 'components'; +import { AuthGuard } from '@app/components'; class Player extends Component { render() { diff --git a/webclient/src/containers/Room/Games.tsx b/webclient/src/containers/Room/Games.tsx index f2489a29f..992170c4e 100644 --- a/webclient/src/containers/Room/Games.tsx +++ b/webclient/src/containers/Room/Games.tsx @@ -12,9 +12,9 @@ import Tooltip from '@mui/material/Tooltip'; // import { RoomsService } from "AppShell/common/services"; -import { SortUtil, RoomsDispatch, RoomsSelectors } from 'store'; -import { UserDisplay } from 'components'; -import { useAppSelector } from 'store/store'; +import { SortUtil, RoomsDispatch, RoomsSelectors } from '@app/store'; +import { UserDisplay } from '@app/components'; +import { useAppSelector } from '@app/store'; import './Games.css'; diff --git a/webclient/src/containers/Room/Messages.tsx b/webclient/src/containers/Room/Messages.tsx index 81aa671b8..88e727b53 100644 --- a/webclient/src/containers/Room/Messages.tsx +++ b/webclient/src/containers/Room/Messages.tsx @@ -1,7 +1,7 @@ // eslint-disable-next-line import React from "react"; -import { Message } from 'components'; +import { Message } from '@app/components'; import './Messages.css'; diff --git a/webclient/src/containers/Room/OpenGames.tsx b/webclient/src/containers/Room/OpenGames.tsx index 79c2a855d..d18d61b9f 100644 --- a/webclient/src/containers/Room/OpenGames.tsx +++ b/webclient/src/containers/Room/OpenGames.tsx @@ -12,9 +12,9 @@ import Tooltip from '@mui/material/Tooltip'; // import { RoomsService } from "AppShell/common/services"; -import { SortUtil, RoomsDispatch, RoomsSelectors } from 'store'; -import { UserDisplay } from 'components'; -import { useAppSelector } from 'store/store'; +import { SortUtil, RoomsDispatch, RoomsSelectors } from '@app/store'; +import { UserDisplay } from '@app/components'; +import { useAppSelector } from '@app/store'; import './OpenGames.css'; diff --git a/webclient/src/containers/Room/Room.tsx b/webclient/src/containers/Room/Room.tsx index 4c470d0fa..15821d200 100644 --- a/webclient/src/containers/Room/Room.tsx +++ b/webclient/src/containers/Room/Room.tsx @@ -4,12 +4,12 @@ import { useNavigate, useParams, generatePath } from 'react-router-dom'; import ListItemButton from '@mui/material/ListItemButton'; import Paper from '@mui/material/Paper'; -import { RoomsService } from 'api'; -import { ScrollToBottomOnChanges, ThreePaneLayout, UserDisplay, VirtualList, AuthGuard } from 'components'; -import { RoomsSelectors } from 'store'; -import { useAppSelector } from 'store/store'; -import { RouteEnum } from 'types'; -import Layout from 'containers/Layout/Layout'; +import { RoomsService } from '@app/api'; +import { ScrollToBottomOnChanges, ThreePaneLayout, UserDisplay, VirtualList, AuthGuard } from '@app/components'; +import { RoomsSelectors } from '@app/store'; +import { useAppSelector } from '@app/store'; +import { App } from '@app/types'; +import Layout from '../Layout/Layout'; import OpenGames from './OpenGames'; import Messages from './Messages'; @@ -32,7 +32,7 @@ const Room = () => { useEffect(() => { if (!joined.find(({ roomId: id }) => id === roomId)) { - navigate(generatePath(RouteEnum.SERVER)); + navigate(generatePath(App.RouteEnum.SERVER)); } }, [joined]); diff --git a/webclient/src/containers/Room/SayMessage.tsx b/webclient/src/containers/Room/SayMessage.tsx index 4dd62dfb4..589ebdacc 100644 --- a/webclient/src/containers/Room/SayMessage.tsx +++ b/webclient/src/containers/Room/SayMessage.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Form } from 'react-final-form' -import { InputAction } from 'components'; +import { InputAction } from '@app/components'; const SayMessage = ({ onSubmit }) => ( diff --git a/webclient/src/containers/Server/Rooms.tsx b/webclient/src/containers/Server/Rooms.tsx index 517f24380..c4705cc3f 100644 --- a/webclient/src/containers/Server/Rooms.tsx +++ b/webclient/src/containers/Server/Rooms.tsx @@ -11,8 +11,8 @@ import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; -import { RoomsService } from 'api'; -import { RouteEnum } from 'types'; +import { RoomsService } from '@app/api'; +import { App } from '@app/types'; import './Rooms.css'; @@ -21,7 +21,7 @@ const Rooms = ({ rooms, joinedRooms }) => { function onClick(roomId) { if (_.find(joinedRooms, room => room.roomId === roomId)) { - navigate(generatePath(RouteEnum.ROOM, { roomId })); + navigate(generatePath(App.RouteEnum.ROOM, { roomId })); } else { RoomsService.joinRoom(roomId); } diff --git a/webclient/src/containers/Server/Server.tsx b/webclient/src/containers/Server/Server.tsx index 3d1778343..45ac959f9 100644 --- a/webclient/src/containers/Server/Server.tsx +++ b/webclient/src/containers/Server/Server.tsx @@ -5,13 +5,13 @@ import { generatePath, useNavigate } from 'react-router-dom'; import ListItemButton from '@mui/material/ListItemButton'; import Paper from '@mui/material/Paper'; -import { AuthGuard, ThreePaneLayout, UserDisplay, VirtualList } from 'components'; -import { useReduxEffect } from 'hooks'; -import { RoomsSelectors, RoomsTypes, ServerSelectors } from 'store'; -import { RouteEnum } from 'types'; -import { useAppSelector } from 'store/store'; +import { AuthGuard, ThreePaneLayout, UserDisplay, VirtualList } from '@app/components'; +import { useReduxEffect } from '@app/hooks'; +import { RoomsSelectors, RoomsTypes, ServerSelectors } from '@app/store'; +import { App } from '@app/types'; +import { useAppSelector } from '@app/store'; import Rooms from './Rooms'; -import Layout from 'containers/Layout/Layout'; +import Layout from '../Layout/Layout'; import './Server.css'; @@ -24,7 +24,7 @@ const Server = () => { useReduxEffect((action: any) => { const roomId = action.roomInfo.roomId.toString(); - navigate(generatePath(RouteEnum.ROOM, { roomId })); + navigate(generatePath(App.RouteEnum.ROOM, { roomId })); }, RoomsTypes.JOIN_ROOM, []); return ( diff --git a/webclient/src/containers/Unsupported/Unsupported.tsx b/webclient/src/containers/Unsupported/Unsupported.tsx index a4eec9d72..5e491d17a 100644 --- a/webclient/src/containers/Unsupported/Unsupported.tsx +++ b/webclient/src/containers/Unsupported/Unsupported.tsx @@ -1,7 +1,7 @@ import { useTranslation } from 'react-i18next'; import Paper from '@mui/material/Paper'; import Typography from '@mui/material/Typography'; -import Layout from 'containers/Layout/Layout'; +import Layout from '../Layout/Layout'; import './Unsupported.css'; diff --git a/webclient/src/dialogs/AccountActivationDialog/AccountActivationDialog.tsx b/webclient/src/dialogs/AccountActivationDialog/AccountActivationDialog.tsx index b14383a20..476a399ec 100644 --- a/webclient/src/dialogs/AccountActivationDialog/AccountActivationDialog.tsx +++ b/webclient/src/dialogs/AccountActivationDialog/AccountActivationDialog.tsx @@ -7,7 +7,7 @@ import CloseIcon from '@mui/icons-material/Close'; import Typography from '@mui/material/Typography'; import { useTranslation } from 'react-i18next'; -import { AccountActivationForm } from 'forms'; +import { AccountActivationForm } from '@app/forms'; import './AccountActivationDialog.css'; diff --git a/webclient/src/dialogs/CardImportDialog/CardImportDialog.tsx b/webclient/src/dialogs/CardImportDialog/CardImportDialog.tsx index 1293fa5ba..a5fc1da90 100644 --- a/webclient/src/dialogs/CardImportDialog/CardImportDialog.tsx +++ b/webclient/src/dialogs/CardImportDialog/CardImportDialog.tsx @@ -6,7 +6,7 @@ import IconButton from '@mui/material/IconButton'; import CloseIcon from '@mui/icons-material/Close'; import Typography from '@mui/material/Typography'; -import { CardImportForm } from 'forms'; +import { CardImportForm } from '@app/forms'; import './CardImportDialog.css'; diff --git a/webclient/src/dialogs/KnownHostDialog/KnownHostDialog.tsx b/webclient/src/dialogs/KnownHostDialog/KnownHostDialog.tsx index bfc164e0d..193b0facd 100644 --- a/webclient/src/dialogs/KnownHostDialog/KnownHostDialog.tsx +++ b/webclient/src/dialogs/KnownHostDialog/KnownHostDialog.tsx @@ -8,7 +8,7 @@ import CloseIcon from '@mui/icons-material/Close'; import Typography from '@mui/material/Typography'; import { useTranslation } from 'react-i18next'; -import { KnownHostForm } from 'forms'; +import { KnownHostForm } from '@app/forms'; import './KnownHostDialog.css'; diff --git a/webclient/src/dialogs/RegistrationDialog/RegistrationDialog.tsx b/webclient/src/dialogs/RegistrationDialog/RegistrationDialog.tsx index 098306077..a4ab8aef5 100644 --- a/webclient/src/dialogs/RegistrationDialog/RegistrationDialog.tsx +++ b/webclient/src/dialogs/RegistrationDialog/RegistrationDialog.tsx @@ -7,7 +7,7 @@ import CloseIcon from '@mui/icons-material/Close'; import Typography from '@mui/material/Typography'; import { useTranslation } from 'react-i18next'; -import { RegisterForm } from 'forms'; +import { RegisterForm } from '@app/forms'; import './RegistrationDialog.css'; diff --git a/webclient/src/dialogs/RequestPasswordResetDialog/RequestPasswordResetDialog.tsx b/webclient/src/dialogs/RequestPasswordResetDialog/RequestPasswordResetDialog.tsx index 6b0158314..96c7b82a8 100644 --- a/webclient/src/dialogs/RequestPasswordResetDialog/RequestPasswordResetDialog.tsx +++ b/webclient/src/dialogs/RequestPasswordResetDialog/RequestPasswordResetDialog.tsx @@ -7,7 +7,7 @@ import CloseIcon from '@mui/icons-material/Close'; import Typography from '@mui/material/Typography'; import { useTranslation } from 'react-i18next'; -import { RequestPasswordResetForm } from 'forms'; +import { RequestPasswordResetForm } from '@app/forms'; import './RequestPasswordResetDialog.css'; diff --git a/webclient/src/dialogs/ResetPasswordDialog/ResetPasswordDialog.tsx b/webclient/src/dialogs/ResetPasswordDialog/ResetPasswordDialog.tsx index f4e1cc520..5c77aaef5 100644 --- a/webclient/src/dialogs/ResetPasswordDialog/ResetPasswordDialog.tsx +++ b/webclient/src/dialogs/ResetPasswordDialog/ResetPasswordDialog.tsx @@ -7,7 +7,7 @@ import CloseIcon from '@mui/icons-material/Close'; import Typography from '@mui/material/Typography'; import { useTranslation } from 'react-i18next'; -import { ResetPasswordForm } from 'forms'; +import { ResetPasswordForm } from '@app/forms'; import './ResetPasswordDialog.css'; diff --git a/webclient/src/forms/AccountActivationForm/AccountActivationForm.tsx b/webclient/src/forms/AccountActivationForm/AccountActivationForm.tsx index d2ec8e662..4ffc9e51a 100644 --- a/webclient/src/forms/AccountActivationForm/AccountActivationForm.tsx +++ b/webclient/src/forms/AccountActivationForm/AccountActivationForm.tsx @@ -6,9 +6,9 @@ import { useTranslation } from 'react-i18next'; import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; -import { InputField } from 'components'; -import { useReduxEffect } from 'hooks'; -import { ServerTypes } from 'store'; +import { InputField } from '@app/components'; +import { useReduxEffect } from '@app/hooks'; +import { ServerTypes } from '@app/store'; import './AccountActivationForm.css'; diff --git a/webclient/src/forms/CardImportForm/CardImportForm.tsx b/webclient/src/forms/CardImportForm/CardImportForm.tsx index 38a1893cb..ab959ea99 100644 --- a/webclient/src/forms/CardImportForm/CardImportForm.tsx +++ b/webclient/src/forms/CardImportForm/CardImportForm.tsx @@ -8,8 +8,8 @@ import Step from '@mui/material/Step'; import StepLabel from '@mui/material/StepLabel'; import CircularProgress from '@mui/material/CircularProgress'; -import { InputField, VirtualList } from 'components'; -import { cardImporterService, CardDTO, SetDTO, TokenDTO } from 'services'; +import { InputField, VirtualList } from '@app/components'; +import { cardImporterService, CardDTO, SetDTO, TokenDTO } from '@app/services'; import './CardImportForm.css'; diff --git a/webclient/src/forms/KnownHostForm/KnownHostForm.tsx b/webclient/src/forms/KnownHostForm/KnownHostForm.tsx index 5fd31e13f..86dc4f85e 100644 --- a/webclient/src/forms/KnownHostForm/KnownHostForm.tsx +++ b/webclient/src/forms/KnownHostForm/KnownHostForm.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'; import Button from '@mui/material/Button'; import AnchorLink from '@mui/material/Link'; -import { InputField } from 'components'; +import { InputField } from '@app/components'; import './KnownHostForm.css'; diff --git a/webclient/src/forms/LoginForm/LoginForm.tsx b/webclient/src/forms/LoginForm/LoginForm.tsx index 20c819304..4d6ee4e37 100644 --- a/webclient/src/forms/LoginForm/LoginForm.tsx +++ b/webclient/src/forms/LoginForm/LoginForm.tsx @@ -5,12 +5,11 @@ import { useTranslation } from 'react-i18next'; import Button from '@mui/material/Button'; -import { CheckboxField, InputField, KnownHosts } from 'components'; -import { useAutoConnect } from 'hooks'; -import { HostDTO, SettingDTO } from 'services'; -import { APP_USER } from 'types'; -import { useAppSelector } from 'store'; -import { Selectors as ServerSelectors } from 'store/server'; +import { CheckboxField, InputField, KnownHosts } from '@app/components'; +import { useAutoConnect } from '@app/hooks'; +import { HostDTO, SettingDTO } from '@app/services'; +import { App } from '@app/types'; +import { useAppSelector, ServerSelectors } from '@app/store'; import './LoginForm.css'; @@ -55,7 +54,7 @@ const LoginForm = ({ onSubmit, disableSubmitButton, onResetPassword }: LoginForm const { values } = form.getState(); useEffect(() => { - SettingDTO.get(APP_USER).then((userSetting: SettingDTO) => { + SettingDTO.get(App.APP_USER).then((userSetting: SettingDTO) => { if (userSetting?.autoConnect && !connectionAttemptMade) { HostDTO.getAll().then(hosts => { let lastSelectedHost = hosts.find(({ lastSelected }) => lastSelected); diff --git a/webclient/src/forms/RegisterForm/RegisterForm.tsx b/webclient/src/forms/RegisterForm/RegisterForm.tsx index 42b34fda0..b87d0ebf3 100644 --- a/webclient/src/forms/RegisterForm/RegisterForm.tsx +++ b/webclient/src/forms/RegisterForm/RegisterForm.tsx @@ -8,12 +8,12 @@ import { useTranslation } from 'react-i18next'; import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; -import { CountryDropdown, InputField, KnownHosts } from 'components'; -import { useReduxEffect } from 'hooks'; -import { ServerDispatch, ServerSelectors, ServerTypes } from 'store'; +import { CountryDropdown, InputField, KnownHosts } from '@app/components'; +import { useReduxEffect } from '@app/hooks'; +import { ServerDispatch, ServerSelectors, ServerTypes } from '@app/store'; import './RegisterForm.css'; -import { useToast } from 'components/Toast'; +import { useToast } from '@app/components'; const RegisterForm = ({ onSubmit }: RegisterFormProps) => { const { t } = useTranslation(); diff --git a/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.tsx b/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.tsx index 589f8da1e..46776cd37 100644 --- a/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.tsx +++ b/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.tsx @@ -7,9 +7,9 @@ import { useTranslation } from 'react-i18next'; import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; -import { InputField, KnownHosts } from 'components'; -import { useReduxEffect } from 'hooks'; -import { ServerTypes } from 'store'; +import { InputField, KnownHosts } from '@app/components'; +import { useReduxEffect } from '@app/hooks'; +import { ServerTypes } from '@app/store'; import './RequestPasswordResetForm.css'; diff --git a/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.tsx b/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.tsx index f0440363f..9e9984edf 100644 --- a/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.tsx +++ b/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.tsx @@ -6,9 +6,9 @@ import { useTranslation } from 'react-i18next'; import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; -import { InputField, KnownHosts } from 'components'; -import { useReduxEffect } from '../../hooks'; -import { ServerTypes } from '../../store'; +import { InputField, KnownHosts } from '@app/components'; +import { useReduxEffect } from '@app/hooks'; +import { ServerTypes } from '@app/store'; import './ResetPasswordForm.css'; diff --git a/webclient/src/forms/SearchForm/SearchForm.tsx b/webclient/src/forms/SearchForm/SearchForm.tsx index bca8a498f..07b1792ad 100644 --- a/webclient/src/forms/SearchForm/SearchForm.tsx +++ b/webclient/src/forms/SearchForm/SearchForm.tsx @@ -6,7 +6,7 @@ import Button from '@mui/material/Button'; import Divider from '@mui/material/Divider'; import Paper from '@mui/material/Paper'; -import { InputField, CheckboxField } from 'components'; +import { InputField, CheckboxField } from '@app/components'; import './SearchForm.css'; diff --git a/webclient/src/hooks/useAutoConnect.ts b/webclient/src/hooks/useAutoConnect.ts index 6a2690975..f8daa78aa 100644 --- a/webclient/src/hooks/useAutoConnect.ts +++ b/webclient/src/hooks/useAutoConnect.ts @@ -1,16 +1,16 @@ import { useEffect, useState } from 'react'; -import { SettingDTO } from 'services'; -import { APP_USER } from 'types'; +import { SettingDTO } from '@app/services'; +import { App } from '@app/types'; export function useAutoConnect() { const [setting, setSetting] = useState(undefined); const [autoConnect, setAutoConnect] = useState(undefined); useEffect(() => { - SettingDTO.get(APP_USER).then((setting: SettingDTO) => { + SettingDTO.get(App.APP_USER).then((setting: SettingDTO) => { if (!setting) { - setting = new SettingDTO(APP_USER); + setting = new SettingDTO(App.APP_USER); setting.save(); } diff --git a/webclient/src/i18n-backend.ts b/webclient/src/i18n-backend.ts index 0a92a3a0c..9c481f5eb 100644 --- a/webclient/src/i18n-backend.ts +++ b/webclient/src/i18n-backend.ts @@ -1,18 +1,18 @@ import { ModuleType } from 'i18next'; -import { Language } from 'types'; +import { App } from '@app/types'; class I18nBackend { static type: ModuleType = 'backend'; static BASE_URL = `${import.meta.env.BASE_URL}locales`; read(language, namespace, callback) { - if (!Language[language]) { + if (!language[App.Language]) { callback(true, null); return; } - fetch(`${I18nBackend.BASE_URL}/${Language[language]}/${namespace}.json`) + fetch(`${I18nBackend.BASE_URL}/${language[App.Language]}/${namespace}.json`) .then(resp => resp.json().then(json => callback(null, json))) .catch(error => callback(error, null)); } diff --git a/webclient/src/i18n.ts b/webclient/src/i18n.ts index ef1885965..0c37911f5 100644 --- a/webclient/src/i18n.ts +++ b/webclient/src/i18n.ts @@ -3,7 +3,7 @@ import LanguageDetector from 'i18next-browser-languagedetector'; import ICU from 'i18next-icu'; import { initReactI18next } from 'react-i18next'; -import { Language } from 'types'; +import { App } from '@app/types'; import I18nBackend from './i18n-backend'; @@ -17,9 +17,9 @@ i18n .use(initReactI18next) // for all options read: https://www.i18next.com/overview/configuration-options .init({ - fallbackLng: Language['en-US'], + fallbackLng: App.Language['en-US'], resources: { - [Language['en-US']]: { translation }, + [App.Language['en-US']]: { translation }, }, partialBundledLanguages: true, diff --git a/webclient/src/index.tsx b/webclient/src/index.tsx index 959d2adc7..ffebe2a70 100644 --- a/webclient/src/index.tsx +++ b/webclient/src/index.tsx @@ -1,9 +1,13 @@ +// MUST be first: installs BigInt.prototype.toJSON before any module that +// creates the Redux store or connects to Redux DevTools. +import './polyfills'; + import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { StyledEngineProvider } from '@mui/material'; import { ThemeProvider } from '@mui/material/styles'; -import { AppShell } from './containers'; +import { AppShell } from '@app/containers'; import { materialTheme } from './material-theme'; import './i18n'; diff --git a/webclient/src/polyfills.ts b/webclient/src/polyfills.ts new file mode 100644 index 000000000..d18a0c94c --- /dev/null +++ b/webclient/src/polyfills.ts @@ -0,0 +1,19 @@ +// Runtime polyfills that must execute before any other application module. +// Import this file first from `src/index.tsx`. + +// ── BigInt.prototype.toJSON ─────────────────────────────────────────────────── +// Protobuf-ES maps proto `int64`/`uint64` fields to native `BigInt`. Those +// land in Redux state (e.g. `ServerInfo_User.accountageSecs`, +// `Response_Register.deniedEndTime`, the outbound `cmdId`), and any consumer +// that JSON-stringifies state — notably the Redux DevTools browser +// extension, but also logging and error-boundary dumps — throws with +// "Do not know how to serialize a BigInt" because `BigInt.prototype` has no +// `toJSON`. Installing one globally makes `JSON.stringify` coerce +// `BigInt → string` instead of throwing. Coercion is lossy but only affects +// serialized representations; the in-memory Redux state still holds real +// `BigInt`s and every consumer reads them via the generated proto accessors. +(BigInt.prototype as unknown as { toJSON: () => string }).toJSON = function bigIntToJSON() { + return this.toString(); +}; + +export {}; diff --git a/webclient/src/services/dexie/DexieDTOs/CardDTO.ts b/webclient/src/services/dexie/DexieDTOs/CardDTO.ts index a060aedb1..dd43681ea 100644 --- a/webclient/src/services/dexie/DexieDTOs/CardDTO.ts +++ b/webclient/src/services/dexie/DexieDTOs/CardDTO.ts @@ -1,8 +1,8 @@ -import { Card } from 'types'; +import { App } from '@app/types'; import { dexieService } from '../DexieService'; -export class CardDTO extends Card { +export class CardDTO extends App.Card { save() { return dexieService.cards.put(this); } diff --git a/webclient/src/services/dexie/DexieDTOs/HostDTO.ts b/webclient/src/services/dexie/DexieDTOs/HostDTO.ts index 17f600626..e07440f64 100644 --- a/webclient/src/services/dexie/DexieDTOs/HostDTO.ts +++ b/webclient/src/services/dexie/DexieDTOs/HostDTO.ts @@ -1,14 +1,14 @@ import { IndexableType } from 'dexie'; -import { Host } from 'types'; +import { App } from '@app/types'; import { dexieService } from '../DexieService'; -export class HostDTO extends Host { +export class HostDTO extends App.Host { save() { return dexieService.hosts.put(this); } - static add(host: Host): Promise { + static add(host: App.Host): Promise { return dexieService.hosts.add(host); } @@ -20,7 +20,7 @@ export class HostDTO extends Host { return dexieService.hosts.toArray(); } - static bulkAdd(hosts: Host[]): Promise { + static bulkAdd(hosts: App.Host[]): Promise { return dexieService.hosts.bulkAdd(hosts); } diff --git a/webclient/src/services/dexie/DexieDTOs/SetDTO.ts b/webclient/src/services/dexie/DexieDTOs/SetDTO.ts index 6ae2dd20a..3bd02903c 100644 --- a/webclient/src/services/dexie/DexieDTOs/SetDTO.ts +++ b/webclient/src/services/dexie/DexieDTOs/SetDTO.ts @@ -1,8 +1,8 @@ -import { Set } from 'types'; +import { App } from '@app/types'; import { dexieService } from '../DexieService'; -export class SetDTO extends Set { +export class SetDTO extends App.Set { save() { return dexieService.sets.put(this); } diff --git a/webclient/src/services/dexie/DexieDTOs/SettingDTO.ts b/webclient/src/services/dexie/DexieDTOs/SettingDTO.ts index bbfa1680d..357ebc5a7 100644 --- a/webclient/src/services/dexie/DexieDTOs/SettingDTO.ts +++ b/webclient/src/services/dexie/DexieDTOs/SettingDTO.ts @@ -1,8 +1,8 @@ -import { Setting } from 'types'; +import { App } from '@app/types'; import { dexieService } from '../DexieService'; -export class SettingDTO extends Setting { +export class SettingDTO extends App.Setting { constructor(user) { super(); diff --git a/webclient/src/services/dexie/DexieDTOs/TokenDTO.ts b/webclient/src/services/dexie/DexieDTOs/TokenDTO.ts index 35d537709..04199e63d 100644 --- a/webclient/src/services/dexie/DexieDTOs/TokenDTO.ts +++ b/webclient/src/services/dexie/DexieDTOs/TokenDTO.ts @@ -1,8 +1,8 @@ -import { Token } from 'types'; +import { App } from '@app/types'; import { dexieService } from '../DexieService'; -export class TokenDTO extends Token { +export class TokenDTO extends App.Token { save() { return dexieService.tokens.put(this); } diff --git a/webclient/src/setupTests.ts b/webclient/src/setupTests.ts index a4fe5ea8d..d58601667 100644 --- a/webclient/src/setupTests.ts +++ b/webclient/src/setupTests.ts @@ -1,9 +1,40 @@ -// ensure jest-dom is always available during testing to cut down on boilerplate +// Install runtime polyfills (BigInt.prototype.toJSON) before any module +// under test loads — matches the production boot order in src/index.tsx. +import './polyfills'; + +// Ensure jest-dom matchers are available in every test file. import '@testing-library/jest-dom/vitest'; -// With isolate: false, all test files share the same module context. -// Restore all mocks/spies after each test to prevent leakage between tests. +// ── Global mock hygiene under `isolate: false` ──────────────────────────────── +// +// Vitest is configured with `test.isolate: false` for speed — every spec file +// in a worker shares the same module graph and the same `vi.mock` factories. +// Without aggressive per-test cleanup, state leaks trivially between tests: +// +// - A test accumulates `.mock.calls` on a shared `vi.fn()`. Later tests +// either see stale history or accidentally match on prior invocations. +// - A test installs `vi.spyOn` on a real method. Without restore, the spy +// persists into every following test and file. +// - A test swaps to fake timers. Real-time code in later tests hangs. +// +// `vi.clearAllMocks()` clears `.mock.calls` on every tracked mock without +// touching implementations — safe for module factories that produce `vi.fn()` +// instances at the top of a spec file and rely on those instances sticking +// around. `vi.restoreAllMocks()` restores original implementations on +// `vi.spyOn` targets. `vi.useRealTimers()` drops any fake-timer installation. +// +// NOTE: we intentionally do NOT call `vi.resetAllMocks()` — it resets the +// implementations of `vi.fn()` instances created inside `vi.mock(...)` +// factories, which breaks any spec that expects those mocks to persist +// across tests in the same file (e.g. `store.dispatch` mocked once at file +// load). +// +// If a specific test needs to install its own `mockReturnValue` / +// `mockImplementation`, it should set it in that test's body and rely on +// the next test overwriting or the global `clearAllMocks` clearing calls — +// it should NOT assume the mock is reset to its factory default automatically. afterEach(() => { + vi.clearAllMocks(); vi.restoreAllMocks(); vi.useRealTimers(); }); diff --git a/webclient/src/store/common/SortUtil.spec.ts b/webclient/src/store/common/SortUtil.spec.ts index acd117506..5a79b0e29 100644 --- a/webclient/src/store/common/SortUtil.spec.ts +++ b/webclient/src/store/common/SortUtil.spec.ts @@ -1,6 +1,5 @@ import { create } from '@bufbuild/protobuf'; -import { SortDirection } from 'types'; -import { ServerInfo_UserSchema } from 'generated/proto/serverinfo_user_pb'; +import { App, Data } from '@app/types'; import SortUtil from './SortUtil'; // ── sortByField ─────────────────────────────────────────────────────────────── @@ -8,48 +7,48 @@ import SortUtil from './SortUtil'; describe('sortByField', () => { it('sorts string field ASC alphabetically', () => { const arr = [{ name: 'Zane' }, { name: 'Alice' }, { name: 'Bob' }]; - SortUtil.sortByField(arr, { field: 'name', order: SortDirection.ASC }); + SortUtil.sortByField(arr, { field: 'name', order: App.SortDirection.ASC }); expect(arr.map(x => x.name)).toEqual(['Alice', 'Bob', 'Zane']); }); it('sorts string field DESC reverse-alphabetically', () => { const arr = [{ name: 'Alice' }, { name: 'Zane' }, { name: 'Bob' }]; - SortUtil.sortByField(arr, { field: 'name', order: SortDirection.DESC }); + SortUtil.sortByField(arr, { field: 'name', order: App.SortDirection.DESC }); expect(arr.map(x => x.name)).toEqual(['Zane', 'Bob', 'Alice']); }); it('sorts number field ASC', () => { const arr = [{ score: 30 }, { score: 10 }, { score: 20 }]; - SortUtil.sortByField(arr, { field: 'score', order: SortDirection.ASC }); + SortUtil.sortByField(arr, { field: 'score', order: App.SortDirection.ASC }); expect(arr.map(x => x.score)).toEqual([10, 20, 30]); }); it('sorts number field DESC', () => { const arr = [{ score: 10 }, { score: 30 }, { score: 20 }]; - SortUtil.sortByField(arr, { field: 'score', order: SortDirection.DESC }); + SortUtil.sortByField(arr, { field: 'score', order: App.SortDirection.DESC }); expect(arr.map(x => x.score)).toEqual([30, 20, 10]); }); it('no-ops on empty array without error', () => { - expect(() => SortUtil.sortByField([], { field: 'name', order: SortDirection.ASC })).not.toThrow(); + expect(() => SortUtil.sortByField([], { field: 'name', order: App.SortDirection.ASC })).not.toThrow(); }); it('sorts with nested dot-notation field', () => { const arr = [{ meta: { rank: 3 } }, { meta: { rank: 1 } }, { meta: { rank: 2 } }]; - SortUtil.sortByField(arr, { field: 'meta.rank', order: SortDirection.ASC }); + SortUtil.sortByField(arr, { field: 'meta.rank', order: App.SortDirection.ASC }); expect(arr.map(x => x.meta.rank)).toEqual([1, 2, 3]); }); it('throws when field resolves to a non-string, non-number value', () => { const arr = [{ data: {} }, { data: {} }]; - expect(() => SortUtil.sortByField(arr, { field: 'data', order: SortDirection.ASC })).toThrow( + expect(() => SortUtil.sortByField(arr, { field: 'data', order: App.SortDirection.ASC })).toThrow( 'SortField must resolve to either a string or number' ); }); it('sorts empty-string values to the bottom when sorting ASC', () => { const arr = [{ name: '' }, { name: 'Alice' }, { name: '' }]; - SortUtil.sortByField(arr, { field: 'name', order: SortDirection.ASC }); + SortUtil.sortByField(arr, { field: 'name', order: App.SortDirection.ASC }); expect(arr[0].name).toBe('Alice'); expect(arr[1].name).toBe(''); expect(arr[2].name).toBe(''); @@ -66,8 +65,8 @@ describe('sortByFields', () => { { group: 'B', name: 'Alice' }, ]; SortUtil.sortByFields(arr, [ - { field: 'group', order: SortDirection.ASC }, - { field: 'name', order: SortDirection.ASC }, + { field: 'group', order: App.SortDirection.ASC }, + { field: 'name', order: App.SortDirection.ASC }, ]); expect(arr.map(x => x.group)).toEqual(['A', 'B', 'C']); }); @@ -79,8 +78,8 @@ describe('sortByFields', () => { { group: 'B', name: 'Bob' }, ]; SortUtil.sortByFields(arr, [ - { field: 'group', order: SortDirection.ASC }, - { field: 'name', order: SortDirection.ASC }, + { field: 'group', order: App.SortDirection.ASC }, + { field: 'name', order: App.SortDirection.ASC }, ]); expect(arr[0]).toEqual({ group: 'A', name: 'Alice' }); expect(arr[1]).toEqual({ group: 'A', name: 'Zane' }); @@ -89,20 +88,20 @@ describe('sortByFields', () => { it('no-ops on empty array', () => { expect(() => - SortUtil.sortByFields([], [{ field: 'name', order: SortDirection.ASC }]) + SortUtil.sortByFields([], [{ field: 'name', order: App.SortDirection.ASC }]) ).not.toThrow(); }); it('sorts by number field', () => { const arr = [{ score: 3 }, { score: 1 }, { score: 2 }]; - SortUtil.sortByFields(arr, [{ field: 'score', order: SortDirection.ASC }]); + SortUtil.sortByFields(arr, [{ field: 'score', order: App.SortDirection.ASC }]); expect(arr.map(x => x.score)).toEqual([1, 2, 3]); }); it('returns 0 when all items tie on every sort key', () => { const arr = [{ score: 5 }, { score: 5 }]; expect(() => - SortUtil.sortByFields(arr, [{ field: 'score', order: SortDirection.ASC }]) + SortUtil.sortByFields(arr, [{ field: 'score', order: App.SortDirection.ASC }]) ).not.toThrow(); expect(arr).toHaveLength(2); }); @@ -110,7 +109,7 @@ describe('sortByFields', () => { it('throws when field resolves to a non-string, non-number value', () => { const arr = [{ data: {} }, { data: {} }]; expect(() => - SortUtil.sortByFields(arr, [{ field: 'data', order: SortDirection.ASC }]) + SortUtil.sortByFields(arr, [{ field: 'data', order: App.SortDirection.ASC }]) ).toThrow('SortField must resolve to either a string or number'); }); }); @@ -120,11 +119,11 @@ describe('sortByFields', () => { describe('sortUsersByField', () => { it('sorts by userLevel DESC first, then name ASC', () => { const users = [ - create(ServerInfo_UserSchema, { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }), - create(ServerInfo_UserSchema, { name: 'Bob', userLevel: 8, accountageSecs: 0n, privlevel: '' }), - create(ServerInfo_UserSchema, { name: 'Carol', userLevel: 1, accountageSecs: 0n, privlevel: '' }), + create(Data.ServerInfo_UserSchema, { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }), + create(Data.ServerInfo_UserSchema, { name: 'Bob', userLevel: 8, accountageSecs: 0n, privlevel: '' }), + create(Data.ServerInfo_UserSchema, { name: 'Carol', userLevel: 1, accountageSecs: 0n, privlevel: '' }), ]; - SortUtil.sortUsersByField(users, { field: 'name', order: SortDirection.ASC }); + SortUtil.sortUsersByField(users, { field: 'name', order: App.SortDirection.ASC }); expect(users[0].name).toBe('Bob'); expect(users[1].name).toBe('Alice'); expect(users[2].name).toBe('Carol'); @@ -132,17 +131,17 @@ describe('sortUsersByField', () => { it('no-ops on empty array', () => { expect(() => - SortUtil.sortUsersByField([], { field: 'name', order: SortDirection.ASC }) + SortUtil.sortUsersByField([], { field: 'name', order: App.SortDirection.ASC }) ).not.toThrow(); }); it('returns 0 (stable) when two users tie on both userLevel and name', () => { const users = [ - create(ServerInfo_UserSchema, { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }), - create(ServerInfo_UserSchema, { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }), + create(Data.ServerInfo_UserSchema, { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }), + create(Data.ServerInfo_UserSchema, { name: 'Alice', userLevel: 1, accountageSecs: 0n, privlevel: '' }), ]; expect(() => - SortUtil.sortUsersByField(users, { field: 'name', order: SortDirection.ASC }) + SortUtil.sortUsersByField(users, { field: 'name', order: App.SortDirection.ASC }) ).not.toThrow(); expect(users).toHaveLength(2); }); @@ -152,18 +151,18 @@ describe('sortUsersByField', () => { describe('toggleSortBy', () => { it('same field + ASC → returns DESC', () => { - const result = SortUtil.toggleSortBy('name', { field: 'name', order: SortDirection.ASC }); - expect(result).toEqual({ field: 'name', order: SortDirection.DESC }); + const result = SortUtil.toggleSortBy('name', { field: 'name', order: App.SortDirection.ASC }); + expect(result).toEqual({ field: 'name', order: App.SortDirection.DESC }); }); it('same field + DESC → returns ASC', () => { - const result = SortUtil.toggleSortBy('name', { field: 'name', order: SortDirection.DESC }); - expect(result).toEqual({ field: 'name', order: SortDirection.ASC }); + const result = SortUtil.toggleSortBy('name', { field: 'name', order: App.SortDirection.DESC }); + expect(result).toEqual({ field: 'name', order: App.SortDirection.ASC }); }); it('different field → returns ASC regardless of current order', () => { - const result = SortUtil.toggleSortBy('score', { field: 'name', order: SortDirection.DESC }); - expect(result).toEqual({ field: 'score', order: SortDirection.ASC }); + const result = SortUtil.toggleSortBy('score', { field: 'name', order: App.SortDirection.DESC }); + expect(result).toEqual({ field: 'score', order: App.SortDirection.ASC }); }); }); @@ -173,7 +172,7 @@ describe('resolveFieldChain via sortByField (numeric index)', () => { it('resolves numeric index in dot-notation chain', () => { const arr = [{ items: ['c', 'b', 'a'] }, { items: ['z', 'y', 'x'] }]; // Sort by items.0 which is the first element of the items array - SortUtil.sortByField(arr, { field: 'items.0', order: SortDirection.ASC }); + SortUtil.sortByField(arr, { field: 'items.0', order: App.SortDirection.ASC }); expect(arr[0].items[0]).toBe('c'); expect(arr[1].items[0]).toBe('z'); }); diff --git a/webclient/src/store/common/SortUtil.ts b/webclient/src/store/common/SortUtil.ts index 8a15b931f..f480d1287 100644 --- a/webclient/src/store/common/SortUtil.ts +++ b/webclient/src/store/common/SortUtil.ts @@ -1,8 +1,7 @@ -import { SortBy, SortDirection } from 'types'; -import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; +import { App, Data } from '@app/types'; export default class SortUtil { - static sortByField(arr: T[], sortBy: SortBy): void { + static sortByField(arr: T[], sortBy: App.SortBy): void { if (arr.length) { const field = SortUtil.resolveFieldChain(arr[0], sortBy.field); const fieldType = typeof field; @@ -21,7 +20,7 @@ export default class SortUtil { } } - static sortByFields(arr: T[], sorts: SortBy[]) { + static sortByFields(arr: T[], sorts: App.SortBy[]) { if (arr.length) { arr.sort((a, b) => { for (let i = 0; i < sorts.length; i++) { @@ -52,35 +51,35 @@ export default class SortUtil { } } - static sortUsersByField(users: ServerInfo_User[], sortBy: SortBy) { + static sortUsersByField(users: Data.ServerInfo_User[], sortBy: App.SortBy) { if (users.length) { users.sort((a, b) => SortUtil.userComparator(a, b, sortBy)) } } - static toggleSortBy(field: F, sortBy: SortBy): { field: F; order: SortDirection } { + static toggleSortBy(field: F, sortBy: App.SortBy): { field: F; order: App.SortDirection } { const sameField = field === sortBy.field; - const isASC = sortBy.order === SortDirection.ASC; + const isASC = sortBy.order === App.SortDirection.ASC; return { field, - order: sameField && isASC ? SortDirection.DESC : SortDirection.ASC + order: sameField && isASC ? App.SortDirection.DESC : App.SortDirection.ASC } } - private static sortByNumber(arr: T[], sortBy: SortBy): void { + private static sortByNumber(arr: T[], sortBy: App.SortBy): void { arr.sort((a, b) => SortUtil.numberComparator(a, b, sortBy)); } - private static sortByString(arr: T[], sortBy: SortBy): void { + private static sortByString(arr: T[], sortBy: App.SortBy): void { arr.sort((a, b) => SortUtil.stringComparator(a, b, sortBy)); } - private static userComparator(a: ServerInfo_User, b: ServerInfo_User, sortBy: SortBy, sortByUserLevel = true) { + private static userComparator(a: Data.ServerInfo_User, b: Data.ServerInfo_User, sortBy: App.SortBy, sortByUserLevel = true) { if (sortByUserLevel) { const adminSortBy = { field: 'userLevel', - order: SortDirection.DESC + order: App.SortDirection.DESC }; const adminSorted = SortUtil.numberComparator(a, b, adminSortBy); @@ -99,18 +98,18 @@ export default class SortUtil { return 0; } - private static numberComparator(a: T, b: T, { field, order }: SortBy) { + private static numberComparator(a: T, b: T, { field, order }: App.SortBy) { const aResolved = SortUtil.resolveFieldChain(a, field); const bResolved = SortUtil.resolveFieldChain(b, field); - if (order === SortDirection.ASC) { + if (order === App.SortDirection.ASC) { return aResolved - bResolved; } else { return bResolved - aResolved; } } - private static stringComparator(a: T, b: T, { field, order }: SortBy) { + private static stringComparator(a: T, b: T, { field, order }: App.SortBy) { const aResolved = SortUtil.resolveFieldChain(a, field); const bResolved = SortUtil.resolveFieldChain(b, field); @@ -125,7 +124,7 @@ export default class SortUtil { return -1; } - if (order === SortDirection.ASC) { + if (order === App.SortDirection.ASC) { return aResolved.localeCompare(bResolved); } else { return bResolved.localeCompare(aResolved); diff --git a/webclient/src/store/common/normalizers.spec.ts b/webclient/src/store/common/normalizers.spec.ts index ddd0a8f1e..f157b93dd 100644 --- a/webclient/src/store/common/normalizers.spec.ts +++ b/webclient/src/store/common/normalizers.spec.ts @@ -1,18 +1,15 @@ import { normalizeRoomInfo, normalizeGameObject, normalizeLogs, normalizeBannedUserError, normalizeUserMessage } from './normalizers'; import { create } from '@bufbuild/protobuf'; -import { ServerInfo_RoomSchema } from 'generated/proto/serverinfo_room_pb'; -import { ServerInfo_GameSchema } from 'generated/proto/serverinfo_game_pb'; -import { Event_RoomSaySchema } from 'generated/proto/event_room_say_pb'; -import { Message } from 'types'; +import { Data, Enriched } from '@app/types'; describe('normalizeRoomInfo', () => { it('builds gametypeMap from gametypeList and normalises games', () => { - const room = create(ServerInfo_RoomSchema, { + const room = create(Data.ServerInfo_RoomSchema, { roomId: 1, name: 'Lobby', gametypeList: [{ gameTypeId: 1, description: 'Standard' }], gameList: [ - create(ServerInfo_GameSchema, { gameId: 10, gameTypes: [1], description: 'My Game' }), + create(Data.ServerInfo_GameSchema, { gameId: 10, gameTypes: [1], description: 'My Game' }), ], }); @@ -25,7 +22,7 @@ describe('normalizeRoomInfo', () => { }); it('handles room with empty gametypeList', () => { - const room = create(ServerInfo_RoomSchema, { roomId: 2, name: 'Empty' }); + const room = create(Data.ServerInfo_RoomSchema, { roomId: 2, name: 'Empty' }); const result = normalizeRoomInfo(room); expect(result.gametypeMap).toEqual({}); expect(result.gameList).toEqual([]); @@ -34,19 +31,19 @@ describe('normalizeRoomInfo', () => { describe('normalizeGameObject', () => { it('maps gameTypes[0] to gameType string via gametypeMap', () => { - const game = create(ServerInfo_GameSchema, { gameId: 1, gameTypes: [5] }); + const game = create(Data.ServerInfo_GameSchema, { gameId: 1, gameTypes: [5] }); const result = normalizeGameObject(game, { 5: 'Legacy' }); expect(result.gameType).toBe('Legacy'); }); it('returns empty string when no gameTypes', () => { - const game = create(ServerInfo_GameSchema, { gameId: 2 }); + const game = create(Data.ServerInfo_GameSchema, { gameId: 2 }); const result = normalizeGameObject(game, {}); expect(result.gameType).toBe(''); }); it('fills empty description with empty string', () => { - const game = create(ServerInfo_GameSchema, { gameId: 3 }); + const game = create(Data.ServerInfo_GameSchema, { gameId: 3 }); const result = normalizeGameObject(game, {}); expect(result.description).toBe(''); }); @@ -55,10 +52,10 @@ describe('normalizeGameObject', () => { describe('normalizeLogs', () => { it('groups logs by targetType', () => { const logs = [ - { targetType: 'room' }, - { targetType: 'game' }, - { targetType: 'room' }, - ] as any[]; + create(Data.ServerInfo_ChatMessageSchema, { targetType: 'room' }), + create(Data.ServerInfo_ChatMessageSchema, { targetType: 'game' }), + create(Data.ServerInfo_ChatMessageSchema, { targetType: 'room' }), + ]; const result = normalizeLogs(logs); expect(result.room).toHaveLength(2); expect(result.game).toHaveLength(1); @@ -71,7 +68,7 @@ describe('normalizeLogs', () => { }); describe('normalizeBannedUserError', () => { - it('returns permanently banned message when endTime is 0', () => { + it('returns permanently banned Enriched.Message when endTime is 0', () => { expect(normalizeBannedUserError('', 0)).toBe('You are permanently banned'); }); @@ -92,11 +89,11 @@ describe('normalizeBannedUserError', () => { }); describe('normalizeUserMessage', () => { - const makeMsg = (fields: Partial): Message => ({ - ...create(Event_RoomSaySchema), + const makeMsg = (fields: Partial): Enriched.Message => ({ + ...create(Data.Event_RoomSaySchema), timeReceived: 0, ...fields, - } as Message); + } as Enriched.Message); it('prepends "name: " to message when name is present', () => { const result = normalizeUserMessage(makeMsg({ name: 'Alice', message: 'hello' })); diff --git a/webclient/src/store/common/normalizers.ts b/webclient/src/store/common/normalizers.ts index a68949984..ac3af7ce4 100644 --- a/webclient/src/store/common/normalizers.ts +++ b/webclient/src/store/common/normalizers.ts @@ -1,19 +1,15 @@ -import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; -import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; -import type { ServerInfo_GameType } from 'generated/proto/serverinfo_gametype_pb'; -import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb'; -import { Game, GametypeMap, LogGroups, Message, Room } from 'types'; +import { Data, Enriched } from '@app/types'; /** Flatten a gametype list into a lookup map of { gameTypeId → description }. */ -export function normalizeGametypeMap(gametypeList: ServerInfo_GameType[]): GametypeMap { - return gametypeList.reduce((map, type) => { +export function normalizeGametypeMap(gametypeList: Data.ServerInfo_GameType[]): Enriched.GametypeMap { + return gametypeList.reduce((map, type) => { map[type.gameTypeId] = type.description; return map; }, {}); } /** Flatten room gameTypes into a map object and normalize all games inside. */ -export function normalizeRoomInfo(roomInfo: ServerInfo_Room): Room { +export function normalizeRoomInfo(roomInfo: Data.ServerInfo_Room): Enriched.Room { const gametypeMap = normalizeGametypeMap(roomInfo.gametypeList); const gameList = roomInfo.gameList.map( @@ -29,7 +25,7 @@ export function normalizeRoomInfo(roomInfo: ServerInfo_Room): Room { } /** Flatten gameTypes[] into a gameType string; fill in default sortable values. */ -export function normalizeGameObject(game: ServerInfo_Game, gametypeMap: GametypeMap): Game { +export function normalizeGameObject(game: Data.ServerInfo_Game, gametypeMap: Enriched.GametypeMap): Enriched.Game { const { gameTypes, description } = game; const hasType = gameTypes && gameTypes.length; @@ -41,13 +37,13 @@ export function normalizeGameObject(game: ServerInfo_Game, gametypeMap: Gametype } /** Group a flat LogItem[] into { room, game, chat } buckets for the server store. */ -export function normalizeLogs(logs: ServerInfo_ChatMessage[]): LogGroups { +export function normalizeLogs(logs: Data.ServerInfo_ChatMessage[]): Enriched.LogGroups { return logs.reduce((obj, log) => { - const type = log.targetType as keyof LogGroups; + const type = log.targetType as keyof Enriched.LogGroups; obj[type] = obj[type] || []; obj[type]!.push(log); return obj; - }, {} as LogGroups); + }, {} as Enriched.LogGroups); } /** @@ -56,7 +52,7 @@ export function normalizeLogs(logs: ServerInfo_ChatMessage[]): LogGroups { * so this is a no-op for those. * Returns a new Message — does not mutate the original. */ -export function normalizeUserMessage(message: Message): Message { +export function normalizeUserMessage(message: Enriched.Message): Enriched.Message { if (!message.name) { return message; } diff --git a/webclient/src/store/game/__mocks__/fixtures.ts b/webclient/src/store/game/__mocks__/fixtures.ts index 87a9ea346..cd5d1f15d 100644 --- a/webclient/src/store/game/__mocks__/fixtures.ts +++ b/webclient/src/store/game/__mocks__/fixtures.ts @@ -1,18 +1,10 @@ -import { ProtoInit } from 'types'; -import type { ServerInfo_Card } from 'generated/proto/serverinfo_card_pb'; -import type { ServerInfo_Counter } from 'generated/proto/serverinfo_counter_pb'; -import type { ServerInfo_Arrow } from 'generated/proto/serverinfo_arrow_pb'; -import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb'; +import type { MessageInitShape } from '@bufbuild/protobuf'; +import { Data } from '@app/types'; import { create } from '@bufbuild/protobuf'; -import { ServerInfo_CardSchema } from 'generated/proto/serverinfo_card_pb'; -import { ServerInfo_CounterSchema } from 'generated/proto/serverinfo_counter_pb'; -import { colorSchema } from 'generated/proto/color_pb'; -import { ServerInfo_ArrowSchema } from 'generated/proto/serverinfo_arrow_pb'; -import { ServerInfo_PlayerPropertiesSchema } from 'generated/proto/serverinfo_playerproperties_pb'; import { GameEntry, GamesState, PlayerEntry, ZoneEntry } from '../game.interfaces'; -export function makeCard(overrides: ProtoInit = {}): ServerInfo_Card { - return create(ServerInfo_CardSchema, { +export function makeCard(overrides: MessageInitShape = {}): Data.ServerInfo_Card { + return create(Data.ServerInfo_CardSchema, { id: 1, name: 'Test Card', x: 0, @@ -34,19 +26,19 @@ export function makeCard(overrides: ProtoInit = {}): ServerInfo }); } -export function makeCounter(overrides: ProtoInit = {}): ServerInfo_Counter { - return create(ServerInfo_CounterSchema, { +export function makeCounter(overrides: MessageInitShape = {}): Data.ServerInfo_Counter { + return create(Data.ServerInfo_CounterSchema, { id: 1, name: 'Life', - counterColor: create(colorSchema, { r: 0, g: 0, b: 0, a: 255 }), + counterColor: create(Data.colorSchema, { r: 0, g: 0, b: 0, a: 255 }), radius: 1, count: 20, ...overrides, }); } -export function makeArrow(overrides: ProtoInit = {}): ServerInfo_Arrow { - return create(ServerInfo_ArrowSchema, { +export function makeArrow(overrides: MessageInitShape = {}): Data.ServerInfo_Arrow { + return create(Data.ServerInfo_ArrowSchema, { id: 1, startPlayerId: 1, startZone: 'table', @@ -54,7 +46,7 @@ export function makeArrow(overrides: ProtoInit = {}): ServerIn targetPlayerId: 1, targetZone: 'table', targetCardId: 2, - arrowColor: create(colorSchema, { r: 255, g: 0, b: 0, a: 255 }), + arrowColor: create(Data.colorSchema, { r: 255, g: 0, b: 0, a: 255 }), ...overrides, }); } @@ -72,8 +64,10 @@ export function makeZoneEntry(overrides: Partial = {}): ZoneEntry { }; } -export function makePlayerProperties(overrides: ProtoInit = {}): ServerInfo_PlayerProperties { - return create(ServerInfo_PlayerPropertiesSchema, { +export function makePlayerProperties( + overrides: MessageInitShape = {}, +): Data.ServerInfo_PlayerProperties { + return create(Data.ServerInfo_PlayerPropertiesSchema, { playerId: 1, spectator: false, conceded: false, diff --git a/webclient/src/store/game/game.actions.spec.ts b/webclient/src/store/game/game.actions.spec.ts index 00afd55d9..b75282943 100644 --- a/webclient/src/store/game/game.actions.spec.ts +++ b/webclient/src/store/game/game.actions.spec.ts @@ -1,32 +1,13 @@ import { create } from '@bufbuild/protobuf'; +import { Data } from '@app/types'; import { Actions } from './game.actions'; import { Types } from './game.types'; import { makeArrow, makeCard, makeCounter, - makeGameEntry, makePlayerProperties, } from './__mocks__/fixtures'; -import { Event_GameStateChangedSchema } from 'generated/proto/event_game_state_changed_pb'; -import { Event_MoveCardSchema } from 'generated/proto/event_move_card_pb'; -import { Event_FlipCardSchema } from 'generated/proto/event_flip_card_pb'; -import { Event_DestroyCardSchema } from 'generated/proto/event_destroy_card_pb'; -import { Event_AttachCardSchema } from 'generated/proto/event_attach_card_pb'; -import { Event_CreateTokenSchema } from 'generated/proto/event_create_token_pb'; -import { Event_SetCardAttrSchema } from 'generated/proto/event_set_card_attr_pb'; -import { Event_SetCardCounterSchema } from 'generated/proto/event_set_card_counter_pb'; -import { Event_CreateArrowSchema } from 'generated/proto/event_create_arrow_pb'; -import { Event_DeleteArrowSchema } from 'generated/proto/event_delete_arrow_pb'; -import { Event_CreateCounterSchema } from 'generated/proto/event_create_counter_pb'; -import { Event_SetCounterSchema } from 'generated/proto/event_set_counter_pb'; -import { Event_DelCounterSchema } from 'generated/proto/event_del_counter_pb'; -import { Event_DrawCardsSchema } from 'generated/proto/event_draw_cards_pb'; -import { Event_RevealCardsSchema } from 'generated/proto/event_reveal_cards_pb'; -import { Event_ShuffleSchema } from 'generated/proto/event_shuffle_pb'; -import { Event_RollDieSchema } from 'generated/proto/event_roll_die_pb'; -import { Event_DumpZoneSchema } from 'generated/proto/event_dump_zone_pb'; -import { Event_ChangeZonePropertiesSchema } from 'generated/proto/event_change_zone_properties_pb'; describe('Actions', () => { it('clearStore', () => { @@ -34,8 +15,8 @@ describe('Actions', () => { }); it('gameJoined', () => { - const entry = makeGameEntry(); - expect(Actions.gameJoined(1, entry)).toEqual({ type: Types.GAME_JOINED, gameId: 1, gameEntry: entry }); + const data = create(Data.Event_GameJoinedSchema, { hostId: 1, playerId: 2 }); + expect(Actions.gameJoined(data)).toEqual({ type: Types.GAME_JOINED, data }); }); it('gameLeft', () => { @@ -51,7 +32,7 @@ describe('Actions', () => { }); it('gameStateChanged', () => { - const data = create(Event_GameStateChangedSchema, { + const data = create(Data.Event_GameStateChangedSchema, { playerList: [], gameStarted: true, activePlayerId: 1, activePhase: 0, secondsElapsed: 0 }); expect(Actions.gameStateChanged(1, data)).toEqual({ type: Types.GAME_STATE_CHANGED, gameId: 1, data }); @@ -81,85 +62,85 @@ describe('Actions', () => { }); it('cardMoved', () => { - const data = create(Event_MoveCardSchema, { cardId: 1 }); + const data = create(Data.Event_MoveCardSchema, { cardId: 1 }); expect(Actions.cardMoved(1, 2, data)).toEqual({ type: Types.CARD_MOVED, gameId: 1, playerId: 2, data }); }); it('cardFlipped', () => { - const data = create(Event_FlipCardSchema, { cardId: 1 }); + const data = create(Data.Event_FlipCardSchema, { cardId: 1 }); expect(Actions.cardFlipped(1, 2, data)).toEqual({ type: Types.CARD_FLIPPED, gameId: 1, playerId: 2, data }); }); it('cardDestroyed', () => { - const data = create(Event_DestroyCardSchema, { cardId: 1 }); + const data = create(Data.Event_DestroyCardSchema, { cardId: 1 }); expect(Actions.cardDestroyed(1, 2, data)).toEqual({ type: Types.CARD_DESTROYED, gameId: 1, playerId: 2, data }); }); it('cardAttached', () => { - const data = create(Event_AttachCardSchema, { cardId: 1 }); + const data = create(Data.Event_AttachCardSchema, { cardId: 1 }); expect(Actions.cardAttached(1, 2, data)).toEqual({ type: Types.CARD_ATTACHED, gameId: 1, playerId: 2, data }); }); it('tokenCreated', () => { - const data = create(Event_CreateTokenSchema, { cardId: 1 }); + const data = create(Data.Event_CreateTokenSchema, { cardId: 1 }); expect(Actions.tokenCreated(1, 2, data)).toEqual({ type: Types.TOKEN_CREATED, gameId: 1, playerId: 2, data }); }); it('cardAttrChanged', () => { - const data = create(Event_SetCardAttrSchema, { cardId: 1 }); + const data = create(Data.Event_SetCardAttrSchema, { cardId: 1 }); expect(Actions.cardAttrChanged(1, 2, data)).toEqual({ type: Types.CARD_ATTR_CHANGED, gameId: 1, playerId: 2, data }); }); it('cardCounterChanged', () => { - const data = create(Event_SetCardCounterSchema, { cardId: 1 }); + const data = create(Data.Event_SetCardCounterSchema, { cardId: 1 }); expect(Actions.cardCounterChanged(1, 2, data)).toEqual({ type: Types.CARD_COUNTER_CHANGED, gameId: 1, playerId: 2, data }); }); it('arrowCreated', () => { const arrow = makeArrow(); - const data = create(Event_CreateArrowSchema, { arrowInfo: arrow }); + const data = create(Data.Event_CreateArrowSchema, { arrowInfo: arrow }); expect(Actions.arrowCreated(1, 2, data)).toEqual({ type: Types.ARROW_CREATED, gameId: 1, playerId: 2, data }); }); it('arrowDeleted', () => { - const data = create(Event_DeleteArrowSchema, { arrowId: 3 }); + const data = create(Data.Event_DeleteArrowSchema, { arrowId: 3 }); expect(Actions.arrowDeleted(1, 2, data)).toEqual({ type: Types.ARROW_DELETED, gameId: 1, playerId: 2, data }); }); it('counterCreated', () => { const counter = makeCounter(); - const data = create(Event_CreateCounterSchema, { counterInfo: counter }); + const data = create(Data.Event_CreateCounterSchema, { counterInfo: counter }); expect(Actions.counterCreated(1, 2, data)).toEqual({ type: Types.COUNTER_CREATED, gameId: 1, playerId: 2, data }); }); it('counterSet', () => { - const data = create(Event_SetCounterSchema, { counterId: 1, value: 10 }); + const data = create(Data.Event_SetCounterSchema, { counterId: 1, value: 10 }); expect(Actions.counterSet(1, 2, data)).toEqual({ type: Types.COUNTER_SET, gameId: 1, playerId: 2, data }); }); it('counterDeleted', () => { - const data = create(Event_DelCounterSchema, { counterId: 1 }); + const data = create(Data.Event_DelCounterSchema, { counterId: 1 }); expect(Actions.counterDeleted(1, 2, data)).toEqual({ type: Types.COUNTER_DELETED, gameId: 1, playerId: 2, data }); }); it('cardsDrawn', () => { const card = makeCard(); - const data = create(Event_DrawCardsSchema, { number: 2, cards: [card] }); + const data = create(Data.Event_DrawCardsSchema, { number: 2, cards: [card] }); expect(Actions.cardsDrawn(1, 2, data)).toEqual({ type: Types.CARDS_DRAWN, gameId: 1, playerId: 2, data }); }); it('cardsRevealed', () => { - const data = create(Event_RevealCardsSchema, { zoneName: 'hand', cards: [] }); + const data = create(Data.Event_RevealCardsSchema, { zoneName: 'hand', cards: [] }); expect(Actions.cardsRevealed(1, 2, data)).toEqual({ type: Types.CARDS_REVEALED, gameId: 1, playerId: 2, data }); }); it('zoneShuffled', () => { - const data = create(Event_ShuffleSchema, { zoneName: 'deck', start: 0, end: 39 }); + const data = create(Data.Event_ShuffleSchema, { zoneName: 'deck', start: 0, end: 39 }); expect(Actions.zoneShuffled(1, 2, data)).toEqual({ type: Types.ZONE_SHUFFLED, gameId: 1, playerId: 2, data }); }); it('dieRolled', () => { - const data = create(Event_RollDieSchema, { sides: 6, value: 4, values: [4] }); + const data = create(Data.Event_RollDieSchema, { sides: 6, value: 4, values: [4] }); expect(Actions.dieRolled(1, 2, data)).toEqual({ type: Types.DIE_ROLLED, gameId: 1, playerId: 2, data }); }); @@ -176,12 +157,12 @@ describe('Actions', () => { }); it('zoneDumped', () => { - const data = create(Event_DumpZoneSchema, { zoneOwnerId: 1, zoneName: 'hand', numberCards: 3, isReversed: false }); + const data = create(Data.Event_DumpZoneSchema, { zoneOwnerId: 1, zoneName: 'hand', numberCards: 3, isReversed: false }); expect(Actions.zoneDumped(1, 2, data)).toEqual({ type: Types.ZONE_DUMPED, gameId: 1, playerId: 2, data }); }); it('zonePropertiesChanged', () => { - const data = create(Event_ChangeZonePropertiesSchema, { zoneName: 'deck', alwaysRevealTopCard: true, alwaysLookAtTopCard: false }); + const data = create(Data.Event_ChangeZonePropertiesSchema, { zoneName: 'deck', alwaysRevealTopCard: true, alwaysLookAtTopCard: false }); expect(Actions.zonePropertiesChanged(1, 2, data)).toEqual({ type: Types.ZONE_PROPERTIES_CHANGED, gameId: 1, diff --git a/webclient/src/store/game/game.actions.ts b/webclient/src/store/game/game.actions.ts index d02b5d402..91a2be55a 100644 --- a/webclient/src/store/game/game.actions.ts +++ b/webclient/src/store/game/game.actions.ts @@ -1,24 +1,4 @@ -import type { Event_AttachCard } from 'generated/proto/event_attach_card_pb'; -import type { Event_ChangeZoneProperties } from 'generated/proto/event_change_zone_properties_pb'; -import type { Event_CreateArrow } from 'generated/proto/event_create_arrow_pb'; -import type { Event_CreateCounter } from 'generated/proto/event_create_counter_pb'; -import type { Event_CreateToken } from 'generated/proto/event_create_token_pb'; -import type { Event_DelCounter } from 'generated/proto/event_del_counter_pb'; -import type { Event_DeleteArrow } from 'generated/proto/event_delete_arrow_pb'; -import type { Event_DestroyCard } from 'generated/proto/event_destroy_card_pb'; -import type { Event_DrawCards } from 'generated/proto/event_draw_cards_pb'; -import type { Event_DumpZone } from 'generated/proto/event_dump_zone_pb'; -import type { Event_FlipCard } from 'generated/proto/event_flip_card_pb'; -import type { Event_GameStateChanged } from 'generated/proto/event_game_state_changed_pb'; -import type { Event_MoveCard } from 'generated/proto/event_move_card_pb'; -import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb'; -import type { Event_RevealCards } from 'generated/proto/event_reveal_cards_pb'; -import type { Event_RollDie } from 'generated/proto/event_roll_die_pb'; -import type { Event_SetCardAttr } from 'generated/proto/event_set_card_attr_pb'; -import type { Event_SetCardCounter } from 'generated/proto/event_set_card_counter_pb'; -import type { Event_SetCounter } from 'generated/proto/event_set_counter_pb'; -import type { Event_Shuffle } from 'generated/proto/event_shuffle_pb'; -import { GameEntry } from './game.interfaces'; +import type { Data } from '@app/types'; import { Types } from './game.types'; export const Actions = { @@ -26,10 +6,9 @@ export const Actions = { type: Types.CLEAR_STORE, }), - gameJoined: (gameId: number, gameEntry: GameEntry) => ({ + gameJoined: (data: Data.Event_GameJoined) => ({ type: Types.GAME_JOINED, - gameId, - gameEntry, + data, }), gameLeft: (gameId: number) => ({ @@ -48,13 +27,13 @@ export const Actions = { hostId, }), - gameStateChanged: (gameId: number, data: Event_GameStateChanged) => ({ + gameStateChanged: (gameId: number, data: Data.Event_GameStateChanged) => ({ type: Types.GAME_STATE_CHANGED, gameId, data, }), - playerJoined: (gameId: number, playerProperties: ServerInfo_PlayerProperties) => ({ + playerJoined: (gameId: number, playerProperties: Data.ServerInfo_PlayerProperties) => ({ type: Types.PLAYER_JOINED, gameId, playerProperties, @@ -67,7 +46,7 @@ export const Actions = { reason, }), - playerPropertiesChanged: (gameId: number, playerId: number, properties: ServerInfo_PlayerProperties) => ({ + playerPropertiesChanged: (gameId: number, playerId: number, properties: Data.ServerInfo_PlayerProperties) => ({ type: Types.PLAYER_PROPERTIES_CHANGED, gameId, playerId, @@ -79,112 +58,112 @@ export const Actions = { gameId, }), - cardMoved: (gameId: number, playerId: number, data: Event_MoveCard) => ({ + cardMoved: (gameId: number, playerId: number, data: Data.Event_MoveCard) => ({ type: Types.CARD_MOVED, gameId, playerId, data, }), - cardFlipped: (gameId: number, playerId: number, data: Event_FlipCard) => ({ + cardFlipped: (gameId: number, playerId: number, data: Data.Event_FlipCard) => ({ type: Types.CARD_FLIPPED, gameId, playerId, data, }), - cardDestroyed: (gameId: number, playerId: number, data: Event_DestroyCard) => ({ + cardDestroyed: (gameId: number, playerId: number, data: Data.Event_DestroyCard) => ({ type: Types.CARD_DESTROYED, gameId, playerId, data, }), - cardAttached: (gameId: number, playerId: number, data: Event_AttachCard) => ({ + cardAttached: (gameId: number, playerId: number, data: Data.Event_AttachCard) => ({ type: Types.CARD_ATTACHED, gameId, playerId, data, }), - tokenCreated: (gameId: number, playerId: number, data: Event_CreateToken) => ({ + tokenCreated: (gameId: number, playerId: number, data: Data.Event_CreateToken) => ({ type: Types.TOKEN_CREATED, gameId, playerId, data, }), - cardAttrChanged: (gameId: number, playerId: number, data: Event_SetCardAttr) => ({ + cardAttrChanged: (gameId: number, playerId: number, data: Data.Event_SetCardAttr) => ({ type: Types.CARD_ATTR_CHANGED, gameId, playerId, data, }), - cardCounterChanged: (gameId: number, playerId: number, data: Event_SetCardCounter) => ({ + cardCounterChanged: (gameId: number, playerId: number, data: Data.Event_SetCardCounter) => ({ type: Types.CARD_COUNTER_CHANGED, gameId, playerId, data, }), - arrowCreated: (gameId: number, playerId: number, data: Event_CreateArrow) => ({ + arrowCreated: (gameId: number, playerId: number, data: Data.Event_CreateArrow) => ({ type: Types.ARROW_CREATED, gameId, playerId, data, }), - arrowDeleted: (gameId: number, playerId: number, data: Event_DeleteArrow) => ({ + arrowDeleted: (gameId: number, playerId: number, data: Data.Event_DeleteArrow) => ({ type: Types.ARROW_DELETED, gameId, playerId, data, }), - counterCreated: (gameId: number, playerId: number, data: Event_CreateCounter) => ({ + counterCreated: (gameId: number, playerId: number, data: Data.Event_CreateCounter) => ({ type: Types.COUNTER_CREATED, gameId, playerId, data, }), - counterSet: (gameId: number, playerId: number, data: Event_SetCounter) => ({ + counterSet: (gameId: number, playerId: number, data: Data.Event_SetCounter) => ({ type: Types.COUNTER_SET, gameId, playerId, data, }), - counterDeleted: (gameId: number, playerId: number, data: Event_DelCounter) => ({ + counterDeleted: (gameId: number, playerId: number, data: Data.Event_DelCounter) => ({ type: Types.COUNTER_DELETED, gameId, playerId, data, }), - cardsDrawn: (gameId: number, playerId: number, data: Event_DrawCards) => ({ + cardsDrawn: (gameId: number, playerId: number, data: Data.Event_DrawCards) => ({ type: Types.CARDS_DRAWN, gameId, playerId, data, }), - cardsRevealed: (gameId: number, playerId: number, data: Event_RevealCards) => ({ + cardsRevealed: (gameId: number, playerId: number, data: Data.Event_RevealCards) => ({ type: Types.CARDS_REVEALED, gameId, playerId, data, }), - zoneShuffled: (gameId: number, playerId: number, data: Event_Shuffle) => ({ + zoneShuffled: (gameId: number, playerId: number, data: Data.Event_Shuffle) => ({ type: Types.ZONE_SHUFFLED, gameId, playerId, data, }), - dieRolled: (gameId: number, playerId: number, data: Event_RollDie) => ({ + dieRolled: (gameId: number, playerId: number, data: Data.Event_RollDie) => ({ type: Types.DIE_ROLLED, gameId, playerId, @@ -209,14 +188,14 @@ export const Actions = { reversed, }), - zoneDumped: (gameId: number, playerId: number, data: Event_DumpZone) => ({ + zoneDumped: (gameId: number, playerId: number, data: Data.Event_DumpZone) => ({ type: Types.ZONE_DUMPED, gameId, playerId, data, }), - zonePropertiesChanged: (gameId: number, playerId: number, data: Event_ChangeZoneProperties) => ({ + zonePropertiesChanged: (gameId: number, playerId: number, data: Data.Event_ChangeZoneProperties) => ({ type: Types.ZONE_PROPERTIES_CHANGED, gameId, playerId, diff --git a/webclient/src/store/game/game.dispatch.spec.ts b/webclient/src/store/game/game.dispatch.spec.ts index 77c6e0227..a06d940e4 100644 --- a/webclient/src/store/game/game.dispatch.spec.ts +++ b/webclient/src/store/game/game.dispatch.spec.ts @@ -1,37 +1,16 @@ -vi.mock('store/store', () => ({ store: { dispatch: vi.fn() } })); +vi.mock('../store', () => ({ store: { dispatch: vi.fn() } })); import { create } from '@bufbuild/protobuf'; -import { store } from 'store/store'; +import { Data } from '@app/types'; +import { store } from '..'; import { Actions } from './game.actions'; import { Dispatch } from './game.dispatch'; import { makeArrow, makeCard, makeCounter, - makeGameEntry, makePlayerProperties, } from './__mocks__/fixtures'; -import { Event_GameStateChangedSchema } from 'generated/proto/event_game_state_changed_pb'; -import { Event_MoveCardSchema } from 'generated/proto/event_move_card_pb'; -import { Event_FlipCardSchema } from 'generated/proto/event_flip_card_pb'; -import { Event_DestroyCardSchema } from 'generated/proto/event_destroy_card_pb'; -import { Event_AttachCardSchema } from 'generated/proto/event_attach_card_pb'; -import { Event_CreateTokenSchema } from 'generated/proto/event_create_token_pb'; -import { Event_SetCardAttrSchema } from 'generated/proto/event_set_card_attr_pb'; -import { Event_SetCardCounterSchema } from 'generated/proto/event_set_card_counter_pb'; -import { Event_CreateArrowSchema } from 'generated/proto/event_create_arrow_pb'; -import { Event_DeleteArrowSchema } from 'generated/proto/event_delete_arrow_pb'; -import { Event_CreateCounterSchema } from 'generated/proto/event_create_counter_pb'; -import { Event_SetCounterSchema } from 'generated/proto/event_set_counter_pb'; -import { Event_DelCounterSchema } from 'generated/proto/event_del_counter_pb'; -import { Event_DrawCardsSchema } from 'generated/proto/event_draw_cards_pb'; -import { Event_RevealCardsSchema } from 'generated/proto/event_reveal_cards_pb'; -import { Event_ShuffleSchema } from 'generated/proto/event_shuffle_pb'; -import { Event_RollDieSchema } from 'generated/proto/event_roll_die_pb'; -import { Event_DumpZoneSchema } from 'generated/proto/event_dump_zone_pb'; -import { Event_ChangeZonePropertiesSchema } from 'generated/proto/event_change_zone_properties_pb'; - -beforeEach(() => vi.clearAllMocks()); describe('Dispatch', () => { it('clearStore dispatches Actions.clearStore()', () => { @@ -40,9 +19,9 @@ describe('Dispatch', () => { }); it('gameJoined dispatches Actions.gameJoined()', () => { - const entry = makeGameEntry(); - Dispatch.gameJoined(1, entry); - expect(store.dispatch).toHaveBeenCalledWith(Actions.gameJoined(1, entry)); + const data = create(Data.Event_GameJoinedSchema, { hostId: 1, playerId: 2 }); + Dispatch.gameJoined(data); + expect(store.dispatch).toHaveBeenCalledWith(Actions.gameJoined(data)); }); it('gameLeft dispatches Actions.gameLeft()', () => { @@ -61,7 +40,7 @@ describe('Dispatch', () => { }); it('gameStateChanged dispatches Actions.gameStateChanged()', () => { - const data = create(Event_GameStateChangedSchema, { + const data = create(Data.Event_GameStateChangedSchema, { playerList: [], gameStarted: false, activePlayerId: 0, activePhase: 0, secondsElapsed: 0 }); Dispatch.gameStateChanged(1, data); @@ -91,97 +70,97 @@ describe('Dispatch', () => { }); it('cardMoved dispatches Actions.cardMoved()', () => { - const data = create(Event_MoveCardSchema, { cardId: 1 }); + const data = create(Data.Event_MoveCardSchema, { cardId: 1 }); Dispatch.cardMoved(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.cardMoved(1, 2, data)); }); it('cardFlipped dispatches Actions.cardFlipped()', () => { - const data = create(Event_FlipCardSchema, { cardId: 1 }); + const data = create(Data.Event_FlipCardSchema, { cardId: 1 }); Dispatch.cardFlipped(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.cardFlipped(1, 2, data)); }); it('cardDestroyed dispatches Actions.cardDestroyed()', () => { - const data = create(Event_DestroyCardSchema, { cardId: 1 }); + const data = create(Data.Event_DestroyCardSchema, { cardId: 1 }); Dispatch.cardDestroyed(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.cardDestroyed(1, 2, data)); }); it('cardAttached dispatches Actions.cardAttached()', () => { - const data = create(Event_AttachCardSchema, { cardId: 1 }); + const data = create(Data.Event_AttachCardSchema, { cardId: 1 }); Dispatch.cardAttached(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.cardAttached(1, 2, data)); }); it('tokenCreated dispatches Actions.tokenCreated()', () => { - const data = create(Event_CreateTokenSchema, { cardId: 1 }); + const data = create(Data.Event_CreateTokenSchema, { cardId: 1 }); Dispatch.tokenCreated(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.tokenCreated(1, 2, data)); }); it('cardAttrChanged dispatches Actions.cardAttrChanged()', () => { - const data = create(Event_SetCardAttrSchema, { cardId: 1 }); + const data = create(Data.Event_SetCardAttrSchema, { cardId: 1 }); Dispatch.cardAttrChanged(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.cardAttrChanged(1, 2, data)); }); it('cardCounterChanged dispatches Actions.cardCounterChanged()', () => { - const data = create(Event_SetCardCounterSchema, { cardId: 1 }); + const data = create(Data.Event_SetCardCounterSchema, { cardId: 1 }); Dispatch.cardCounterChanged(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.cardCounterChanged(1, 2, data)); }); it('arrowCreated dispatches Actions.arrowCreated()', () => { - const data = create(Event_CreateArrowSchema, { arrowInfo: makeArrow() }); + const data = create(Data.Event_CreateArrowSchema, { arrowInfo: makeArrow() }); Dispatch.arrowCreated(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.arrowCreated(1, 2, data)); }); it('arrowDeleted dispatches Actions.arrowDeleted()', () => { - const data = create(Event_DeleteArrowSchema, { arrowId: 3 }); + const data = create(Data.Event_DeleteArrowSchema, { arrowId: 3 }); Dispatch.arrowDeleted(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.arrowDeleted(1, 2, data)); }); it('counterCreated dispatches Actions.counterCreated()', () => { - const data = create(Event_CreateCounterSchema, { counterInfo: makeCounter() }); + const data = create(Data.Event_CreateCounterSchema, { counterInfo: makeCounter() }); Dispatch.counterCreated(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.counterCreated(1, 2, data)); }); it('counterSet dispatches Actions.counterSet()', () => { - const data = create(Event_SetCounterSchema, { counterId: 1, value: 10 }); + const data = create(Data.Event_SetCounterSchema, { counterId: 1, value: 10 }); Dispatch.counterSet(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.counterSet(1, 2, data)); }); it('counterDeleted dispatches Actions.counterDeleted()', () => { - const data = create(Event_DelCounterSchema, { counterId: 1 }); + const data = create(Data.Event_DelCounterSchema, { counterId: 1 }); Dispatch.counterDeleted(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.counterDeleted(1, 2, data)); }); it('cardsDrawn dispatches Actions.cardsDrawn()', () => { - const data = create(Event_DrawCardsSchema, { number: 2, cards: [makeCard()] }); + const data = create(Data.Event_DrawCardsSchema, { number: 2, cards: [makeCard()] }); Dispatch.cardsDrawn(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.cardsDrawn(1, 2, data)); }); it('cardsRevealed dispatches Actions.cardsRevealed()', () => { - const data = create(Event_RevealCardsSchema, { zoneName: 'hand', cards: [] }); + const data = create(Data.Event_RevealCardsSchema, { zoneName: 'hand', cards: [] }); Dispatch.cardsRevealed(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.cardsRevealed(1, 2, data)); }); it('zoneShuffled dispatches Actions.zoneShuffled()', () => { - const data = create(Event_ShuffleSchema, { zoneName: 'deck', start: 0, end: 39 }); + const data = create(Data.Event_ShuffleSchema, { zoneName: 'deck', start: 0, end: 39 }); Dispatch.zoneShuffled(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.zoneShuffled(1, 2, data)); }); it('dieRolled dispatches Actions.dieRolled()', () => { - const data = create(Event_RollDieSchema, { sides: 6, value: 4, values: [4] }); + const data = create(Data.Event_RollDieSchema, { sides: 6, value: 4, values: [4] }); Dispatch.dieRolled(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.dieRolled(1, 2, data)); }); @@ -202,13 +181,13 @@ describe('Dispatch', () => { }); it('zoneDumped dispatches Actions.zoneDumped()', () => { - const data = create(Event_DumpZoneSchema, { zoneOwnerId: 1, zoneName: 'hand', numberCards: 3, isReversed: false }); + const data = create(Data.Event_DumpZoneSchema, { zoneOwnerId: 1, zoneName: 'hand', numberCards: 3, isReversed: false }); Dispatch.zoneDumped(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.zoneDumped(1, 2, data)); }); it('zonePropertiesChanged dispatches Actions.zonePropertiesChanged()', () => { - const data = create(Event_ChangeZonePropertiesSchema, { zoneName: 'deck', alwaysRevealTopCard: true, alwaysLookAtTopCard: false }); + const data = create(Data.Event_ChangeZonePropertiesSchema, { zoneName: 'deck', alwaysRevealTopCard: true, alwaysLookAtTopCard: false }); Dispatch.zonePropertiesChanged(1, 2, data); expect(store.dispatch).toHaveBeenCalledWith(Actions.zonePropertiesChanged(1, 2, data)); }); diff --git a/webclient/src/store/game/game.dispatch.ts b/webclient/src/store/game/game.dispatch.ts index ea0dbcfff..413b2b2e6 100644 --- a/webclient/src/store/game/game.dispatch.ts +++ b/webclient/src/store/game/game.dispatch.ts @@ -1,34 +1,14 @@ -import type { Event_AttachCard } from 'generated/proto/event_attach_card_pb'; -import type { Event_ChangeZoneProperties } from 'generated/proto/event_change_zone_properties_pb'; -import type { Event_CreateArrow } from 'generated/proto/event_create_arrow_pb'; -import type { Event_CreateCounter } from 'generated/proto/event_create_counter_pb'; -import type { Event_CreateToken } from 'generated/proto/event_create_token_pb'; -import type { Event_DelCounter } from 'generated/proto/event_del_counter_pb'; -import type { Event_DeleteArrow } from 'generated/proto/event_delete_arrow_pb'; -import type { Event_DestroyCard } from 'generated/proto/event_destroy_card_pb'; -import type { Event_DrawCards } from 'generated/proto/event_draw_cards_pb'; -import type { Event_DumpZone } from 'generated/proto/event_dump_zone_pb'; -import type { Event_FlipCard } from 'generated/proto/event_flip_card_pb'; -import type { Event_GameStateChanged } from 'generated/proto/event_game_state_changed_pb'; -import type { Event_MoveCard } from 'generated/proto/event_move_card_pb'; -import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb'; -import type { Event_RevealCards } from 'generated/proto/event_reveal_cards_pb'; -import type { Event_RollDie } from 'generated/proto/event_roll_die_pb'; -import type { Event_SetCardAttr } from 'generated/proto/event_set_card_attr_pb'; -import type { Event_SetCardCounter } from 'generated/proto/event_set_card_counter_pb'; -import type { Event_SetCounter } from 'generated/proto/event_set_counter_pb'; -import type { Event_Shuffle } from 'generated/proto/event_shuffle_pb'; -import { store } from 'store/store'; +import type { Data } from '@app/types'; +import { store } from '..'; import { Actions } from './game.actions'; -import { GameEntry } from './game.interfaces'; export const Dispatch = { clearStore: () => { store.dispatch(Actions.clearStore()); }, - gameJoined: (gameId: number, gameEntry: GameEntry) => { - store.dispatch(Actions.gameJoined(gameId, gameEntry)); + gameJoined: (data: Data.Event_GameJoined) => { + store.dispatch(Actions.gameJoined(data)); }, gameLeft: (gameId: number) => { @@ -43,11 +23,11 @@ export const Dispatch = { store.dispatch(Actions.gameHostChanged(gameId, hostId)); }, - gameStateChanged: (gameId: number, data: Event_GameStateChanged) => { + gameStateChanged: (gameId: number, data: Data.Event_GameStateChanged) => { store.dispatch(Actions.gameStateChanged(gameId, data)); }, - playerJoined: (gameId: number, playerProperties: ServerInfo_PlayerProperties) => { + playerJoined: (gameId: number, playerProperties: Data.ServerInfo_PlayerProperties) => { store.dispatch(Actions.playerJoined(gameId, playerProperties)); }, @@ -55,7 +35,7 @@ export const Dispatch = { store.dispatch(Actions.playerLeft(gameId, playerId, reason)); }, - playerPropertiesChanged: (gameId: number, playerId: number, properties: ServerInfo_PlayerProperties) => { + playerPropertiesChanged: (gameId: number, playerId: number, properties: Data.ServerInfo_PlayerProperties) => { store.dispatch(Actions.playerPropertiesChanged(gameId, playerId, properties)); }, @@ -63,67 +43,67 @@ export const Dispatch = { store.dispatch(Actions.kicked(gameId)); }, - cardMoved: (gameId: number, playerId: number, data: Event_MoveCard) => { + cardMoved: (gameId: number, playerId: number, data: Data.Event_MoveCard) => { store.dispatch(Actions.cardMoved(gameId, playerId, data)); }, - cardFlipped: (gameId: number, playerId: number, data: Event_FlipCard) => { + cardFlipped: (gameId: number, playerId: number, data: Data.Event_FlipCard) => { store.dispatch(Actions.cardFlipped(gameId, playerId, data)); }, - cardDestroyed: (gameId: number, playerId: number, data: Event_DestroyCard) => { + cardDestroyed: (gameId: number, playerId: number, data: Data.Event_DestroyCard) => { store.dispatch(Actions.cardDestroyed(gameId, playerId, data)); }, - cardAttached: (gameId: number, playerId: number, data: Event_AttachCard) => { + cardAttached: (gameId: number, playerId: number, data: Data.Event_AttachCard) => { store.dispatch(Actions.cardAttached(gameId, playerId, data)); }, - tokenCreated: (gameId: number, playerId: number, data: Event_CreateToken) => { + tokenCreated: (gameId: number, playerId: number, data: Data.Event_CreateToken) => { store.dispatch(Actions.tokenCreated(gameId, playerId, data)); }, - cardAttrChanged: (gameId: number, playerId: number, data: Event_SetCardAttr) => { + cardAttrChanged: (gameId: number, playerId: number, data: Data.Event_SetCardAttr) => { store.dispatch(Actions.cardAttrChanged(gameId, playerId, data)); }, - cardCounterChanged: (gameId: number, playerId: number, data: Event_SetCardCounter) => { + cardCounterChanged: (gameId: number, playerId: number, data: Data.Event_SetCardCounter) => { store.dispatch(Actions.cardCounterChanged(gameId, playerId, data)); }, - arrowCreated: (gameId: number, playerId: number, data: Event_CreateArrow) => { + arrowCreated: (gameId: number, playerId: number, data: Data.Event_CreateArrow) => { store.dispatch(Actions.arrowCreated(gameId, playerId, data)); }, - arrowDeleted: (gameId: number, playerId: number, data: Event_DeleteArrow) => { + arrowDeleted: (gameId: number, playerId: number, data: Data.Event_DeleteArrow) => { store.dispatch(Actions.arrowDeleted(gameId, playerId, data)); }, - counterCreated: (gameId: number, playerId: number, data: Event_CreateCounter) => { + counterCreated: (gameId: number, playerId: number, data: Data.Event_CreateCounter) => { store.dispatch(Actions.counterCreated(gameId, playerId, data)); }, - counterSet: (gameId: number, playerId: number, data: Event_SetCounter) => { + counterSet: (gameId: number, playerId: number, data: Data.Event_SetCounter) => { store.dispatch(Actions.counterSet(gameId, playerId, data)); }, - counterDeleted: (gameId: number, playerId: number, data: Event_DelCounter) => { + counterDeleted: (gameId: number, playerId: number, data: Data.Event_DelCounter) => { store.dispatch(Actions.counterDeleted(gameId, playerId, data)); }, - cardsDrawn: (gameId: number, playerId: number, data: Event_DrawCards) => { + cardsDrawn: (gameId: number, playerId: number, data: Data.Event_DrawCards) => { store.dispatch(Actions.cardsDrawn(gameId, playerId, data)); }, - cardsRevealed: (gameId: number, playerId: number, data: Event_RevealCards) => { + cardsRevealed: (gameId: number, playerId: number, data: Data.Event_RevealCards) => { store.dispatch(Actions.cardsRevealed(gameId, playerId, data)); }, - zoneShuffled: (gameId: number, playerId: number, data: Event_Shuffle) => { + zoneShuffled: (gameId: number, playerId: number, data: Data.Event_Shuffle) => { store.dispatch(Actions.zoneShuffled(gameId, playerId, data)); }, - dieRolled: (gameId: number, playerId: number, data: Event_RollDie) => { + dieRolled: (gameId: number, playerId: number, data: Data.Event_RollDie) => { store.dispatch(Actions.dieRolled(gameId, playerId, data)); }, @@ -139,11 +119,11 @@ export const Dispatch = { store.dispatch(Actions.turnReversed(gameId, reversed)); }, - zoneDumped: (gameId: number, playerId: number, data: Event_DumpZone) => { + zoneDumped: (gameId: number, playerId: number, data: Data.Event_DumpZone) => { store.dispatch(Actions.zoneDumped(gameId, playerId, data)); }, - zonePropertiesChanged: (gameId: number, playerId: number, data: Event_ChangeZoneProperties) => { + zonePropertiesChanged: (gameId: number, playerId: number, data: Data.Event_ChangeZoneProperties) => { store.dispatch(Actions.zonePropertiesChanged(gameId, playerId, data)); }, diff --git a/webclient/src/store/game/game.interfaces.ts b/webclient/src/store/game/game.interfaces.ts index 62abb9774..62b87d991 100644 --- a/webclient/src/store/game/game.interfaces.ts +++ b/webclient/src/store/game/game.interfaces.ts @@ -1,7 +1,4 @@ -import type { ServerInfo_Card } from 'generated/proto/serverinfo_card_pb'; -import type { ServerInfo_Counter } from 'generated/proto/serverinfo_counter_pb'; -import type { ServerInfo_Arrow } from 'generated/proto/serverinfo_arrow_pb'; -import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb'; +import type { Data } from '@app/types'; export interface GamesState { games: { [gameId: number]: GameEntry }; @@ -32,14 +29,14 @@ export interface GameEntry { /** Normalized from ServerInfo_Player — keyed collections for O(1) lookup. */ export interface PlayerEntry { - properties: ServerInfo_PlayerProperties; + properties: Data.ServerInfo_PlayerProperties; deckList: string; /** Zones keyed by zone name (e.g. "hand", "deck", "table"). */ zones: { [zoneName: string]: ZoneEntry }; /** Player-level counters (e.g. life) keyed by counter id. */ - counters: { [counterId: number]: ServerInfo_Counter }; + counters: { [counterId: number]: Data.ServerInfo_Counter }; /** Arrows keyed by arrow id. */ - arrows: { [arrowId: number]: ServerInfo_Arrow }; + arrows: { [arrowId: number]: Data.ServerInfo_Arrow }; } /** Normalized from ServerInfo_Zone — card list is an ordered array matching proto. */ @@ -51,7 +48,7 @@ export interface ZoneEntry { /** Authoritative card count (used for hidden zones where cardList may be empty). */ cardCount: number; /** Ordered card list; may be empty for hidden zones with no dump active. */ - cards: ServerInfo_Card[]; + cards: Data.ServerInfo_Card[]; alwaysRevealTopCard: boolean; alwaysLookAtTopCard: boolean; } diff --git a/webclient/src/store/game/game.reducer.spec.ts b/webclient/src/store/game/game.reducer.spec.ts index bda9cae90..048dbbd93 100644 --- a/webclient/src/store/game/game.reducer.spec.ts +++ b/webclient/src/store/game/game.reducer.spec.ts @@ -1,6 +1,5 @@ import { create } from '@bufbuild/protobuf'; -import { CardAttribute } from 'generated/proto/card_attributes_pb'; -import type { ServerInfo_Player } from 'generated/proto/serverinfo_player_pb'; +import { Data } from '@app/types'; import { gamesReducer } from './game.reducer'; import { Types } from './game.types'; import { @@ -13,7 +12,6 @@ import { makeState, makeZoneEntry, } from './__mocks__/fixtures'; -import { ServerInfo_PlayerSchema } from 'generated/proto/serverinfo_player_pb'; // ── 2A: Initialisation & lifecycle ─────────────────────────────────────────── @@ -30,9 +28,16 @@ describe('2A: Initialisation & lifecycle', () => { }); it('GAME_JOINED → inserts gameEntry keyed by gameId', () => { - const entry = makeGameEntry({ gameId: 42 }); - const result = gamesReducer({ games: {} }, { type: Types.GAME_JOINED, gameId: 42, gameEntry: entry }); - expect(result.games[42]).toBe(entry); + const data = create(Data.Event_GameJoinedSchema, { + gameInfo: create(Data.ServerInfo_GameSchema, { gameId: 42, roomId: 1, description: 'test' }), + hostId: 5, + playerId: 2, + spectator: false, + judge: false, + resuming: false, + }); + const result = gamesReducer({ games: {} }, { type: Types.GAME_JOINED, data }); + expect(result.games[42]).toEqual(expect.objectContaining({ gameId: 42, hostId: 5, localPlayerId: 2 })); }); it('GAME_LEFT → removes game by gameId', () => { @@ -69,8 +74,8 @@ describe('2B: Game state & player management', () => { const card = makeCard({ id: 5 }); const counter = makeCounter({ id: 2 }); const arrow = makeArrow({ id: 3 }); - const playerList: ServerInfo_Player[] = [ - create(ServerInfo_PlayerSchema, { + const playerList: Data.ServerInfo_Player[] = [ + create(Data.ServerInfo_PlayerSchema, { properties: makePlayerProperties({ playerId: 7 }), deckList: 'some deck', zoneList: [ @@ -550,7 +555,7 @@ describe('2E: CARD_ATTR_CHANGED', () => { }); } - function dispatchAttr(state: ReturnType, attribute: CardAttribute, attrValue: string) { + function dispatchAttr(state: ReturnType, attribute: Data.CardAttribute, attrValue: string) { return gamesReducer(state, { type: Types.CARD_ATTR_CHANGED, gameId: 1, @@ -560,37 +565,37 @@ describe('2E: CARD_ATTR_CHANGED', () => { } it('AttrTapped (1) → card.tapped = true when attrValue is "1"', () => { - const result = dispatchAttr(stateWithCard(), CardAttribute.AttrTapped, '1'); + const result = dispatchAttr(stateWithCard(), Data.CardAttribute.AttrTapped, '1'); expect(result.games[1].players[1].zones['table'].cards[0].tapped).toBe(true); }); it('AttrAttacking (2) → card.attacking = true when attrValue is "1"', () => { - const result = dispatchAttr(stateWithCard(), CardAttribute.AttrAttacking, '1'); + const result = dispatchAttr(stateWithCard(), Data.CardAttribute.AttrAttacking, '1'); expect(result.games[1].players[1].zones['table'].cards[0].attacking).toBe(true); }); it('AttrFaceDown (3) → card.faceDown = true when attrValue is "1"', () => { - const result = dispatchAttr(stateWithCard(), CardAttribute.AttrFaceDown, '1'); + const result = dispatchAttr(stateWithCard(), Data.CardAttribute.AttrFaceDown, '1'); expect(result.games[1].players[1].zones['table'].cards[0].faceDown).toBe(true); }); it('AttrColor (4) → card.color = attrValue', () => { - const result = dispatchAttr(stateWithCard(), CardAttribute.AttrColor, 'red'); + const result = dispatchAttr(stateWithCard(), Data.CardAttribute.AttrColor, 'red'); expect(result.games[1].players[1].zones['table'].cards[0].color).toBe('red'); }); it('AttrPT (5) → card.pt = attrValue', () => { - const result = dispatchAttr(stateWithCard(), CardAttribute.AttrPT, '2/3'); + const result = dispatchAttr(stateWithCard(), Data.CardAttribute.AttrPT, '2/3'); expect(result.games[1].players[1].zones['table'].cards[0].pt).toBe('2/3'); }); it('AttrAnnotation (6) → card.annotation = attrValue', () => { - const result = dispatchAttr(stateWithCard(), CardAttribute.AttrAnnotation, 'enchanted'); + const result = dispatchAttr(stateWithCard(), Data.CardAttribute.AttrAnnotation, 'enchanted'); expect(result.games[1].players[1].zones['table'].cards[0].annotation).toBe('enchanted'); }); it('AttrDoesntUntap (7) → card.doesntUntap = true when attrValue is "1"', () => { - const result = dispatchAttr(stateWithCard(), CardAttribute.AttrDoesntUntap, '1'); + const result = dispatchAttr(stateWithCard(), Data.CardAttribute.AttrDoesntUntap, '1'); expect(result.games[1].players[1].zones['table'].cards[0].doesntUntap).toBe(true); }); }); diff --git a/webclient/src/store/game/game.reducer.ts b/webclient/src/store/game/game.reducer.ts index 451b75fd2..602a0dbbb 100644 --- a/webclient/src/store/game/game.reducer.ts +++ b/webclient/src/store/game/game.reducer.ts @@ -1,12 +1,5 @@ -import { CardAttribute } from 'generated/proto/card_attributes_pb'; -import type { ServerInfo_CardCounter } from 'generated/proto/serverinfo_cardcounter_pb'; -import type { ServerInfo_Card } from 'generated/proto/serverinfo_card_pb'; -import type { ServerInfo_Counter } from 'generated/proto/serverinfo_counter_pb'; -import type { ServerInfo_Arrow } from 'generated/proto/serverinfo_arrow_pb'; -import type { ServerInfo_Player } from 'generated/proto/serverinfo_player_pb'; +import { Data } from '@app/types'; import { create } from '@bufbuild/protobuf'; -import { ServerInfo_CardSchema } from 'generated/proto/serverinfo_card_pb'; -import { ServerInfo_CardCounterSchema } from 'generated/proto/serverinfo_cardcounter_pb'; import { GameAction } from './game.actions'; import { GameEntry, GameMessage, GamesState, PlayerEntry, ZoneEntry } from './game.interfaces'; import { Types } from './game.types'; @@ -74,7 +67,7 @@ function removeGame(state: GamesState, gameId: number): GamesState { } /** Converts the proto PlayerInfo[] array into the keyed PlayerEntry map used in the store. */ -function normalizePlayers(playerList: ServerInfo_Player[]): { [playerId: number]: PlayerEntry } { +function normalizePlayers(playerList: Data.ServerInfo_Player[]): { [playerId: number]: PlayerEntry } { const players: { [playerId: number]: PlayerEntry } = {}; for (const player of playerList) { const playerId = player.properties.playerId; @@ -92,12 +85,12 @@ function normalizePlayers(playerList: ServerInfo_Player[]): { [playerId: number] }; } - const counters: { [counterId: number]: ServerInfo_Counter } = {}; + const counters: { [counterId: number]: Data.ServerInfo_Counter } = {}; for (const counter of player.counterList) { counters[counter.id] = counter; } - const arrows: { [arrowId: number]: ServerInfo_Arrow } = {}; + const arrows: { [arrowId: number]: Data.ServerInfo_Arrow } = {}; for (const arrow of player.arrowList) { arrows[arrow.id] = arrow; } @@ -120,8 +113,8 @@ function buildEmptyCard( y: number, faceDown: boolean, providerId: string -): ServerInfo_Card { - return create(ServerInfo_CardSchema, { +): Data.ServerInfo_Card { + return create(Data.ServerInfo_CardSchema, { id, name, x, @@ -157,9 +150,31 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio } case Types.GAME_JOINED: { + const { data } = action; + const gameInfo = data.gameInfo; + if (!gameInfo) { + return state; + } + const gameEntry: GameEntry = { + gameId: gameInfo.gameId, + roomId: gameInfo.roomId, + description: gameInfo.description, + hostId: data.hostId, + localPlayerId: data.playerId, + spectator: data.spectator, + judge: data.judge, + resuming: data.resuming, + started: gameInfo.started, + activePlayerId: -1, + activePhase: -1, + secondsElapsed: 0, + reversed: false, + players: {}, + messages: [], + }; return { ...state, - games: { ...state.games, [action.gameId]: action.gameEntry }, + games: { ...state.games, [gameEntry.gameId]: gameEntry }, }; } @@ -266,8 +281,8 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio } // Locate card in source zone (by id for visible zones, by position for hidden) - let removedCard: ServerInfo_Card | undefined; - let newSourceCards: ServerInfo_Card[]; + let removedCard: Data.ServerInfo_Card | undefined; + let newSourceCards: Data.ServerInfo_Card[]; if (cardId >= 0) { removedCard = sourceZoneEntry.cards.find(c => c.id === cardId); newSourceCards = sourceZoneEntry.cards.filter(c => c.id !== cardId); @@ -280,7 +295,7 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio } const effectiveNewId = newCardId >= 0 ? newCardId : (removedCard?.id ?? -1); - const movedCard: ServerInfo_Card = removedCard + const movedCard: Data.ServerInfo_Card = removedCard ? { ...removedCard, id: effectiveNewId, @@ -423,7 +438,7 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio return state; } - const newCard: ServerInfo_Card = create(ServerInfo_CardSchema, { + const newCard: Data.ServerInfo_Card = create(Data.ServerInfo_CardSchema, { id: cardId, name: cardName, x, @@ -469,15 +484,15 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio return state; } - const attrPatch: Partial = {}; - switch (attribute as CardAttribute) { - case CardAttribute.AttrTapped: attrPatch.tapped = attrValue === '1'; break; - case CardAttribute.AttrAttacking: attrPatch.attacking = attrValue === '1'; break; - case CardAttribute.AttrFaceDown: attrPatch.faceDown = attrValue === '1'; break; - case CardAttribute.AttrColor: attrPatch.color = attrValue; break; - case CardAttribute.AttrPT: attrPatch.pt = attrValue; break; - case CardAttribute.AttrAnnotation: attrPatch.annotation = attrValue; break; - case CardAttribute.AttrDoesntUntap: attrPatch.doesntUntap = attrValue === '1'; break; + const attrPatch: Partial = {}; + switch (attribute as Data.CardAttribute) { + case Data.CardAttribute.AttrTapped: attrPatch.tapped = attrValue === '1'; break; + case Data.CardAttribute.AttrAttacking: attrPatch.attacking = attrValue === '1'; break; + case Data.CardAttribute.AttrFaceDown: attrPatch.faceDown = attrValue === '1'; break; + case Data.CardAttribute.AttrColor: attrPatch.color = attrValue; break; + case Data.CardAttribute.AttrPT: attrPatch.pt = attrValue; break; + case Data.CardAttribute.AttrAnnotation: attrPatch.annotation = attrValue; break; + case Data.CardAttribute.AttrDoesntUntap: attrPatch.doesntUntap = attrValue === '1'; break; } const updatedCards = [...zone.cards]; @@ -507,7 +522,7 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio } const card = zone.cards[cardIdx]; - let newCounterList: ServerInfo_CardCounter[]; + let newCounterList: Data.ServerInfo_CardCounter[]; if (counterValue <= 0) { newCounterList = card.counterList.filter(c => c.id !== counterId); } else { @@ -515,7 +530,7 @@ export const gamesReducer = (state: GamesState = initialState, action: GameActio newCounterList = existing >= 0 ? card.counterList.map(c => (c.id === counterId ? { ...c, value: counterValue } : c)) - : [...card.counterList, create(ServerInfo_CardCounterSchema, { id: counterId, value: counterValue })]; + : [...card.counterList, create(Data.ServerInfo_CardCounterSchema, { id: counterId, value: counterValue })]; } const updatedCards = [...zone.cards]; diff --git a/webclient/src/store/game/game.selectors.ts b/webclient/src/store/game/game.selectors.ts index acd653039..179c6de82 100644 --- a/webclient/src/store/game/game.selectors.ts +++ b/webclient/src/store/game/game.selectors.ts @@ -1,12 +1,12 @@ import { createSelector } from '@reduxjs/toolkit'; -import type { ServerInfo_Card } from 'generated/proto/serverinfo_card_pb'; +import type { Data } from '@app/types'; import { GamesState, GameEntry, PlayerEntry, ZoneEntry } from './game.interfaces'; interface State { games: GamesState; } -const EMPTY_ARRAY: ServerInfo_Card[] = []; +const EMPTY_ARRAY: Data.ServerInfo_Card[] = []; const EMPTY_OBJECT = {} as Record; export const Selectors = { diff --git a/webclient/src/store/index.ts b/webclient/src/store/index.ts index 2ee14345d..701e6a362 100644 --- a/webclient/src/store/index.ts +++ b/webclient/src/store/index.ts @@ -9,7 +9,7 @@ export { Selectors as GameSelectors, Dispatch as GameDispatch } from './game'; -export * from 'store/game/game.interfaces'; +export * from './game/game.interfaces'; // Server export { @@ -17,13 +17,13 @@ export { Selectors as ServerSelectors, Dispatch as ServerDispatch } from './server'; -export * from 'store/server/server.interfaces'; +export * from './server/server.interfaces'; export { Types as RoomsTypes, Selectors as RoomsSelectors, - Dispatch as RoomsDispatch } from 'store/rooms'; + Dispatch as RoomsDispatch } from './rooms'; -export * from 'store/rooms/rooms.interfaces'; +export * from './rooms/rooms.interfaces'; diff --git a/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts b/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts index 311980a98..940ec76ea 100644 --- a/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts +++ b/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts @@ -1,21 +1,13 @@ -import { - Game, - GameSortField, - Message, - ProtoInit, - Room, - SortDirection, - UserSortField, -} from 'types'; -import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; +import { App, Data, Enriched } from '@app/types'; +import type { MessageInitShape } from '@bufbuild/protobuf'; + import { create } from '@bufbuild/protobuf'; -import { ServerInfo_UserSchema } from 'generated/proto/serverinfo_user_pb'; -import { ServerInfo_GameSchema } from 'generated/proto/serverinfo_game_pb'; -import { ServerInfo_RoomSchema } from 'generated/proto/serverinfo_room_pb'; import { RoomsState } from '../rooms.interfaces'; -export function makeUser(overrides: ProtoInit = {}): ServerInfo_User { - return create(ServerInfo_UserSchema, { +export function makeUser( + overrides: MessageInitShape = {} +): Data.ServerInfo_User { + return create(Data.ServerInfo_UserSchema, { name: 'TestUser', accountageSecs: 0n, privlevel: '', @@ -24,10 +16,10 @@ export function makeUser(overrides: ProtoInit = {}): ServerInfo }); } -export function makeRoom(overrides: ProtoInit = {}): Room { +export function makeRoom(overrides: Partial> = {}): Enriched.Room { const { gametypeMap = {}, order = 0, gameList = [], ...protoOverrides } = overrides; return { - ...create(ServerInfo_RoomSchema, { + ...create(Data.ServerInfo_RoomSchema, { roomId: 1, name: 'Test Room', description: '', @@ -45,10 +37,12 @@ export function makeRoom(overrides: ProtoInit = {}): Room { }; } -export function makeGame(overrides: ProtoInit = {}): Game & { startTime: number } { +export function makeGame( + overrides: Partial> = {}, +): Enriched.Game & { startTime: number } { const { gameType = '', startTime = 0, ...protoOverrides } = overrides; return { - ...create(ServerInfo_GameSchema, { + ...create(Data.ServerInfo_GameSchema, { gameId: 1, roomId: 1, description: 'Test Game', @@ -61,7 +55,7 @@ export function makeGame(overrides: ProtoInit = {} }; } -export function makeMessage(overrides: Partial = {}): Message { +export function makeMessage(overrides: Partial = {}): Enriched.Message { return { message: 'hello', messageType: 0, @@ -80,12 +74,12 @@ export function makeRoomsState(overrides: Partial = {}): RoomsState joinedGameIds: {}, messages: {}, sortGamesBy: { - field: GameSortField.START_TIME, - order: SortDirection.DESC, + field: App.GameSortField.START_TIME, + order: App.SortDirection.DESC, }, sortUsersBy: { - field: UserSortField.NAME, - order: SortDirection.ASC, + field: App.UserSortField.NAME, + order: App.SortDirection.ASC, }, ...overrides, }; diff --git a/webclient/src/store/rooms/rooms.actions.spec.ts b/webclient/src/store/rooms/rooms.actions.spec.ts index 542d626b9..cd8d3a936 100644 --- a/webclient/src/store/rooms/rooms.actions.spec.ts +++ b/webclient/src/store/rooms/rooms.actions.spec.ts @@ -1,7 +1,7 @@ import { Actions } from './rooms.actions'; import { Types } from './rooms.types'; import { makeGame, makeMessage, makeRoom, makeUser } from './__mocks__/rooms-fixtures'; -import { GameSortField, SortDirection } from 'types'; +import { App } from '@app/types'; describe('Actions', () => { it('clearStore', () => { @@ -42,11 +42,11 @@ describe('Actions', () => { }); it('sortGames', () => { - expect(Actions.sortGames(1, GameSortField.START_TIME, SortDirection.ASC)).toEqual({ + expect(Actions.sortGames(1, App.GameSortField.START_TIME, App.SortDirection.ASC)).toEqual({ type: Types.SORT_GAMES, roomId: 1, - field: GameSortField.START_TIME, - order: SortDirection.ASC, + field: App.GameSortField.START_TIME, + order: App.SortDirection.ASC, }); }); diff --git a/webclient/src/store/rooms/rooms.actions.tsx b/webclient/src/store/rooms/rooms.actions.tsx index 40dfa7559..df4acac95 100644 --- a/webclient/src/store/rooms/rooms.actions.tsx +++ b/webclient/src/store/rooms/rooms.actions.tsx @@ -1,7 +1,4 @@ -import { GameSortField, Message, SortDirection } from 'types'; -import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; -import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; -import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; +import { App, Data, Enriched } from '@app/types'; import { Types } from './rooms.types'; @@ -10,12 +7,12 @@ export const Actions = { type: Types.CLEAR_STORE, }), - updateRooms: (rooms: ServerInfo_Room[]) => ({ + updateRooms: (rooms: Data.ServerInfo_Room[]) => ({ type: Types.UPDATE_ROOMS, rooms, }), - joinRoom: (roomInfo: ServerInfo_Room) => ({ + joinRoom: (roomInfo: Data.ServerInfo_Room) => ({ type: Types.JOIN_ROOM, roomInfo, }), @@ -25,19 +22,19 @@ export const Actions = { roomId, }), - addMessage: (roomId: number, message: Message) => ({ + addMessage: (roomId: number, message: Enriched.Message) => ({ type: Types.ADD_MESSAGE, roomId, message, }), - updateGames: (roomId: number, games: ServerInfo_Game[]) => ({ + updateGames: (roomId: number, games: Data.ServerInfo_Game[]) => ({ type: Types.UPDATE_GAMES, roomId, games, }), - userJoined: (roomId: number, user: ServerInfo_User) => ({ + userJoined: (roomId: number, user: Data.ServerInfo_User) => ({ type: Types.USER_JOINED, roomId, user, @@ -49,7 +46,7 @@ export const Actions = { name, }), - sortGames: (roomId: number, field: GameSortField, order: SortDirection) => ({ + sortGames: (roomId: number, field: App.GameSortField, order: App.SortDirection) => ({ type: Types.SORT_GAMES, roomId, field, diff --git a/webclient/src/store/rooms/rooms.dispatch.spec.ts b/webclient/src/store/rooms/rooms.dispatch.spec.ts index ff090b36c..9b305c894 100644 --- a/webclient/src/store/rooms/rooms.dispatch.spec.ts +++ b/webclient/src/store/rooms/rooms.dispatch.spec.ts @@ -1,12 +1,10 @@ -vi.mock('store', () => ({ store: { dispatch: vi.fn() } })); +vi.mock('..', () => ({ store: { dispatch: vi.fn() } })); -import { store } from 'store'; +import { store } from '..'; import { Actions } from './rooms.actions'; import { Dispatch } from './rooms.dispatch'; import { makeGame, makeMessage, makeRoom, makeUser } from './__mocks__/rooms-fixtures'; -import { GameSortField, SortDirection } from 'types'; - -beforeEach(() => vi.clearAllMocks()); +import { App } from '@app/types'; describe('Dispatch', () => { it('clearStore dispatches Actions.clearStore()', () => { @@ -63,9 +61,9 @@ describe('Dispatch', () => { }); it('sortGames dispatches Actions.sortGames()', () => { - Dispatch.sortGames(1, GameSortField.START_TIME, SortDirection.ASC); + Dispatch.sortGames(1, App.GameSortField.START_TIME, App.SortDirection.ASC); expect(store.dispatch).toHaveBeenCalledWith( - Actions.sortGames(1, GameSortField.START_TIME, SortDirection.ASC) + Actions.sortGames(1, App.GameSortField.START_TIME, App.SortDirection.ASC) ); }); diff --git a/webclient/src/store/rooms/rooms.dispatch.tsx b/webclient/src/store/rooms/rooms.dispatch.tsx index a1c653f12..efa7177cf 100644 --- a/webclient/src/store/rooms/rooms.dispatch.tsx +++ b/webclient/src/store/rooms/rooms.dispatch.tsx @@ -1,21 +1,18 @@ -import { GameSortField, Message, SortDirection } from 'types'; -import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; -import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; -import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; +import { App, Data, Enriched } from '@app/types'; import { Actions } from './rooms.actions'; -import { store } from 'store'; +import { store } from '..'; export const Dispatch = { clearStore: () => { store.dispatch(Actions.clearStore()); }, - updateRooms: (rooms: ServerInfo_Room[]) => { + updateRooms: (rooms: Data.ServerInfo_Room[]) => { store.dispatch(Actions.updateRooms(rooms)); }, - joinRoom: (roomInfo: ServerInfo_Room) => { + joinRoom: (roomInfo: Data.ServerInfo_Room) => { store.dispatch(Actions.joinRoom(roomInfo)); }, @@ -24,15 +21,15 @@ export const Dispatch = { store.dispatch(Actions.leaveRoom(roomId)); }, - addMessage: (roomId: number, message: Message) => { + addMessage: (roomId: number, message: Enriched.Message) => { store.dispatch(Actions.addMessage(roomId, message)); }, - updateGames: (roomId: number, games: ServerInfo_Game[]) => { + updateGames: (roomId: number, games: Data.ServerInfo_Game[]) => { store.dispatch(Actions.updateGames(roomId, games)); }, - userJoined: (roomId: number, user: ServerInfo_User) => { + userJoined: (roomId: number, user: Data.ServerInfo_User) => { store.dispatch(Actions.userJoined(roomId, user)); }, @@ -40,7 +37,7 @@ export const Dispatch = { store.dispatch(Actions.userLeft(roomId, name)); }, - sortGames: (roomId: number, field: GameSortField, order: SortDirection) => { + sortGames: (roomId: number, field: App.GameSortField, order: App.SortDirection) => { store.dispatch(Actions.sortGames(roomId, field, order)); }, diff --git a/webclient/src/store/rooms/rooms.interfaces.tsx b/webclient/src/store/rooms/rooms.interfaces.tsx index 8e9cf1943..bb1ba2375 100644 --- a/webclient/src/store/rooms/rooms.interfaces.tsx +++ b/webclient/src/store/rooms/rooms.interfaces.tsx @@ -1,4 +1,4 @@ -import { GameSortField, Message, Room, Game, SortBy, UserSortField } from 'types'; +import { App, Enriched } from '@app/types'; export interface RoomsState { rooms: RoomsStateRooms; @@ -11,12 +11,12 @@ export interface RoomsState { } export interface RoomsStateRooms { - [roomId: number]: Room; + [roomId: number]: Enriched.Room; } export interface RoomsStateGames { [roomId: number]: { - [gameId: number]: Game; + [gameId: number]: Enriched.Game; }; } @@ -31,13 +31,13 @@ export interface JoinedGames { } export interface RoomsStateMessages { - [roomId: number]: Message[]; + [roomId: number]: Enriched.Message[]; } -export interface RoomsStateSortGamesBy extends SortBy { - field: GameSortField +export interface RoomsStateSortGamesBy extends App.SortBy { + field: App.GameSortField } -export interface RoomsStateSortUsersBy extends SortBy { - field: UserSortField +export interface RoomsStateSortUsersBy extends App.SortBy { + field: App.UserSortField } diff --git a/webclient/src/store/rooms/rooms.reducer.spec.ts b/webclient/src/store/rooms/rooms.reducer.spec.ts index df0ef2836..874888944 100644 --- a/webclient/src/store/rooms/rooms.reducer.spec.ts +++ b/webclient/src/store/rooms/rooms.reducer.spec.ts @@ -1,4 +1,4 @@ -import { GameSortField, SortDirection } from 'types'; +import { App } from '@app/types'; import { roomsReducer } from './rooms.reducer'; import { Types, MAX_ROOM_MESSAGES } from './rooms.types'; import { makeGame, makeMessage, makeRoom, makeRoomsState, makeUser } from './__mocks__/rooms-fixtures'; @@ -191,11 +191,10 @@ describe('UPDATE_GAMES', () => { expect(result.rooms[1].gameList.find(g => g.gameId === 2).description).toBe('new'); }); - it('returns { ...state } (not identity) when roomId is unknown', () => { + it('returns state identity when roomId is unknown', () => { const state = makeRoomsState({ rooms: {} }); const result = roomsReducer(state, { type: Types.UPDATE_GAMES, roomId: 999, games: [] }); - expect(result).not.toBe(state); - expect(result.rooms).toEqual(state.rooms); + expect(result).toBe(state); }); }); @@ -231,10 +230,10 @@ describe('SORT_GAMES', () => { const result = roomsReducer(state, { type: Types.SORT_GAMES, roomId: 1, - field: GameSortField.START_TIME, - order: SortDirection.ASC, + field: App.GameSortField.START_TIME, + order: App.SortDirection.ASC, }); - expect(result.sortGamesBy).toEqual({ field: GameSortField.START_TIME, order: SortDirection.ASC }); + expect(result.sortGamesBy).toEqual({ field: App.GameSortField.START_TIME, order: App.SortDirection.ASC }); }); }); diff --git a/webclient/src/store/rooms/rooms.reducer.tsx b/webclient/src/store/rooms/rooms.reducer.tsx index bbf5a1ed5..ec57b2255 100644 --- a/webclient/src/store/rooms/rooms.reducer.tsx +++ b/webclient/src/store/rooms/rooms.reducer.tsx @@ -1,6 +1,6 @@ import * as _ from 'lodash'; -import { GameSortField, Room, UserSortField, SortDirection } from 'types'; +import { App, Enriched } from '@app/types'; import { normalizeGameObject, normalizeGametypeMap, normalizeRoomInfo, normalizeUserMessage, SortUtil } from '../common'; @@ -15,12 +15,12 @@ const initialState: RoomsState = { joinedGameIds: {}, messages: {}, sortGamesBy: { - field: GameSortField.START_TIME, - order: SortDirection.DESC + field: App.GameSortField.START_TIME, + order: App.SortDirection.DESC }, sortUsersBy: { - field: UserSortField.NAME, - order: SortDirection.ASC + field: App.UserSortField.NAME, + order: App.SortDirection.ASC } }; @@ -46,11 +46,11 @@ export const roomsReducer = (state = initialState, action: RoomsAction) => { const gametypeMap = normalizeGametypeMap(gametypeList); rooms[roomId] = { - ...(existing as Room), + ...(existing as Enriched.Room), ...roomMeta, gametypeMap, - gameList: (existing as Room).gameList, - userList: (existing as Room).userList, + gameList: (existing as Enriched.Room).gameList, + userList: (existing as Enriched.Room).userList, order, }; }); @@ -149,8 +149,10 @@ export const roomsReducer = (state = initialState, action: RoomsAction) => { const { rooms, sortGamesBy } = state; const room = rooms[roomId]; - if (!room) { - return { ...state }; + // An empty gameList means no game updates — skip to avoid + // overwriting the existing game list with an empty one. + if (!room || !games?.length) { + return state; } // Normalize incoming raw proto games using the room's gametypeMap diff --git a/webclient/src/store/server/__mocks__/server-fixtures.ts b/webclient/src/store/server/__mocks__/server-fixtures.ts index 7a5cf20fb..5146db4c2 100644 --- a/webclient/src/store/server/__mocks__/server-fixtures.ts +++ b/webclient/src/store/server/__mocks__/server-fixtures.ts @@ -1,33 +1,13 @@ -import { - Game, - ProtoInit, - SortDirection, - StatusEnum, - UserSortField, - WebSocketConnectOptions, -} from 'types'; -import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; -import type { ServerInfo_Ban } from 'generated/proto/serverinfo_ban_pb'; -import type { ServerInfo_Warning } from 'generated/proto/serverinfo_warning_pb'; -import type { Response_WarnList } from 'generated/proto/response_warn_list_pb'; -import type { ServerInfo_ReplayMatch } from 'generated/proto/serverinfo_replay_match_pb'; -import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb'; -import type { Response_DeckList } from 'generated/proto/response_deck_list_pb'; -import type { ServerInfo_DeckStorage_TreeItem } from 'generated/proto/serverinfo_deckstorage_pb'; +import { App, Data, Enriched } from '@app/types'; +import type { MessageInitShape } from '@bufbuild/protobuf'; + import { create } from '@bufbuild/protobuf'; -import { ServerInfo_GameSchema } from 'generated/proto/serverinfo_game_pb'; -import { ServerInfo_UserSchema } from 'generated/proto/serverinfo_user_pb'; -import { ServerInfo_ReplayMatchSchema } from 'generated/proto/serverinfo_replay_match_pb'; -import { ServerInfo_ChatMessageSchema } from 'generated/proto/serverinfo_chat_message_pb'; -import { ServerInfo_BanSchema } from 'generated/proto/serverinfo_ban_pb'; -import { ServerInfo_WarningSchema } from 'generated/proto/serverinfo_warning_pb'; -import { Response_WarnListSchema } from 'generated/proto/response_warn_list_pb'; -import { ServerInfo_DeckStorage_TreeItemSchema, ServerInfo_DeckStorage_FolderSchema } from 'generated/proto/serverinfo_deckstorage_pb'; -import { Response_DeckListSchema } from 'generated/proto/response_deck_list_pb'; import { ServerState } from '../server.interfaces'; -export function makeUser(overrides: ProtoInit = {}): ServerInfo_User { - return create(ServerInfo_UserSchema, { +export function makeUser( + overrides: MessageInitShape = {} +): Data.ServerInfo_User { + return create(Data.ServerInfo_UserSchema, { name: 'TestUser', accountageSecs: 0n, privlevel: '', @@ -36,8 +16,10 @@ export function makeUser(overrides: ProtoInit = {}): ServerInfo }); } -export function makeLogItem(overrides: ProtoInit = {}): ServerInfo_ChatMessage { - return create(ServerInfo_ChatMessageSchema, { +export function makeLogItem( + overrides: MessageInitShape = {} +): Data.ServerInfo_ChatMessage { + return create(Data.ServerInfo_ChatMessageSchema, { message: '', senderId: '', senderIp: '', @@ -50,8 +32,10 @@ export function makeLogItem(overrides: ProtoInit = {}): }); } -export function makeBanHistoryItem(overrides: ProtoInit = {}): ServerInfo_Ban { - return create(ServerInfo_BanSchema, { +export function makeBanHistoryItem( + overrides: MessageInitShape = {} +): Data.ServerInfo_Ban { + return create(Data.ServerInfo_BanSchema, { adminId: '', adminName: '', banTime: '', @@ -62,8 +46,10 @@ export function makeBanHistoryItem(overrides: ProtoInit = {}): S }); } -export function makeWarnHistoryItem(overrides: ProtoInit = {}): ServerInfo_Warning { - return create(ServerInfo_WarningSchema, { +export function makeWarnHistoryItem( + overrides: MessageInitShape = {} +): Data.ServerInfo_Warning { + return create(Data.ServerInfo_WarningSchema, { userName: '', adminName: '', reason: '', @@ -72,8 +58,10 @@ export function makeWarnHistoryItem(overrides: ProtoInit = { }); } -export function makeWarnListItem(overrides: ProtoInit = {}): Response_WarnList { - return create(Response_WarnListSchema, { +export function makeWarnListItem( + overrides: MessageInitShape = {} +): Data.Response_WarnList { + return create(Data.Response_WarnListSchema, { warning: [], userName: '', userClientid: '', @@ -81,23 +69,29 @@ export function makeWarnListItem(overrides: ProtoInit = {}): }); } -export function makeDeckTreeItem(overrides: ProtoInit = {}): ServerInfo_DeckStorage_TreeItem { - return create(ServerInfo_DeckStorage_TreeItemSchema, { +export function makeDeckTreeItem( + overrides: MessageInitShape = {}, +): Data.ServerInfo_DeckStorage_TreeItem { + return create(Data.ServerInfo_DeckStorage_TreeItemSchema, { id: 1, name: 'item', ...overrides, }); } -export function makeDeckList(overrides: ProtoInit = {}): Response_DeckList { - return create(Response_DeckListSchema, { - root: create(ServerInfo_DeckStorage_FolderSchema, { items: [] }), +export function makeDeckList( + overrides: MessageInitShape = {} +): Data.Response_DeckList { + return create(Data.Response_DeckListSchema, { + root: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [] }), ...overrides, }); } -export function makeReplayMatch(overrides: ProtoInit = {}): ServerInfo_ReplayMatch { - return create(ServerInfo_ReplayMatchSchema, { +export function makeReplayMatch( + overrides: MessageInitShape = {} +): Data.ServerInfo_ReplayMatch { + return create(Data.ServerInfo_ReplayMatchSchema, { gameId: 1, roomName: 'Test Room', timeStarted: 0, @@ -110,16 +104,26 @@ export function makeReplayMatch(overrides: ProtoInit = { }); } -export function makeGame(overrides: Partial = {}): Game { - return { ...create(ServerInfo_GameSchema, { description: '' }), gameType: '', ...overrides }; +export function makeGame(overrides: Partial = {}): Enriched.Game { + return { ...create(Data.ServerInfo_GameSchema, { description: '' }), gameType: '', ...overrides }; } -export function makeConnectOptions(overrides: Partial = {}): WebSocketConnectOptions { +export function makeLoginSuccessContext( + overrides: Partial = {} +): Enriched.LoginSuccessContext { + return { + hashedPassword: 'hash', + ...overrides, + }; +} + +export function makePendingActivationContext( + overrides: Partial = {} +): Enriched.PendingActivationContext { return { host: 'localhost', port: '4747', userName: 'user', - password: 'pass', ...overrides, }; } @@ -131,7 +135,7 @@ export function makeServerState(overrides: Partial = {}): ServerSta ignoreList: [], status: { connectionAttemptMade: false, - state: StatusEnum.DISCONNECTED, + state: App.StatusEnum.DISCONNECTED, description: null, }, info: { @@ -147,8 +151,8 @@ export function makeServerState(overrides: Partial = {}): ServerSta user: null, users: [], sortUsersBy: { - field: UserSortField.NAME, - order: SortDirection.ASC, + field: App.UserSortField.NAME, + order: App.SortDirection.ASC, }, messages: {}, userInfo: {}, diff --git a/webclient/src/store/server/server.actions.spec.ts b/webclient/src/store/server/server.actions.spec.ts index b25e768f1..277ac816e 100644 --- a/webclient/src/store/server/server.actions.spec.ts +++ b/webclient/src/store/server/server.actions.spec.ts @@ -1,16 +1,14 @@ import { Actions } from './server.actions'; +import { App, Data } from '@app/types'; import { Types } from './server.types'; import { create } from '@bufbuild/protobuf'; -import { Event_NotifyUserSchema } from 'generated/proto/event_notify_user_pb'; -import { Event_ServerShutdownSchema } from 'generated/proto/event_server_shutdown_pb'; -import { Event_UserMessageSchema } from 'generated/proto/event_user_message_pb'; import { makeBanHistoryItem, - makeConnectOptions, + makeLoginSuccessContext, + makePendingActivationContext, makeDeckList, makeDeckTreeItem, makeReplayMatch, - makeGame, makeUser, makeWarnHistoryItem, makeWarnListItem, @@ -30,7 +28,7 @@ describe('Actions', () => { }); it('loginSuccessful', () => { - const options = makeConnectOptions(); + const options = makeLoginSuccessContext(); expect(Actions.loginSuccessful(options)).toEqual({ type: Types.LOGIN_SUCCESSFUL, options }); }); @@ -38,10 +36,6 @@ describe('Actions', () => { expect(Actions.loginFailed()).toEqual({ type: Types.LOGIN_FAILED }); }); - it('connectionClosed', () => { - expect(Actions.connectionClosed(3)).toEqual({ type: Types.CONNECTION_CLOSED, reason: 3 }); - }); - it('connectionFailed', () => { expect(Actions.connectionFailed()).toEqual({ type: Types.CONNECTION_FAILED }); }); @@ -92,7 +86,7 @@ describe('Actions', () => { }); it('updateStatus', () => { - const status = { state: 2, description: 'connected' }; + const status = { state: App.StatusEnum.CONNECTED, description: 'connected' }; expect(Actions.updateStatus(status)).toEqual({ type: Types.UPDATE_STATUS, status }); }); @@ -116,7 +110,7 @@ describe('Actions', () => { }); it('viewLogs', () => { - const logs = [{ targetType: 'room' }] as any[]; + const logs = [create(Data.ServerInfo_ChatMessageSchema, { targetType: 'room' })]; expect(Actions.viewLogs(logs)).toEqual({ type: Types.VIEW_LOGS, logs }); }); @@ -153,7 +147,7 @@ describe('Actions', () => { }); it('accountAwaitingActivation', () => { - const options = makeConnectOptions(); + const options = makePendingActivationContext(); expect(Actions.accountAwaitingActivation(options)).toEqual({ type: Types.ACCOUNT_AWAITING_ACTIVATION, options }); }); @@ -222,17 +216,17 @@ describe('Actions', () => { }); it('notifyUser', () => { - const notification = create(Event_NotifyUserSchema, { type: 1, warningReason: '', customTitle: '', customContent: '' }); + const notification = create(Data.Event_NotifyUserSchema, { type: 1, warningReason: '', customTitle: '', customContent: '' }); expect(Actions.notifyUser(notification)).toEqual({ type: Types.NOTIFY_USER, notification }); }); it('serverShutdown', () => { - const data = create(Event_ServerShutdownSchema, { reason: 'maintenance', minutes: 5 }); + const data = create(Data.Event_ServerShutdownSchema, { reason: 'maintenance', minutes: 5 }); expect(Actions.serverShutdown(data)).toEqual({ type: Types.SERVER_SHUTDOWN, data }); }); it('userMessage', () => { - const messageData = create(Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'hey' }); + const messageData = create(Data.Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'hey' }); expect(Actions.userMessage(messageData)).toEqual({ type: Types.USER_MESSAGE, messageData }); }); @@ -360,9 +354,8 @@ describe('Actions', () => { }); it('gamesOfUser', () => { - const games = [makeGame({ gameId: 1 })]; - const gametypeMap = { 1: 'Standard' }; - expect(Actions.gamesOfUser('alice', games, gametypeMap)).toEqual({ type: Types.GAMES_OF_USER, userName: 'alice', games, gametypeMap }); + const response = create(Data.Response_GetGamesOfUserSchema, { roomList: [], gameList: [] }); + expect(Actions.gamesOfUser('alice', response)).toEqual({ type: Types.GAMES_OF_USER, userName: 'alice', response }); }); it('clearRegistrationErrors', () => { diff --git a/webclient/src/store/server/server.actions.ts b/webclient/src/store/server/server.actions.ts index a29f4d49d..143a800f7 100644 --- a/webclient/src/store/server/server.actions.ts +++ b/webclient/src/store/server/server.actions.ts @@ -1,16 +1,4 @@ -import { - GametypeMap, WebSocketConnectOptions -} from 'types'; -import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; -import type { ServerInfo_Ban } from 'generated/proto/serverinfo_ban_pb'; -import type { ServerInfo_Warning } from 'generated/proto/serverinfo_warning_pb'; -import type { Response_WarnList } from 'generated/proto/response_warn_list_pb'; -import type { ServerInfo_ReplayMatch } from 'generated/proto/serverinfo_replay_match_pb'; -import type { Response_DeckList } from 'generated/proto/response_deck_list_pb'; -import type { ServerInfo_DeckStorage_TreeItem } from 'generated/proto/serverinfo_deckstorage_pb'; -import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb'; -import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; -import { NotifyUserData, ServerShutdownData, UserMessageData } from 'websocket/events/session/interfaces'; +import { Data, Enriched } from '@app/types'; import { ServerStateStatus } from './server.interfaces'; import { Types } from './server.types'; @@ -24,17 +12,13 @@ export const Actions = { connectionAttempted: () => ({ type: Types.CONNECTION_ATTEMPTED }), - loginSuccessful: (options: WebSocketConnectOptions) => ({ + loginSuccessful: (options: Enriched.LoginSuccessContext) => ({ type: Types.LOGIN_SUCCESSFUL, options }), loginFailed: () => ({ type: Types.LOGIN_FAILED, }), - connectionClosed: (reason: number) => ({ - type: Types.CONNECTION_CLOSED, - reason - }), connectionFailed: () => ({ type: Types.CONNECTION_FAILED, }), @@ -48,11 +32,11 @@ export const Actions = { type: Types.SERVER_MESSAGE, message }), - updateBuddyList: (buddyList: ServerInfo_User[]) => ({ + updateBuddyList: (buddyList: Data.ServerInfo_User[]) => ({ type: Types.UPDATE_BUDDY_LIST, buddyList }), - addToBuddyList: (user: ServerInfo_User) => ({ + addToBuddyList: (user: Data.ServerInfo_User) => ({ type: Types.ADD_TO_BUDDY_LIST, user }), @@ -60,11 +44,11 @@ export const Actions = { type: Types.REMOVE_FROM_BUDDY_LIST, userName }), - updateIgnoreList: (ignoreList: ServerInfo_User[]) => ({ + updateIgnoreList: (ignoreList: Data.ServerInfo_User[]) => ({ type: Types.UPDATE_IGNORE_LIST, ignoreList }), - addToIgnoreList: (user: ServerInfo_User) => ({ + addToIgnoreList: (user: Data.ServerInfo_User) => ({ type: Types.ADD_TO_IGNORE_LIST, user }), @@ -76,19 +60,19 @@ export const Actions = { type: Types.UPDATE_INFO, info }), - updateStatus: (status: ServerStateStatus) => ({ + updateStatus: (status: Pick) => ({ type: Types.UPDATE_STATUS, status }), - updateUser: (user: ServerInfo_User) => ({ + updateUser: (user: Data.ServerInfo_User) => ({ type: Types.UPDATE_USER, user }), - updateUsers: (users: ServerInfo_User[]) => ({ + updateUsers: (users: Data.ServerInfo_User[]) => ({ type: Types.UPDATE_USERS, users }), - userJoined: (user: ServerInfo_User) => ({ + userJoined: (user: Data.ServerInfo_User) => ({ type: Types.USER_JOINED, user }), @@ -96,7 +80,7 @@ export const Actions = { type: Types.USER_LEFT, name }), - viewLogs: (logs: ServerInfo_ChatMessage[]) => ({ + viewLogs: (logs: Data.ServerInfo_ChatMessage[]) => ({ type: Types.VIEW_LOGS, logs }), @@ -129,7 +113,7 @@ export const Actions = { clearRegistrationErrors: () => ({ type: Types.CLEAR_REGISTRATION_ERRORS, }), - accountAwaitingActivation: (options: WebSocketConnectOptions) => ({ + accountAwaitingActivation: (options: Enriched.PendingActivationContext) => ({ type: Types.ACCOUNT_AWAITING_ACTIVATION, options }), @@ -169,27 +153,27 @@ export const Actions = { accountPasswordChange: () => ({ type: Types.ACCOUNT_PASSWORD_CHANGE, }), - accountEditChanged: (user: Partial) => ({ + accountEditChanged: (user: Partial) => ({ type: Types.ACCOUNT_EDIT_CHANGED, user, }), - accountImageChanged: (user: Partial) => ({ + accountImageChanged: (user: Partial) => ({ type: Types.ACCOUNT_IMAGE_CHANGED, user, }), - getUserInfo: (userInfo: ServerInfo_User) => ({ + getUserInfo: (userInfo: Data.ServerInfo_User) => ({ type: Types.GET_USER_INFO, userInfo, }), - notifyUser: (notification: NotifyUserData) => ({ + notifyUser: (notification: Data.Event_NotifyUser) => ({ type: Types.NOTIFY_USER, notification, }), - serverShutdown: (data: ServerShutdownData) => ({ + serverShutdown: (data: Data.Event_ServerShutdown) => ({ type: Types.SERVER_SHUTDOWN, data, }), - userMessage: (messageData: UserMessageData) => ({ + userMessage: (messageData: Data.Event_UserMessage) => ({ type: Types.USER_MESSAGE, messageData, }), @@ -207,17 +191,17 @@ export const Actions = { type: Types.BAN_FROM_SERVER, userName, }), - banHistory: (userName: string, banHistory: ServerInfo_Ban[]) => ({ + banHistory: (userName: string, banHistory: Data.ServerInfo_Ban[]) => ({ type: Types.BAN_HISTORY, userName, banHistory, }), - warnHistory: (userName: string, warnHistory: ServerInfo_Warning[]) => ({ + warnHistory: (userName: string, warnHistory: Data.ServerInfo_Warning[]) => ({ type: Types.WARN_HISTORY, userName, warnHistory, }), - warnListOptions: (warnList: Response_WarnList[]) => ({ + warnListOptions: (warnList: Data.Response_WarnList[]) => ({ type: Types.WARN_LIST_OPTIONS, warnList, }), @@ -245,17 +229,17 @@ export const Actions = { userName, notes, }), - replayList: (matchList: ServerInfo_ReplayMatch[]) => ({ type: Types.REPLAY_LIST, matchList }), - replayAdded: (matchInfo: ServerInfo_ReplayMatch) => ({ type: Types.REPLAY_ADDED, matchInfo }), + replayList: (matchList: Data.ServerInfo_ReplayMatch[]) => ({ type: Types.REPLAY_LIST, matchList }), + replayAdded: (matchInfo: Data.ServerInfo_ReplayMatch) => ({ type: Types.REPLAY_ADDED, matchInfo }), replayModifyMatch: (gameId: number, doNotHide: boolean) => ({ type: Types.REPLAY_MODIFY_MATCH, gameId, doNotHide }), replayDeleteMatch: (gameId: number) => ({ type: Types.REPLAY_DELETE_MATCH, gameId }), - backendDecks: (deckList: Response_DeckList) => ({ type: Types.BACKEND_DECKS, deckList }), + backendDecks: (deckList: Data.Response_DeckList) => ({ type: Types.BACKEND_DECKS, deckList }), deckNewDir: (path: string, dirName: string) => ({ type: Types.DECK_NEW_DIR, path, dirName }), deckDelDir: (path: string) => ({ type: Types.DECK_DEL_DIR, path }), - deckUpload: (path: string, treeItem: ServerInfo_DeckStorage_TreeItem) => ({ type: Types.DECK_UPLOAD, path, treeItem }), + deckUpload: (path: string, treeItem: Data.ServerInfo_DeckStorage_TreeItem) => ({ type: Types.DECK_UPLOAD, path, treeItem }), deckDelete: (deckId: number) => ({ type: Types.DECK_DELETE, deckId }), - gamesOfUser: (userName: string, games: ServerInfo_Game[], gametypeMap: GametypeMap) => - ({ type: Types.GAMES_OF_USER, userName, games, gametypeMap }), + gamesOfUser: (userName: string, response: Data.Response_GetGamesOfUser) => + ({ type: Types.GAMES_OF_USER, userName, response }), } export type ServerAction = ReturnType; diff --git a/webclient/src/store/server/server.dispatch.spec.ts b/webclient/src/store/server/server.dispatch.spec.ts index ad2365a79..d58d3fcb1 100644 --- a/webclient/src/store/server/server.dispatch.spec.ts +++ b/webclient/src/store/server/server.dispatch.spec.ts @@ -1,26 +1,22 @@ -vi.mock('store', () => ({ store: { dispatch: vi.fn() } })); +vi.mock('..', () => ({ store: { dispatch: vi.fn() } })); -import { store } from 'store'; +import { store } from '..'; import { Actions } from './server.actions'; import { Dispatch } from './server.dispatch'; +import { App, Data } from '@app/types'; import { create } from '@bufbuild/protobuf'; -import { Event_NotifyUserSchema } from 'generated/proto/event_notify_user_pb'; -import { Event_ServerShutdownSchema } from 'generated/proto/event_server_shutdown_pb'; -import { Event_UserMessageSchema } from 'generated/proto/event_user_message_pb'; import { makeBanHistoryItem, - makeConnectOptions, + makeLoginSuccessContext, + makePendingActivationContext, makeDeckList, makeDeckTreeItem, - makeGame, makeReplayMatch, makeUser, makeWarnHistoryItem, makeWarnListItem, } from './__mocks__/server-fixtures'; -beforeEach(() => vi.clearAllMocks()); - describe('Dispatch', () => { it('initialized dispatches Actions.initialized()', () => { Dispatch.initialized(); @@ -38,7 +34,7 @@ describe('Dispatch', () => { }); it('loginSuccessful dispatches Actions.loginSuccessful()', () => { - const options = makeConnectOptions(); + const options = makeLoginSuccessContext(); Dispatch.loginSuccessful(options); expect(store.dispatch).toHaveBeenCalledWith(Actions.loginSuccessful(options)); }); @@ -48,11 +44,6 @@ describe('Dispatch', () => { expect(store.dispatch).toHaveBeenCalledWith(Actions.loginFailed()); }); - it('connectionClosed dispatches Actions.connectionClosed()', () => { - Dispatch.connectionClosed(3); - expect(store.dispatch).toHaveBeenCalledWith(Actions.connectionClosed(3)); - }); - it('connectionFailed dispatches Actions.connectionFailed()', () => { Dispatch.connectionFailed(); expect(store.dispatch).toHaveBeenCalledWith(Actions.connectionFailed()); @@ -108,8 +99,8 @@ describe('Dispatch', () => { }); it('updateStatus dispatches Actions.updateStatus({ state, description })', () => { - Dispatch.updateStatus(2, 'ok'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.updateStatus({ state: 2, description: 'ok' })); + Dispatch.updateStatus(App.StatusEnum.CONNECTED, 'ok'); + expect(store.dispatch).toHaveBeenCalledWith(Actions.updateStatus({ state: App.StatusEnum.CONNECTED, description: 'ok' })); }); it('updateUser dispatches Actions.updateUser()', () => { @@ -136,7 +127,7 @@ describe('Dispatch', () => { }); it('viewLogs dispatches Actions.viewLogs()', () => { - const logs = [{ targetType: 'room' }] as any[]; + const logs = [create(Data.ServerInfo_ChatMessageSchema, { targetType: 'room' })]; Dispatch.viewLogs(logs); expect(store.dispatch).toHaveBeenCalledWith(Actions.viewLogs(logs)); }); @@ -187,7 +178,7 @@ describe('Dispatch', () => { }); it('accountAwaitingActivation dispatches correctly', () => { - const options = makeConnectOptions(); + const options = makePendingActivationContext(); Dispatch.accountAwaitingActivation(options); expect(store.dispatch).toHaveBeenCalledWith(Actions.accountAwaitingActivation(options)); }); @@ -266,19 +257,19 @@ describe('Dispatch', () => { }); it('notifyUser dispatches correctly', () => { - const notification = create(Event_NotifyUserSchema, { type: 1, warningReason: '', customTitle: '', customContent: '' }); + const notification = create(Data.Event_NotifyUserSchema, { type: 1, warningReason: '', customTitle: '', customContent: '' }); Dispatch.notifyUser(notification); expect(store.dispatch).toHaveBeenCalledWith(Actions.notifyUser(notification)); }); it('serverShutdown dispatches correctly', () => { - const data = create(Event_ServerShutdownSchema, { reason: 'maintenance', minutes: 5 }); + const data = create(Data.Event_ServerShutdownSchema, { reason: 'maintenance', minutes: 5 }); Dispatch.serverShutdown(data); expect(store.dispatch).toHaveBeenCalledWith(Actions.serverShutdown(data)); }); it('userMessage dispatches correctly', () => { - const messageData = create(Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'hey' }); + const messageData = create(Data.Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'hey' }); Dispatch.userMessage(messageData); expect(store.dispatch).toHaveBeenCalledWith(Actions.userMessage(messageData)); }); @@ -391,10 +382,9 @@ describe('Dispatch', () => { }); it('gamesOfUser dispatches correctly', () => { - const games = [makeGame({ gameId: 1 })]; - const gametypeMap = { 1: 'Standard' }; - Dispatch.gamesOfUser('alice', games, gametypeMap); - expect(store.dispatch).toHaveBeenCalledWith(Actions.gamesOfUser('alice', games, gametypeMap)); + const response = create(Data.Response_GetGamesOfUserSchema, { roomList: [], gameList: [] }); + Dispatch.gamesOfUser('alice', response); + expect(store.dispatch).toHaveBeenCalledWith(Actions.gamesOfUser('alice', response)); }); it('clearRegistrationErrors dispatches correctly', () => { diff --git a/webclient/src/store/server/server.dispatch.ts b/webclient/src/store/server/server.dispatch.ts index 19db7ced1..1438d96d7 100644 --- a/webclient/src/store/server/server.dispatch.ts +++ b/webclient/src/store/server/server.dispatch.ts @@ -1,18 +1,6 @@ import { Actions } from './server.actions'; -import { store } from 'store'; -import { - GametypeMap, WebSocketConnectOptions -} from 'types'; -import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; -import type { ServerInfo_Ban } from 'generated/proto/serverinfo_ban_pb'; -import type { ServerInfo_Warning } from 'generated/proto/serverinfo_warning_pb'; -import type { Response_WarnList } from 'generated/proto/response_warn_list_pb'; -import type { ServerInfo_ReplayMatch } from 'generated/proto/serverinfo_replay_match_pb'; -import type { Response_DeckList } from 'generated/proto/response_deck_list_pb'; -import type { ServerInfo_DeckStorage_TreeItem } from 'generated/proto/serverinfo_deckstorage_pb'; -import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb'; -import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; -import { NotifyUserData, ServerShutdownData, UserMessageData } from 'websocket/events/session/interfaces'; +import { store } from '..'; +import { App, Data, Enriched } from '@app/types'; export const Dispatch = { initialized: () => { @@ -24,15 +12,12 @@ export const Dispatch = { connectionAttempted: () => { store.dispatch(Actions.connectionAttempted()); }, - loginSuccessful: (options: WebSocketConnectOptions) => { + loginSuccessful: (options: Enriched.LoginSuccessContext) => { store.dispatch(Actions.loginSuccessful(options)); }, loginFailed: () => { store.dispatch(Actions.loginFailed()); }, - connectionClosed: (reason: number) => { - store.dispatch(Actions.connectionClosed(reason)); - }, connectionFailed: () => { store.dispatch(Actions.connectionFailed()); }, @@ -42,19 +27,19 @@ export const Dispatch = { testConnectionFailed: () => { store.dispatch(Actions.testConnectionFailed()); }, - updateBuddyList: (buddyList: ServerInfo_User[]) => { + updateBuddyList: (buddyList: Data.ServerInfo_User[]) => { store.dispatch(Actions.updateBuddyList(buddyList)); }, - addToBuddyList: (user: ServerInfo_User) => { + addToBuddyList: (user: Data.ServerInfo_User) => { store.dispatch(Actions.addToBuddyList(user)); }, removeFromBuddyList: (userName: string) => { store.dispatch(Actions.removeFromBuddyList(userName)); }, - updateIgnoreList: (ignoreList: ServerInfo_User[]) => { + updateIgnoreList: (ignoreList: Data.ServerInfo_User[]) => { store.dispatch(Actions.updateIgnoreList(ignoreList)); }, - addToIgnoreList: (user: ServerInfo_User) => { + addToIgnoreList: (user: Data.ServerInfo_User) => { store.dispatch(Actions.addToIgnoreList(user)); }, removeFromIgnoreList: (userName: string) => { @@ -66,25 +51,25 @@ export const Dispatch = { version })); }, - updateStatus: (state: number, description: string) => { + updateStatus: (state: App.StatusEnum, description: string) => { store.dispatch(Actions.updateStatus({ state, description })); }, - updateUser: (user: ServerInfo_User) => { + updateUser: (user: Data.ServerInfo_User) => { store.dispatch(Actions.updateUser(user)); }, - updateUsers: (users: ServerInfo_User[]) => { + updateUsers: (users: Data.ServerInfo_User[]) => { store.dispatch(Actions.updateUsers(users)); }, - userJoined: (user: ServerInfo_User) => { + userJoined: (user: Data.ServerInfo_User) => { store.dispatch(Actions.userJoined(user)); }, userLeft: (name: string) => { store.dispatch(Actions.userLeft(name)); }, - viewLogs: (logs: ServerInfo_ChatMessage[]) => { + viewLogs: (logs: Data.ServerInfo_ChatMessage[]) => { store.dispatch(Actions.viewLogs(logs)); }, clearLogs: () => { @@ -114,7 +99,7 @@ export const Dispatch = { registrationUserNameError: (error: string) => { store.dispatch(Actions.registrationUserNameError(error)); }, - accountAwaitingActivation: (options: WebSocketConnectOptions) => { + accountAwaitingActivation: (options: Enriched.PendingActivationContext) => { store.dispatch(Actions.accountAwaitingActivation(options)); }, accountActivationSuccess: () => { @@ -150,22 +135,22 @@ export const Dispatch = { accountPasswordChange: () => { store.dispatch(Actions.accountPasswordChange()); }, - accountEditChanged: (user: Partial) => { + accountEditChanged: (user: Partial) => { store.dispatch(Actions.accountEditChanged(user)); }, - accountImageChanged: (user: Partial) => { + accountImageChanged: (user: Partial) => { store.dispatch(Actions.accountImageChanged(user)); }, - getUserInfo: (userInfo: ServerInfo_User) => { + getUserInfo: (userInfo: Data.ServerInfo_User) => { store.dispatch(Actions.getUserInfo(userInfo)); }, - notifyUser: (notification: NotifyUserData) => { + notifyUser: (notification: Data.Event_NotifyUser) => { store.dispatch(Actions.notifyUser(notification)) }, - serverShutdown: (data: ServerShutdownData) => { + serverShutdown: (data: Data.Event_ServerShutdown) => { store.dispatch(Actions.serverShutdown(data)) }, - userMessage: (messageData: UserMessageData) => { + userMessage: (messageData: Data.Event_UserMessage) => { store.dispatch(Actions.userMessage(messageData)) }, addToList: (list: string, userName: string) => { @@ -177,13 +162,13 @@ export const Dispatch = { banFromServer: (userName: string) => { store.dispatch(Actions.banFromServer(userName)); }, - banHistory: (userName: string, banHistory: ServerInfo_Ban[]) => { + banHistory: (userName: string, banHistory: Data.ServerInfo_Ban[]) => { store.dispatch(Actions.banHistory(userName, banHistory)) }, - warnHistory: (userName: string, warnHistory: ServerInfo_Warning[]) => { + warnHistory: (userName: string, warnHistory: Data.ServerInfo_Warning[]) => { store.dispatch(Actions.warnHistory(userName, warnHistory)) }, - warnListOptions: (warnList: Response_WarnList[]) => { + warnListOptions: (warnList: Data.Response_WarnList[]) => { store.dispatch(Actions.warnListOptions(warnList)) }, warnUser: (userName: string) => { @@ -201,10 +186,10 @@ export const Dispatch = { updateAdminNotes: (userName: string, notes: string) => { store.dispatch(Actions.updateAdminNotes(userName, notes)); }, - replayList: (matchList: ServerInfo_ReplayMatch[]) => { + replayList: (matchList: Data.ServerInfo_ReplayMatch[]) => { store.dispatch(Actions.replayList(matchList)); }, - replayAdded: (matchInfo: ServerInfo_ReplayMatch) => { + replayAdded: (matchInfo: Data.ServerInfo_ReplayMatch) => { store.dispatch(Actions.replayAdded(matchInfo)); }, replayModifyMatch: (gameId: number, doNotHide: boolean) => { @@ -213,7 +198,7 @@ export const Dispatch = { replayDeleteMatch: (gameId: number) => { store.dispatch(Actions.replayDeleteMatch(gameId)); }, - backendDecks: (deckList: Response_DeckList) => { + backendDecks: (deckList: Data.Response_DeckList) => { store.dispatch(Actions.backendDecks(deckList)); }, deckNewDir: (path: string, dirName: string) => { @@ -222,13 +207,13 @@ export const Dispatch = { deckDelDir: (path: string) => { store.dispatch(Actions.deckDelDir(path)); }, - deckUpload: (path: string, treeItem: ServerInfo_DeckStorage_TreeItem) => { + deckUpload: (path: string, treeItem: Data.ServerInfo_DeckStorage_TreeItem) => { store.dispatch(Actions.deckUpload(path, treeItem)); }, deckDelete: (deckId: number) => { store.dispatch(Actions.deckDelete(deckId)); }, - gamesOfUser: (userName: string, games: ServerInfo_Game[], gametypeMap: GametypeMap) => { - store.dispatch(Actions.gamesOfUser(userName, games, gametypeMap)); + gamesOfUser: (userName: string, response: Data.Response_GetGamesOfUser) => { + store.dispatch(Actions.gamesOfUser(userName, response)); }, } diff --git a/webclient/src/store/server/server.interfaces.ts b/webclient/src/store/server/server.interfaces.ts index 3791c3509..371a67654 100644 --- a/webclient/src/store/server/server.interfaces.ts +++ b/webclient/src/store/server/server.interfaces.ts @@ -1,105 +1,57 @@ -import { - Game, SortBy, UserSortField -} from 'types'; -import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; -import type { ServerInfo_Ban } from 'generated/proto/serverinfo_ban_pb'; -import type { ServerInfo_Warning } from 'generated/proto/serverinfo_warning_pb'; -import type { Response_WarnList } from 'generated/proto/response_warn_list_pb'; -import type { ServerInfo_ReplayMatch } from 'generated/proto/serverinfo_replay_match_pb'; -import type { Response_DeckList } from 'generated/proto/response_deck_list_pb'; -import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb'; -import { NotifyUserData, ServerShutdownData, UserMessageData } from 'websocket/events/session/interfaces'; - -export interface ServerConnectParams { - host: string; - port: string; - userName: string; - password: string; -} - -export interface ServerRegisterParams { - host: string; - port: string; - userName: string; - password: string; - email: string; - country: string; - realName: string; -} - -export interface RequestPasswordSaltParams { - userName: string; -} - -export interface ForgotPasswordParams { - userName: string; -} - -export interface ForgotPasswordChallengeParams extends ForgotPasswordParams { - email: string; -} - -export interface ForgotPasswordResetParams extends ForgotPasswordParams { - token: string; - newPassword: string; -} - -export interface AccountActivationParams extends ServerRegisterParams { - token: string; -} +import { App, Data, Enriched } from '@app/types'; export interface ServerState { initialized: boolean; - buddyList: ServerInfo_User[]; - ignoreList: ServerInfo_User[]; + buddyList: Data.ServerInfo_User[]; + ignoreList: Data.ServerInfo_User[]; info: ServerStateInfo; status: ServerStateStatus; logs: ServerStateLogs; - user: ServerInfo_User; - users: ServerInfo_User[]; + user: Data.ServerInfo_User | null; + users: Data.ServerInfo_User[]; sortUsersBy: ServerStateSortUsersBy; messages: { - [userName: string]: UserMessageData[]; + [userName: string]: Data.Event_UserMessage[]; } userInfo: { - [userName: string]: ServerInfo_User; + [userName: string]: Data.ServerInfo_User; } - notifications: NotifyUserData[]; - serverShutdown: ServerShutdownData; + notifications: Data.Event_NotifyUser[]; + serverShutdown: Data.Event_ServerShutdown | null; banUser: string; banHistory: { - [userName: string]: ServerInfo_Ban[]; + [userName: string]: Data.ServerInfo_Ban[]; }; warnHistory: { - [userName: string]: ServerInfo_Warning[]; + [userName: string]: Data.ServerInfo_Warning[]; }; - warnListOptions: Response_WarnList[]; + warnListOptions: Data.Response_WarnList[]; warnUser: string; adminNotes: { [userName: string]: string }; - replays: ServerInfo_ReplayMatch[]; - backendDecks: Response_DeckList | null; - gamesOfUser: { [userName: string]: Game[] }; + replays: Data.ServerInfo_ReplayMatch[]; + backendDecks: Data.Response_DeckList | null; + gamesOfUser: { [userName: string]: Enriched.Game[] }; registrationError: string | null; } export interface ServerStateStatus { connectionAttemptMade: boolean; - description: string; - state: number; + description: string | null; + state: App.StatusEnum; } export interface ServerStateInfo { - message: string; - name: string; - version: string; + message: string | null; + name: string | null; + version: string | null; } export interface ServerStateLogs { - room: ServerInfo_ChatMessage[]; - game: ServerInfo_ChatMessage[]; - chat: ServerInfo_ChatMessage[]; + room: Data.ServerInfo_ChatMessage[]; + game: Data.ServerInfo_ChatMessage[]; + chat: Data.ServerInfo_ChatMessage[]; } -export interface ServerStateSortUsersBy extends SortBy { - field: UserSortField +export interface ServerStateSortUsersBy extends App.SortBy { + field: App.UserSortField } diff --git a/webclient/src/store/server/server.reducer.spec.ts b/webclient/src/store/server/server.reducer.spec.ts index 573683a76..bee69ac50 100644 --- a/webclient/src/store/server/server.reducer.spec.ts +++ b/webclient/src/store/server/server.reducer.spec.ts @@ -1,13 +1,10 @@ -import { StatusEnum } from 'types'; -import { ServerInfo_User_UserLevelFlag as UserLevelFlag } from 'generated/proto/serverinfo_user_pb'; +import { App, Data } from '@app/types'; import { create } from '@bufbuild/protobuf'; -import { Event_UserMessageSchema } from 'generated/proto/event_user_message_pb'; -import { ServerInfo_DeckStorage_FolderSchema, ServerInfo_DeckStorage_TreeItemSchema } from 'generated/proto/serverinfo_deckstorage_pb'; import { serverReducer } from './server.reducer'; import { Types } from './server.types'; import { makeBanHistoryItem, - makeConnectOptions, + makePendingActivationContext, makeDeckList, makeDeckTreeItem, makeGame, @@ -19,6 +16,8 @@ import { makeWarnListItem, } from './__mocks__/server-fixtures'; +const UserLevelFlag = Data.ServerInfo_User_UserLevelFlag; + // ── Initialisation ─────────────────────────────────────────────────────────── describe('Initialisation', () => { @@ -26,7 +25,7 @@ describe('Initialisation', () => { const result = serverReducer(undefined, { type: '@@INIT' }); expect(result.initialized).toBe(false); expect(result.buddyList).toEqual([]); - expect(result.status.state).toBe(StatusEnum.DISCONNECTED); + expect(result.status.state).toBe(App.StatusEnum.DISCONNECTED); }); it('INITIALIZED → resets to initialState with initialized: true', () => { @@ -38,7 +37,7 @@ describe('Initialisation', () => { }); it('CLEAR_STORE → resets to initialState but preserves status', () => { - const status = { state: StatusEnum.LOGGED_IN, description: 'logged in' }; + const status = { state: App.StatusEnum.LOGGED_IN, description: 'logged in', connectionAttemptMade: true }; const state = makeServerState({ status, banUser: 'someone' }); const result = serverReducer(state, { type: Types.CLEAR_STORE }); expect(result.banUser).toBe(''); @@ -57,13 +56,13 @@ describe('Initialisation', () => { describe('Account & Connection', () => { it('CONNECTION_ATTEMPTED → sets connectionAttemptMade to true', () => { - const state = makeServerState({ status: { connectionAttemptMade: false, state: StatusEnum.DISCONNECTED, description: null } }); + const state = makeServerState({ status: { connectionAttemptMade: false, state: App.StatusEnum.DISCONNECTED, description: null } }); const result = serverReducer(state, { type: Types.CONNECTION_ATTEMPTED }); expect(result.status.connectionAttemptMade).toBe(true); }); it('ACCOUNT_AWAITING_ACTIVATION → returns state unchanged', () => { - const options = makeConnectOptions(); + const options = makePendingActivationContext(); const state = makeServerState(); const result = serverReducer(state, { type: Types.ACCOUNT_AWAITING_ACTIVATION, options }); expect(result).toBe(state); @@ -133,11 +132,13 @@ describe('Server Info & Status', () => { expect(result.info.message).toBe('hi'); }); - it('UPDATE_STATUS → replaces state.status entirely', () => { + it('UPDATE_STATUS → merges state and description into status', () => { const state = makeServerState(); - const status = { state: StatusEnum.LOGGED_IN, description: 'ok' }; - const result = serverReducer(state, { type: Types.UPDATE_STATUS, status }); - expect(result.status).toEqual(status); + const update = { state: App.StatusEnum.LOGGED_IN, description: 'ok' }; + const result = serverReducer(state, { type: Types.UPDATE_STATUS, status: update }); + expect(result.status.state).toBe(App.StatusEnum.LOGGED_IN); + expect(result.status.description).toBe('ok'); + expect(result.status.connectionAttemptMade).toBe(false); }); }); @@ -281,12 +282,12 @@ describe('Messaging', () => { }); it('USER_MESSAGE → appends to existing messages for that user', () => { - const existingMsg = create(Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'first' }); + const existingMsg = create(Data.Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'first' }); const state = makeServerState({ user: makeUser({ name: 'Bob' }), messages: { Alice: [existingMsg] }, }); - const newMsg = create(Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'second' }); + const newMsg = create(Data.Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'second' }); const result = serverReducer(state, { type: Types.USER_MESSAGE, messageData: newMsg }); expect(result.messages['Alice']).toHaveLength(2); }); @@ -482,11 +483,11 @@ describe('Deck Storage', () => { }); it('DECK_UPLOAD with nested path → inserts into matching subfolder', () => { - const subfolder = create(ServerInfo_DeckStorage_TreeItemSchema, { - id: 0, name: 'myDecks', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] }) + const subfolder = create(Data.ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: 'myDecks', folder: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [] }) }); const state = makeServerState({ - backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) }) + backendDecks: makeDeckList({ root: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) }) }); const item = makeDeckTreeItem({ name: 'new.cod' }); const result = serverReducer(state, { type: Types.DECK_UPLOAD, path: 'myDecks', treeItem: item }); @@ -512,18 +513,20 @@ describe('Deck Storage', () => { it('DECK_DELETE → removes item by id from tree', () => { const item = makeDeckTreeItem({ id: 7 }); - const state = makeServerState({ backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [item] }) }) }); + const state = makeServerState({ + backendDecks: makeDeckList({ root: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [item] }) }), + }); const result = serverReducer(state, { type: Types.DECK_DELETE, deckId: 7 }); expect(result.backendDecks.root.items).toHaveLength(0); }); it('DECK_DELETE → recursively removes item nested inside a subfolder', () => { const nested = makeDeckTreeItem({ id: 9, name: 'nested.cod' }); - const subfolder = create(ServerInfo_DeckStorage_TreeItemSchema, { - id: 0, name: 'sub', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [nested] }) + const subfolder = create(Data.ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: 'sub', folder: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [nested] }) }); const state = makeServerState({ - backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) }) + backendDecks: makeDeckList({ root: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) }) }); const result = serverReducer(state, { type: Types.DECK_DELETE, deckId: 9 }); expect(result.backendDecks.root.items[0].folder.items).toHaveLength(0); @@ -544,11 +547,11 @@ describe('Deck Storage', () => { }); it('DECK_NEW_DIR nested → inserts folder inside matching subfolder', () => { - const subfolder = create(ServerInfo_DeckStorage_TreeItemSchema, { - id: 0, name: 'parent', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] }) + const subfolder = create(Data.ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: 'parent', folder: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [] }) }); const state = makeServerState({ - backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) }) + backendDecks: makeDeckList({ root: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) }) }); const result = serverReducer(state, { type: Types.DECK_NEW_DIR, path: 'parent', dirName: 'child' }); const parent = result.backendDecks.root.items.find(i => i.name === 'parent'); @@ -563,36 +566,36 @@ describe('Deck Storage', () => { }); it('DECK_DEL_DIR → removes folder from root by name', () => { - const subfolder = create(ServerInfo_DeckStorage_TreeItemSchema, { - id: 0, name: 'myDir', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] }) + const subfolder = create(Data.ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: 'myDir', folder: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [] }) }); const state = makeServerState({ - backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) }) + backendDecks: makeDeckList({ root: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) }) }); const result = serverReducer(state, { type: Types.DECK_DEL_DIR, path: 'myDir' }); expect(result.backendDecks.root.items).toHaveLength(0); }); it('DECK_DEL_DIR → returns deck tree unchanged when path is empty', () => { - const subfolder = create(ServerInfo_DeckStorage_TreeItemSchema, { - id: 0, name: 'keep', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] }) + const subfolder = create(Data.ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: 'keep', folder: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [] }) }); const state = makeServerState({ - backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) }) + backendDecks: makeDeckList({ root: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [subfolder] }) }) }); const result = serverReducer(state, { type: Types.DECK_DEL_DIR, path: '' }); expect(result.backendDecks.root.items).toHaveLength(1); }); it('DECK_DEL_DIR → recursively removes nested subfolder via multi-segment path', () => { - const child = create(ServerInfo_DeckStorage_TreeItemSchema, { - id: 0, name: 'child', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] }) + const child = create(Data.ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: 'child', folder: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [] }) }); - const parent = create(ServerInfo_DeckStorage_TreeItemSchema, { - id: 0, name: 'parent', folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [child] }) + const parent = create(Data.ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: 'parent', folder: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [child] }) }); const state = makeServerState({ - backendDecks: makeDeckList({ root: create(ServerInfo_DeckStorage_FolderSchema, { items: [parent] }) }) + backendDecks: makeDeckList({ root: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [parent] }) }) }); const result = serverReducer(state, { type: Types.DECK_DEL_DIR, path: 'parent/child' }); expect(result.backendDecks.root.items[0].folder.items).toHaveLength(0); @@ -603,24 +606,31 @@ describe('Deck Storage', () => { describe('GAMES_OF_USER', () => { it('stores normalized games keyed by userName', () => { - const games = [makeGame({ gameId: 5 })]; + const response = create(Data.Response_GetGamesOfUserSchema, { + gameList: [create(Data.ServerInfo_GameSchema, { gameId: 5, description: '' })], + roomList: [], + }); const state = makeServerState(); - const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', games, gametypeMap: {} }); - expect(result.gamesOfUser['alice']).toEqual(games); + const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', response }); + expect(result.gamesOfUser['alice']).toEqual([makeGame({ gameId: 5 })]); }); it('overwrites previous games for same user', () => { const old = [makeGame({ gameId: 1 })]; - const fresh = [makeGame({ gameId: 2 })]; + const response = create(Data.Response_GetGamesOfUserSchema, { + gameList: [create(Data.ServerInfo_GameSchema, { gameId: 2, description: '' })], + roomList: [], + }); const state = makeServerState({ gamesOfUser: { alice: old } }); - const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', games: fresh, gametypeMap: {} }); - expect(result.gamesOfUser['alice']).toEqual(fresh); + const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', response }); + expect(result.gamesOfUser['alice']).toEqual([makeGame({ gameId: 2 })]); }); it('does not affect other users\' entries', () => { const bobGames = [makeGame({ gameId: 3 })]; + const response = create(Data.Response_GetGamesOfUserSchema, { gameList: [], roomList: [] }); const state = makeServerState({ gamesOfUser: { bob: bobGames } }); - const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', games: [], gametypeMap: {} }); + const result = serverReducer(state, { type: Types.GAMES_OF_USER, userName: 'alice', response }); expect(result.gamesOfUser['bob']).toBe(bobGames); }); }); diff --git a/webclient/src/store/server/server.reducer.ts b/webclient/src/store/server/server.reducer.ts index 44276bce6..0d2915944 100644 --- a/webclient/src/store/server/server.reducer.ts +++ b/webclient/src/store/server/server.reducer.ts @@ -1,11 +1,7 @@ -import { SortDirection, StatusEnum, UserSortField } from 'types'; -import { ServerInfo_User_UserLevelFlag } from 'generated/proto/serverinfo_user_pb'; -import type { ServerInfo_DeckStorage_Folder, ServerInfo_DeckStorage_TreeItem } from 'generated/proto/serverinfo_deckstorage_pb'; +import { App, Data } from '@app/types'; import { create } from '@bufbuild/protobuf'; -import { Response_DeckListSchema } from 'generated/proto/response_deck_list_pb'; -import { ServerInfo_DeckStorage_FolderSchema, ServerInfo_DeckStorage_TreeItemSchema } from 'generated/proto/serverinfo_deckstorage_pb'; -import { normalizeBannedUserError, normalizeGameObject, normalizeLogs, SortUtil } from '../common'; +import { normalizeBannedUserError, normalizeGameObject, normalizeGametypeMap, normalizeLogs, SortUtil } from '../common'; import { ServerAction } from './server.actions'; import { ServerState } from './server.interfaces' @@ -16,17 +12,17 @@ function splitPath(path: string): string[] { } function insertAtPath( - folder: ServerInfo_DeckStorage_Folder, + folder: Data.ServerInfo_DeckStorage_Folder, pathSegments: string[], - item: ServerInfo_DeckStorage_TreeItem, -): ServerInfo_DeckStorage_Folder { + item: Data.ServerInfo_DeckStorage_TreeItem, +): Data.ServerInfo_DeckStorage_Folder { if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === '')) { - return create(ServerInfo_DeckStorage_FolderSchema, { items: [...folder.items, item] }); + return create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [...folder.items, item] }); } const [head, ...tail] = pathSegments; const match = folder.items.find(child => child.name === head && child.folder); if (match) { - return create(ServerInfo_DeckStorage_FolderSchema, { + return create(Data.ServerInfo_DeckStorage_FolderSchema, { items: folder.items.map(child => child === match ? { ...child, folder: insertAtPath(child.folder!, tail, item) } @@ -34,14 +30,14 @@ function insertAtPath( ), }); } - const created: ServerInfo_DeckStorage_TreeItem = create(ServerInfo_DeckStorage_TreeItemSchema, { - id: 0, name: head, folder: insertAtPath(create(ServerInfo_DeckStorage_FolderSchema, { items: [] }), tail, item) + const created: Data.ServerInfo_DeckStorage_TreeItem = create(Data.ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: head, folder: insertAtPath(create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [] }), tail, item) }); - return create(ServerInfo_DeckStorage_FolderSchema, { items: [...folder.items, created] }); + return create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [...folder.items, created] }); } -function removeById(folder: ServerInfo_DeckStorage_Folder, id: number): ServerInfo_DeckStorage_Folder { - return create(ServerInfo_DeckStorage_FolderSchema, { +function removeById(folder: Data.ServerInfo_DeckStorage_Folder, id: number): Data.ServerInfo_DeckStorage_Folder { + return create(Data.ServerInfo_DeckStorage_FolderSchema, { items: folder.items .filter(item => item.id !== id) .map(item => @@ -50,17 +46,17 @@ function removeById(folder: ServerInfo_DeckStorage_Folder, id: number): ServerIn }); } -function removeByPath(folder: ServerInfo_DeckStorage_Folder, pathSegments: string[]): ServerInfo_DeckStorage_Folder { +function removeByPath(folder: Data.ServerInfo_DeckStorage_Folder, pathSegments: string[]): Data.ServerInfo_DeckStorage_Folder { if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === '')) { return folder; } const [head, ...tail] = pathSegments; if (tail.length === 0) { - return create(ServerInfo_DeckStorage_FolderSchema, { + return create(Data.ServerInfo_DeckStorage_FolderSchema, { items: folder.items.filter(item => !(item.name === head && item.folder != null)) }); } - return create(ServerInfo_DeckStorage_FolderSchema, { + return create(Data.ServerInfo_DeckStorage_FolderSchema, { items: folder.items.map(item => item.name === head && item.folder ? { ...item, folder: removeByPath(item.folder, tail) } @@ -76,7 +72,7 @@ const initialState: ServerState = { status: { connectionAttemptMade: false, - state: StatusEnum.DISCONNECTED, + state: App.StatusEnum.DISCONNECTED, description: null }, info: { @@ -92,8 +88,8 @@ const initialState: ServerState = { user: null, users: [], sortUsersBy: { - field: UserSortField.NAME, - order: SortDirection.ASC + field: App.UserSortField.NAME, + order: App.SortDirection.ASC }, messages: {}, userInfo: {}, @@ -232,11 +228,19 @@ export const serverReducer = (state = initialState, action: ServerAction) => { } case Types.UPDATE_STATUS: { const { status } = action; - - return { + const newState = { ...state, - status: { ...status } + status: { ...state.status, ...status } + }; + + if (status.state === App.StatusEnum.DISCONNECTED) { + return { + ...newState, + status: { ...newState.status, connectionAttemptMade: false } + }; } + + return newState; } case Types.UPDATE_USER: case Types.ACCOUNT_EDIT_CHANGED: @@ -417,11 +421,11 @@ export const serverReducer = (state = initialState, action: ServerAction) => { } let newLevel = user.userLevel; newLevel = shouldBeMod - ? (newLevel | ServerInfo_User_UserLevelFlag.IsModerator) - : (newLevel & ~ServerInfo_User_UserLevelFlag.IsModerator); + ? (newLevel | Data.ServerInfo_User_UserLevelFlag.IsModerator) + : (newLevel & ~Data.ServerInfo_User_UserLevelFlag.IsModerator); newLevel = shouldBeJudge - ? (newLevel | ServerInfo_User_UserLevelFlag.IsJudge) - : (newLevel & ~ServerInfo_User_UserLevelFlag.IsJudge); + ? (newLevel | Data.ServerInfo_User_UserLevelFlag.IsJudge) + : (newLevel & ~Data.ServerInfo_User_UserLevelFlag.IsJudge); return { ...user, userLevel: newLevel, @@ -455,7 +459,7 @@ export const serverReducer = (state = initialState, action: ServerAction) => { } return { ...state, - backendDecks: create(Response_DeckListSchema, { + backendDecks: create(Data.Response_DeckListSchema, { root: insertAtPath(state.backendDecks.root, splitPath(action.path), action.treeItem), }), }; @@ -466,7 +470,7 @@ export const serverReducer = (state = initialState, action: ServerAction) => { } return { ...state, - backendDecks: create(Response_DeckListSchema, { + backendDecks: create(Data.Response_DeckListSchema, { root: removeById(state.backendDecks.root, action.deckId), }), }; @@ -475,12 +479,12 @@ export const serverReducer = (state = initialState, action: ServerAction) => { if (!state.backendDecks?.root) { return state; } - const newFolder: ServerInfo_DeckStorage_TreeItem = create(ServerInfo_DeckStorage_TreeItemSchema, { - id: 0, name: action.dirName, folder: create(ServerInfo_DeckStorage_FolderSchema, { items: [] }) + const newFolder: Data.ServerInfo_DeckStorage_TreeItem = create(Data.ServerInfo_DeckStorage_TreeItemSchema, { + id: 0, name: action.dirName, folder: create(Data.ServerInfo_DeckStorage_FolderSchema, { items: [] }) }); return { ...state, - backendDecks: create(Response_DeckListSchema, { + backendDecks: create(Data.Response_DeckListSchema, { root: insertAtPath(state.backendDecks.root, splitPath(action.path), newFolder), }), }; @@ -491,14 +495,17 @@ export const serverReducer = (state = initialState, action: ServerAction) => { } return { ...state, - backendDecks: create(Response_DeckListSchema, { + backendDecks: create(Data.Response_DeckListSchema, { root: removeByPath(state.backendDecks.root, splitPath(action.path)), }), }; } case Types.GAMES_OF_USER: { - const { userName, games, gametypeMap } = action; - const normalizedGames = games.map(g => normalizeGameObject(g, gametypeMap)); + const { userName, response } = action; + const gametypeMap = normalizeGametypeMap( + (response.roomList ?? []).flatMap(room => room.gametypeList ?? []) + ); + const normalizedGames = (response.gameList ?? []).map(g => normalizeGameObject(g, gametypeMap)); return { ...state, gamesOfUser: { @@ -518,7 +525,6 @@ export const serverReducer = (state = initialState, action: ServerAction) => { // Signal-only action types — no state mutation, explicit for discriminated-union exhaustiveness case Types.LOGIN_SUCCESSFUL: case Types.LOGIN_FAILED: - case Types.CONNECTION_CLOSED: case Types.CONNECTION_FAILED: case Types.TEST_CONNECTION_SUCCESSFUL: case Types.TEST_CONNECTION_FAILED: diff --git a/webclient/src/store/server/server.selectors.spec.ts b/webclient/src/store/server/server.selectors.spec.ts index f8658524f..646a8812b 100644 --- a/webclient/src/store/server/server.selectors.spec.ts +++ b/webclient/src/store/server/server.selectors.spec.ts @@ -6,7 +6,7 @@ import { makeServerState, makeUser, } from './__mocks__/server-fixtures'; -import { StatusEnum } from 'types'; +import { App } from '@app/types'; function rootState(server: ServerState) { return { server }; @@ -34,17 +34,17 @@ describe('Selectors', () => { }); it('getDescription → returns status.description', () => { - const state = makeServerState({ status: { connectionAttemptMade: false, state: StatusEnum.CONNECTED, description: 'ok' } }); + const state = makeServerState({ status: { connectionAttemptMade: false, state: App.StatusEnum.CONNECTED, description: 'ok' } }); expect(Selectors.getDescription(rootState(state))).toBe('ok'); }); it('getState → returns status.state', () => { - const state = makeServerState({ status: { connectionAttemptMade: false, state: StatusEnum.LOGGED_IN, description: null } }); - expect(Selectors.getState(rootState(state))).toBe(StatusEnum.LOGGED_IN); + const state = makeServerState({ status: { connectionAttemptMade: false, state: App.StatusEnum.LOGGED_IN, description: null } }); + expect(Selectors.getState(rootState(state))).toBe(App.StatusEnum.LOGGED_IN); }); it('getConnectionAttemptMade → returns status.connectionAttemptMade', () => { - const state = makeServerState({ status: { connectionAttemptMade: true, state: StatusEnum.DISCONNECTED, description: null } }); + const state = makeServerState({ status: { connectionAttemptMade: true, state: App.StatusEnum.DISCONNECTED, description: null } }); expect(Selectors.getConnectionAttemptMade(rootState(state))).toBe(true); }); diff --git a/webclient/src/store/server/server.types.ts b/webclient/src/store/server/server.types.ts index 89d3e7666..8a8906e4c 100644 --- a/webclient/src/store/server/server.types.ts +++ b/webclient/src/store/server/server.types.ts @@ -4,7 +4,6 @@ export const Types = { CONNECTION_ATTEMPTED: '[Server] Connection Attempted', LOGIN_SUCCESSFUL: '[Server] Login Successful', LOGIN_FAILED: '[Server] Login Failed', - CONNECTION_CLOSED: '[Server] Connection Closed', CONNECTION_FAILED: '[Server] Connection Failed', TEST_CONNECTION_SUCCESSFUL: '[Server] Test Connection Successful', TEST_CONNECTION_FAILED: '[Server] Test Connection Failed', diff --git a/webclient/src/types/app.ts b/webclient/src/types/app.ts new file mode 100644 index 000000000..2ec7c2a68 --- /dev/null +++ b/webclient/src/types/app.ts @@ -0,0 +1,8 @@ +export * from './cards'; +export * from './constants'; +export * from './countries'; +export * from './languages'; +export * from './routes'; +export * from './server'; +export * from './settings'; +export * from './sort'; diff --git a/webclient/src/types/data.ts b/webclient/src/types/data.ts new file mode 100644 index 000000000..4c0ffa673 --- /dev/null +++ b/webclient/src/types/data.ts @@ -0,0 +1 @@ +export * from '@app/generated'; diff --git a/webclient/src/types/enriched.ts b/webclient/src/types/enriched.ts new file mode 100644 index 000000000..b67aabae7 --- /dev/null +++ b/webclient/src/types/enriched.ts @@ -0,0 +1,145 @@ +import type { + Event_RoomSay, + GameEventContext, + ServerInfo_ChatMessage, + ServerInfo_Game, + ServerInfo_Room, +} from '@app/generated'; + +import { WebSocketConnectReason } from './server'; + +// ── Domain model types (proto types extended with client-side fields) ───────── + +export type Game = ServerInfo_Game & { + gameType: string; +}; + +export interface GametypeMap { [index: number]: string } + +export type Room = ServerInfo_Room & { + gametypeMap: GametypeMap; + gameList: Game[]; + order: number; +}; + +export type Message = Event_RoomSay & { + timeReceived: number; +}; + +/** + * Passed to every game event handler alongside the event payload. + * Contains per-container metadata from GameEventContainer. + * Not stored in Redux — transient routing metadata only. + */ +export interface GameEventMeta { + gameId: number; + playerId: number; + /** Raw protobuf GameEventContext object. Not stored in Redux. */ + context: GameEventContext | null; + secondsElapsed: number; + /** Proto type is uint32. Non-zero means the action was forced by a judge. */ + forcedByJudge: number; +} + +export interface LogGroups { + room: ServerInfo_ChatMessage[]; + game: ServerInfo_ChatMessage[]; + chat: ServerInfo_ChatMessage[]; +} + +// ── Connect options ─────────────────────────────────────────────────────────── +// Each variant is the enriched input for one session flow: the network +// transport fields (host/port) + the subset of proto Command_* fields the UI +// actually produces (user-entered credentials, tokens, email, etc.) + a +// `reason` discriminator so the websocket layer can route. +// +// Hand-written instead of `MessageInitShape & ...` +// because MessageInitShape is a `Message | { initShape }` union which +// collapses to the Message branding when intersected, requiring `$typeName` +// on literals. Keep these in sync with the corresponding proto command by +// convention; fields here map 1:1 to Command_* members. + +interface ConnectTransport { + host: string; + port: string; + keepalive?: number; + autojoinrooms?: boolean; + clientid?: string; +} + +export interface LoginConnectOptions extends ConnectTransport { + reason: WebSocketConnectReason.LOGIN; + userName: string; + password?: string; + hashedPassword?: string; +} + +export interface RegisterConnectOptions extends ConnectTransport { + reason: WebSocketConnectReason.REGISTER; + userName: string; + password: string; + email: string; + country: string; + realName: string; +} + +export interface ActivateConnectOptions extends ConnectTransport { + reason: WebSocketConnectReason.ACTIVATE_ACCOUNT; + userName: string; + token: string; + /** Plaintext password carried through so post-activation auto-login can hash it. */ + password?: string; +} + +export interface PasswordResetRequestConnectOptions extends ConnectTransport { + reason: WebSocketConnectReason.PASSWORD_RESET_REQUEST; + userName: string; +} + +export interface PasswordResetChallengeConnectOptions extends ConnectTransport { + reason: WebSocketConnectReason.PASSWORD_RESET_CHALLENGE; + userName: string; + email: string; +} + +export interface PasswordResetConnectOptions extends ConnectTransport { + reason: WebSocketConnectReason.PASSWORD_RESET; + userName: string; + token: string; + newPassword: string; +} + +/** + * Test connection has no proto command — it just opens and closes a socket to + * verify reachability. + */ +export interface TestConnectionOptions extends ConnectTransport { + reason: WebSocketConnectReason.TEST_CONNECTION; +} + +export type WebSocketConnectOptions = + | LoginConnectOptions + | RegisterConnectOptions + | ActivateConnectOptions + | PasswordResetRequestConnectOptions + | PasswordResetChallengeConnectOptions + | PasswordResetConnectOptions + | TestConnectionOptions; + +/** + * Context preserved through the ACCOUNT_AWAITING_ACTIVATION signal so the + * activation dialog can resubmit against the same host/user without re-entering them. + */ +export interface PendingActivationContext { + host: string; + port: string; + userName: string; +} + +/** + * Payload for the LOGIN_SUCCESSFUL signal. Only carries what the UI needs to + * persist into the selected host record (hashedPassword for "remember me"). + */ +export interface LoginSuccessContext { + hashedPassword?: string; +} diff --git a/webclient/src/types/game.ts b/webclient/src/types/game.ts deleted file mode 100644 index 8cffb3f85..000000000 --- a/webclient/src/types/game.ts +++ /dev/null @@ -1,123 +0,0 @@ -// ── Imports from generated proto files ─────────────────────────────────────── - -import type { ProtoInit } from './utilities'; -import type { GameEventContext } from 'generated/proto/game_event_context_pb'; -import type { Command_MoveCard } from 'generated/proto/command_move_card_pb'; -import type { Command_DrawCards } from 'generated/proto/command_draw_cards_pb'; -import type { Command_RollDie } from 'generated/proto/command_roll_die_pb'; -import type { Command_Shuffle } from 'generated/proto/command_shuffle_pb'; -import type { Command_FlipCard } from 'generated/proto/command_flip_card_pb'; -import type { Command_AttachCard } from 'generated/proto/command_attach_card_pb'; -import type { Command_CreateToken } from 'generated/proto/command_create_token_pb'; -import type { Command_SetCardAttr } from 'generated/proto/command_set_card_attr_pb'; -import type { Command_SetCardCounter } from 'generated/proto/command_set_card_counter_pb'; -import type { Command_IncCardCounter } from 'generated/proto/command_inc_card_counter_pb'; -import type { Command_RevealCards } from 'generated/proto/command_reveal_cards_pb'; -import type { Command_DumpZone } from 'generated/proto/command_dump_zone_pb'; -import type { Command_ChangeZoneProperties } from 'generated/proto/command_change_zone_properties_pb'; -import type { Command_CreateArrow } from 'generated/proto/command_create_arrow_pb'; -import type { Command_DeleteArrow } from 'generated/proto/command_delete_arrow_pb'; -import type { Command_CreateCounter } from 'generated/proto/command_create_counter_pb'; -import type { Command_SetCounter } from 'generated/proto/command_set_counter_pb'; -import type { Command_IncCounter } from 'generated/proto/command_inc_counter_pb'; -import type { Command_DelCounter } from 'generated/proto/command_del_counter_pb'; -import type { Command_KickFromGame } from 'generated/proto/command_kick_from_game_pb'; -import type { Command_ReadyStart } from 'generated/proto/command_ready_start_pb'; -import type { Command_Mulligan } from 'generated/proto/command_mulligan_pb'; -import type { Command_DeckSelect } from 'generated/proto/command_deck_select_pb'; -import type { Command_SetSideboardPlan } from 'generated/proto/command_set_sideboard_plan_pb'; -import type { Command_SetSideboardLock } from 'generated/proto/command_set_sideboard_lock_pb'; -import type { Command_SetActivePhase } from 'generated/proto/command_set_active_phase_pb'; -import type { Command_GameSay } from 'generated/proto/command_game_say_pb'; -import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; - -// ── UI types (not proto mirrors) ────────────────────────────────────────────── - -export type Game = ServerInfo_Game & { - gameType: string; -}; - -export enum GameSortField { - START_TIME = 'startTime' -} - -export interface GameConfig { - description: string; - password: string; - maxPlayers: number; - onlyBuddies: boolean; - onlyRegistered: boolean; - spectatorsAllowed: boolean; - spectatorsNeedPassword: boolean; - spectatorsCanTalk: boolean; - spectatorsSeeEverything: boolean; - gameTypeIds: number[]; - joinAsJudge: boolean; - joinAsSpectator: boolean; - startingLifeTotal?: number; - shareDecklistsOnLoad?: boolean; -} - -export interface JoinGameParams { - gameId: number; - password: string; - spectator: boolean; - overrideRestrictions: boolean; - joinAsJudge: boolean; -} - -export enum LeaveGameReason { - OTHER = 1, - USER_KICKED = 2, - USER_LEFT = 3, - USER_DISCONNECTED = 4 -} - -// ── GameEventContext (imported for use in GameEventMeta below) ─────────────── - -/** - * Passed to every game event handler alongside the event payload. - * Contains per-container metadata from GameEventContainer. - * Not stored in Redux — transient routing metadata only. - */ -export interface GameEventMeta { - gameId: number; - playerId: number; - /** Raw protobuf GameEventContext object. Not stored in Redux. */ - context: GameEventContext | null; - secondsElapsed: number; - /** Proto type is uint32. Non-zero means the action was forced by a judge. */ - forcedByJudge: number; -} - -// ── Type aliases for generated command param types (init shapes) ────────────── -// These use ProtoInit<> because callers construct plain objects; -// the command functions internally call create(Schema, params). - -export type MoveCardParams = ProtoInit; -export type DrawCardsParams = ProtoInit; -export type RollDieParams = ProtoInit; -export type ShuffleParams = ProtoInit; -export type FlipCardParams = ProtoInit; -export type AttachCardParams = ProtoInit; -export type CreateTokenParams = ProtoInit; -export type SetCardAttrParams = ProtoInit; -export type SetCardCounterParams = ProtoInit; -export type IncCardCounterParams = ProtoInit; -export type RevealCardsParams = ProtoInit; -export type DumpZoneParams = ProtoInit; -export type ChangeZonePropertiesParams = ProtoInit; -export type CreateArrowParams = ProtoInit; -export type DeleteArrowParams = ProtoInit; -export type CreateCounterParams = ProtoInit; -export type SetCounterParams = ProtoInit; -export type IncCounterParams = ProtoInit; -export type DelCounterParams = ProtoInit; -export type KickFromGameParams = ProtoInit; -export type ReadyStartParams = ProtoInit; -export type MulliganParams = ProtoInit; -export type DeckSelectParams = ProtoInit; -export type SetSideboardPlanParams = ProtoInit; -export type SetSideboardLockParams = ProtoInit; -export type SetActivePhaseParams = ProtoInit; -export type GameSayParams = ProtoInit; diff --git a/webclient/src/types/index.ts b/webclient/src/types/index.ts index b542d7937..732981b87 100644 --- a/webclient/src/types/index.ts +++ b/webclient/src/types/index.ts @@ -1,14 +1,3 @@ -export * from './cards'; -export * from './constants'; -export * from './countries'; -export * from './game'; -export * from './room'; -export * from './server'; -export * from './sort'; -export * from './user'; -export * from './routes'; -export * from './message'; -export * from './settings'; -export * from './languages'; -export * from './logs'; -export * from './utilities'; +export * as Data from './data'; +export * as Enriched from './enriched'; +export * as App from './app'; diff --git a/webclient/src/types/logs.ts b/webclient/src/types/logs.ts deleted file mode 100644 index 3cf34b486..000000000 --- a/webclient/src/types/logs.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface LogFilters { - userName?: string; - ipAddress?: string; - gameName?: string; - gameId?: string; - message?: string; - logLocation?: string[]; - dateRange: number; - maximumResults?: number; -} diff --git a/webclient/src/types/message.ts b/webclient/src/types/message.ts deleted file mode 100644 index d4dc282a1..000000000 --- a/webclient/src/types/message.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { Event_RoomSay } from 'generated/proto/event_room_say_pb'; - -export type Message = Event_RoomSay & { - timeReceived: number; -}; diff --git a/webclient/src/types/room.ts b/webclient/src/types/room.ts deleted file mode 100644 index f4441f697..000000000 --- a/webclient/src/types/room.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; -import type { Game } from './game'; - -export interface GametypeMap { [index: number]: string } - -export type Room = ServerInfo_Room & { - gametypeMap: GametypeMap; - gameList: Game[]; - order: number; -}; diff --git a/webclient/src/types/server.ts b/webclient/src/types/server.ts index 8c42b1165..6c3fa8006 100644 --- a/webclient/src/types/server.ts +++ b/webclient/src/types/server.ts @@ -1,5 +1,3 @@ -import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb'; - export interface ServerStatus { status: StatusEnum; description: string; @@ -14,23 +12,6 @@ export enum StatusEnum { DISCONNECTING = 99 } -export interface WebSocketConnectOptions { - host?: string; - port?: string; - userName?: string; - password?: string; - hashedPassword?: string; - newPassword?: string; - token?: string; - email?: string; - realName?: string; - country?: string; - autojoinrooms?: boolean; - keepalive?: number; - clientid?: string; - reason?: WebSocketConnectReason; -} - export enum WebSocketConnectReason { LOGIN, REGISTER, @@ -110,9 +91,3 @@ export const KnownHosts = { [KnownHost.ROOSTER]: { port: 4748, host: 'server.cockatrice.us', }, [KnownHost.TETRARCH]: { port: 443, host: 'mtg.tetrarch.co/servatrice' }, } - -export interface LogGroups { - room: ServerInfo_ChatMessage[]; - game: ServerInfo_ChatMessage[]; - chat: ServerInfo_ChatMessage[]; -} diff --git a/webclient/src/types/sort.ts b/webclient/src/types/sort.ts index c3312732c..4619c169b 100644 --- a/webclient/src/types/sort.ts +++ b/webclient/src/types/sort.ts @@ -3,7 +3,15 @@ export enum SortDirection { DESC = 'DESC' } -export interface SortBy { - field: string; +export interface SortBy { + field: T; order: SortDirection; } + +export enum GameSortField { + START_TIME = 'startTime' +} + +export enum UserSortField { + NAME = 'name' +} diff --git a/webclient/src/types/user.ts b/webclient/src/types/user.ts deleted file mode 100644 index 3034b977c..000000000 --- a/webclient/src/types/user.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum UserSortField { - NAME = 'name' -} diff --git a/webclient/src/types/utilities.ts b/webclient/src/types/utilities.ts deleted file mode 100644 index 9a113c264..000000000 --- a/webclient/src/types/utilities.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Init shape for constructing protobuf messages via create(). - * Strips $typeName and $unknown branding, making all fields optional. - * Use for function parameters that feed into create(). - */ -export type ProtoInit = { - [K in keyof T as K extends '$typeName' | '$unknown' ? never : K]?: T[K]; -}; diff --git a/webclient/src/websocket/WebClient.spec.ts b/webclient/src/websocket/WebClient.spec.ts index 799c7e552..9cb5c4671 100644 --- a/webclient/src/websocket/WebClient.spec.ts +++ b/webclient/src/websocket/WebClient.spec.ts @@ -1,10 +1,10 @@ const captured = vi.hoisted(() => ({ - wsOptions: null as any, - pbOptions: null as any, + wsOptions: null as WebSocketServiceConfig | null, + pbOptions: null as SocketTransport | null, })); vi.mock('./services/WebSocketService', () => ({ - WebSocketService: vi.fn().mockImplementation(function WebSocketServiceImpl(options: any) { + WebSocketService: vi.fn().mockImplementation(function WebSocketServiceImpl(options: WebSocketServiceConfig) { captured.wsOptions = options; return { message$: { subscribe: vi.fn() }, @@ -18,7 +18,7 @@ vi.mock('./services/WebSocketService', () => ({ })); vi.mock('./services/ProtobufService', () => ({ - ProtobufService: vi.fn().mockImplementation(function ProtobufServiceImpl(options: any) { + ProtobufService: vi.fn().mockImplementation(function ProtobufServiceImpl(options: SocketTransport) { captured.pbOptions = options; return { handleMessageEvent: vi.fn(), @@ -32,7 +32,7 @@ vi.mock('./persistence', () => ({ SessionPersistence: { clearStore: vi.fn(), initialized: vi.fn(), connectionAttempted: vi.fn() }, })); -vi.mock('store', () => ({ +vi.mock('@app/store', () => ({ GameDispatch: { clearStore: vi.fn() }, })); @@ -45,17 +45,18 @@ import { WebSocketService } from './services/WebSocketService'; import { ProtobufService } from './services/ProtobufService'; import { RoomPersistence, SessionPersistence } from './persistence'; import { ping } from './commands/session'; -import { StatusEnum } from 'types'; +import { App, Enriched } from '@app/types'; import { Subject } from 'rxjs'; import { Mock } from 'vitest'; +import { SocketTransport } from './services/ProtobufService'; +import { WebSocketServiceConfig } from './services/WebSocketService'; describe('WebClient', () => { let client: WebClient; let messageSubject: Subject; beforeEach(() => { - vi.clearAllMocks(); - (ProtobufService as Mock).mockImplementation(function ProtobufServiceImpl(options: any) { + (ProtobufService as Mock).mockImplementation(function ProtobufServiceImpl(options: SocketTransport) { captured.pbOptions = options; return { handleMessageEvent: vi.fn(), @@ -63,7 +64,7 @@ describe('WebClient', () => { }; }); messageSubject = new Subject(); - (WebSocketService as Mock).mockImplementation(function WebSocketServiceImpl(options: any) { + (WebSocketService as Mock).mockImplementation(function WebSocketServiceImpl(options: WebSocketServiceConfig) { captured.wsOptions = options; return { message$: messageSubject, @@ -97,13 +98,13 @@ describe('WebClient', () => { describe('connect', () => { it('calls SessionPersistence.connectionAttempted', () => { - const opts: any = { host: 'h', port: 1 }; + const opts: Enriched.WebSocketConnectOptions = { host: 'h', port: '1', reason: App.WebSocketConnectReason.LOGIN, userName: 'u' }; client.connect(opts); expect(SessionPersistence.connectionAttempted).toHaveBeenCalled(); }); it('stores options and calls socket.connect', () => { - const opts: any = { host: 'h', port: 1 }; + const opts: Enriched.WebSocketConnectOptions = { host: 'h', port: '1', reason: App.WebSocketConnectReason.LOGIN, userName: 'u' }; client.connect(opts); expect(client.options).toBe(opts); expect(client.socket.connect).toHaveBeenCalledWith(opts); @@ -112,7 +113,7 @@ describe('WebClient', () => { describe('testConnect', () => { it('delegates to socket.testConnect', () => { - const opts: any = { host: 'h', port: 1 }; + const opts: Enriched.WebSocketConnectOptions = { host: 'h', port: '1', reason: App.WebSocketConnectReason.LOGIN, userName: 'u' }; client.testConnect(opts); expect(client.socket.testConnect).toHaveBeenCalledWith(opts); }); @@ -127,19 +128,19 @@ describe('WebClient', () => { describe('updateStatus', () => { it('sets the status', () => { - client.updateStatus(StatusEnum.CONNECTED); - expect(client.status).toBe(StatusEnum.CONNECTED); + client.updateStatus(App.StatusEnum.CONNECTED); + expect(client.status).toBe(App.StatusEnum.CONNECTED); }); it('calls protobuf.resetCommands and clears stores on DISCONNECTED', () => { - client.updateStatus(StatusEnum.DISCONNECTED); + client.updateStatus(App.StatusEnum.DISCONNECTED); expect(client.protobuf.resetCommands).toHaveBeenCalled(); expect(RoomPersistence.clearStore).toHaveBeenCalled(); expect(SessionPersistence.clearStore).toHaveBeenCalled(); }); it('does not clear stores when status is not DISCONNECTED', () => { - client.updateStatus(StatusEnum.CONNECTED); + client.updateStatus(App.StatusEnum.CONNECTED); expect(client.protobuf.resetCommands).not.toHaveBeenCalled(); expect(RoomPersistence.clearStore).not.toHaveBeenCalled(); }); diff --git a/webclient/src/websocket/WebClient.ts b/webclient/src/websocket/WebClient.ts index 5895c653a..68df7749e 100644 --- a/webclient/src/websocket/WebClient.ts +++ b/webclient/src/websocket/WebClient.ts @@ -1,18 +1,18 @@ -import { StatusEnum, WebSocketConnectOptions } from 'types'; +import { App, Enriched } from '@app/types'; import { ProtobufService } from './services/ProtobufService'; import { WebSocketService } from './services/WebSocketService'; import { ping } from './commands/session'; -import { GameDispatch } from 'store'; +import { GameDispatch } from '@app/store'; import { RoomPersistence, SessionPersistence } from './persistence'; export class WebClient { public socket: WebSocketService; public protobuf: ProtobufService; - public options: WebSocketConnectOptions; - public status: StatusEnum; + public options: Enriched.WebSocketConnectOptions | null = null; + public status: App.StatusEnum; constructor() { this.socket = new WebSocketService({ @@ -35,13 +35,13 @@ export class WebClient { } } - public connect(options: WebSocketConnectOptions) { + public connect(options: Enriched.WebSocketConnectOptions) { SessionPersistence.connectionAttempted(); this.options = options; this.socket.connect(options); } - public testConnect(options: WebSocketConnectOptions) { + public testConnect(options: Enriched.WebSocketConnectOptions) { this.socket.testConnect(options); } @@ -49,10 +49,10 @@ export class WebClient { this.socket.disconnect(); } - public updateStatus(status: StatusEnum) { + public updateStatus(status: App.StatusEnum) { this.status = status; - if (status === StatusEnum.DISCONNECTED) { + if (status === App.StatusEnum.DISCONNECTED) { this.protobuf.resetCommands(); this.clearStores(); } diff --git a/webclient/src/websocket/commands/admin/adjustMod.ts b/webclient/src/websocket/commands/admin/adjustMod.ts index f6fba41e8..3f034c8d3 100644 --- a/webclient/src/websocket/commands/admin/adjustMod.ts +++ b/webclient/src/websocket/commands/admin/adjustMod.ts @@ -1,12 +1,16 @@ import { create } from '@bufbuild/protobuf'; +import { Data } from '@app/types'; import webClient from '../../WebClient'; -import { Command_AdjustMod_ext, Command_AdjustModSchema } from 'generated/proto/admin_commands_pb'; import { AdminPersistence } from '../../persistence'; export function adjustMod(userName: string, shouldBeMod?: boolean, shouldBeJudge?: boolean): void { - webClient.protobuf.sendAdminCommand(Command_AdjustMod_ext, create(Command_AdjustModSchema, { userName, shouldBeMod, shouldBeJudge }), { - onSuccess: () => { - AdminPersistence.adjustMod(userName, shouldBeMod, shouldBeJudge); - }, - }); + webClient.protobuf.sendAdminCommand( + Data.Command_AdjustMod_ext, + create(Data.Command_AdjustModSchema, { userName, shouldBeMod, shouldBeJudge }), + { + onSuccess: () => { + AdminPersistence.adjustMod(userName, shouldBeMod, shouldBeJudge); + }, + } + ); } diff --git a/webclient/src/websocket/commands/admin/adminCommands.spec.ts b/webclient/src/websocket/commands/admin/adminCommands.spec.ts index d3422d148..9129ee987 100644 --- a/webclient/src/websocket/commands/admin/adminCommands.spec.ts +++ b/webclient/src/websocket/commands/admin/adminCommands.spec.ts @@ -27,8 +27,6 @@ const { invokeOnSuccess } = makeCallbackHelpers( 2 ); -beforeEach(() => vi.clearAllMocks()); - // ---------------------------------------------------------------- // adjustMod // ---------------------------------------------------------------- diff --git a/webclient/src/websocket/commands/admin/reloadConfig.ts b/webclient/src/websocket/commands/admin/reloadConfig.ts index 08c92ffee..ee318eb28 100644 --- a/webclient/src/websocket/commands/admin/reloadConfig.ts +++ b/webclient/src/websocket/commands/admin/reloadConfig.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; +import { Data } from '@app/types'; import webClient from '../../WebClient'; -import { Command_ReloadConfig_ext, Command_ReloadConfigSchema } from 'generated/proto/admin_commands_pb'; import { AdminPersistence } from '../../persistence'; export function reloadConfig(): void { - webClient.protobuf.sendAdminCommand(Command_ReloadConfig_ext, create(Command_ReloadConfigSchema), { + webClient.protobuf.sendAdminCommand(Data.Command_ReloadConfig_ext, create(Data.Command_ReloadConfigSchema), { onSuccess: () => { AdminPersistence.reloadConfig(); }, diff --git a/webclient/src/websocket/commands/admin/shutdownServer.ts b/webclient/src/websocket/commands/admin/shutdownServer.ts index 86cd75259..0bbb5f626 100644 --- a/webclient/src/websocket/commands/admin/shutdownServer.ts +++ b/webclient/src/websocket/commands/admin/shutdownServer.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; +import { Data } from '@app/types'; import webClient from '../../WebClient'; -import { Command_ShutdownServer_ext, Command_ShutdownServerSchema } from 'generated/proto/admin_commands_pb'; import { AdminPersistence } from '../../persistence'; export function shutdownServer(reason: string, minutes: number): void { - webClient.protobuf.sendAdminCommand(Command_ShutdownServer_ext, create(Command_ShutdownServerSchema, { reason, minutes }), { + webClient.protobuf.sendAdminCommand(Data.Command_ShutdownServer_ext, create(Data.Command_ShutdownServerSchema, { reason, minutes }), { onSuccess: () => { AdminPersistence.shutdownServer(); }, diff --git a/webclient/src/websocket/commands/admin/updateServerMessage.ts b/webclient/src/websocket/commands/admin/updateServerMessage.ts index 4bd1c3f12..70e2e1eb6 100644 --- a/webclient/src/websocket/commands/admin/updateServerMessage.ts +++ b/webclient/src/websocket/commands/admin/updateServerMessage.ts @@ -1,10 +1,10 @@ import { create } from '@bufbuild/protobuf'; +import { Data } from '@app/types'; import webClient from '../../WebClient'; -import { Command_UpdateServerMessage_ext, Command_UpdateServerMessageSchema } from 'generated/proto/admin_commands_pb'; import { AdminPersistence } from '../../persistence'; export function updateServerMessage(): void { - webClient.protobuf.sendAdminCommand(Command_UpdateServerMessage_ext, create(Command_UpdateServerMessageSchema), { + webClient.protobuf.sendAdminCommand(Data.Command_UpdateServerMessage_ext, create(Data.Command_UpdateServerMessageSchema), { onSuccess: () => { AdminPersistence.updateServerMessage(); }, diff --git a/webclient/src/websocket/commands/game/attachCard.ts b/webclient/src/websocket/commands/game/attachCard.ts index 08c6b110f..4c414730c 100644 --- a/webclient/src/websocket/commands/game/attachCard.ts +++ b/webclient/src/websocket/commands/game/attachCard.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_AttachCardSchema, Command_AttachCard_ext } from 'generated/proto/command_attach_card_pb'; -import { AttachCardParams } from 'types'; -export function attachCard(gameId: number, params: AttachCardParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_AttachCard_ext, create(Command_AttachCardSchema, params)); +import { Data } from '@app/types'; + +export function attachCard(gameId: number, params: Data.AttachCardParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_AttachCard_ext, create(Data.Command_AttachCardSchema, params)); } diff --git a/webclient/src/websocket/commands/game/changeZoneProperties.ts b/webclient/src/websocket/commands/game/changeZoneProperties.ts index a35e9ec25..073a3458e 100644 --- a/webclient/src/websocket/commands/game/changeZoneProperties.ts +++ b/webclient/src/websocket/commands/game/changeZoneProperties.ts @@ -1,8 +1,12 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_ChangeZonePropertiesSchema, Command_ChangeZoneProperties_ext } from 'generated/proto/command_change_zone_properties_pb'; -import { ChangeZonePropertiesParams } from 'types'; -export function changeZoneProperties(gameId: number, params: ChangeZonePropertiesParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_ChangeZoneProperties_ext, create(Command_ChangeZonePropertiesSchema, params)); +import { Data } from '@app/types'; + +export function changeZoneProperties(gameId: number, params: Data.ChangeZonePropertiesParams): void { + webClient.protobuf.sendGameCommand( + gameId, + Data.Command_ChangeZoneProperties_ext, + create(Data.Command_ChangeZonePropertiesSchema, params) + ); } diff --git a/webclient/src/websocket/commands/game/concede.ts b/webclient/src/websocket/commands/game/concede.ts index 8aaad96b1..65f19a44f 100644 --- a/webclient/src/websocket/commands/game/concede.ts +++ b/webclient/src/websocket/commands/game/concede.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_ConcedeSchema, Command_Concede_ext } from 'generated/proto/command_concede_pb'; +import { Data } from '@app/types'; export function concede(gameId: number): void { - webClient.protobuf.sendGameCommand(gameId, Command_Concede_ext, create(Command_ConcedeSchema)); + webClient.protobuf.sendGameCommand(gameId, Data.Command_Concede_ext, create(Data.Command_ConcedeSchema)); } diff --git a/webclient/src/websocket/commands/game/createArrow.ts b/webclient/src/websocket/commands/game/createArrow.ts index d85b3168e..544f0c14a 100644 --- a/webclient/src/websocket/commands/game/createArrow.ts +++ b/webclient/src/websocket/commands/game/createArrow.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_CreateArrowSchema, Command_CreateArrow_ext } from 'generated/proto/command_create_arrow_pb'; -import { CreateArrowParams } from 'types'; -export function createArrow(gameId: number, params: CreateArrowParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_CreateArrow_ext, create(Command_CreateArrowSchema, params)); +import { Data } from '@app/types'; + +export function createArrow(gameId: number, params: Data.CreateArrowParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_CreateArrow_ext, create(Data.Command_CreateArrowSchema, params)); } diff --git a/webclient/src/websocket/commands/game/createCounter.ts b/webclient/src/websocket/commands/game/createCounter.ts index 53153efc3..24c7a9ccb 100644 --- a/webclient/src/websocket/commands/game/createCounter.ts +++ b/webclient/src/websocket/commands/game/createCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_CreateCounterSchema, Command_CreateCounter_ext } from 'generated/proto/command_create_counter_pb'; -import { CreateCounterParams } from 'types'; -export function createCounter(gameId: number, params: CreateCounterParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_CreateCounter_ext, create(Command_CreateCounterSchema, params)); +import { Data } from '@app/types'; + +export function createCounter(gameId: number, params: Data.CreateCounterParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_CreateCounter_ext, create(Data.Command_CreateCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/createToken.ts b/webclient/src/websocket/commands/game/createToken.ts index 06780afba..5c38f8ec6 100644 --- a/webclient/src/websocket/commands/game/createToken.ts +++ b/webclient/src/websocket/commands/game/createToken.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_CreateTokenSchema, Command_CreateToken_ext } from 'generated/proto/command_create_token_pb'; -import { CreateTokenParams } from 'types'; -export function createToken(gameId: number, params: CreateTokenParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_CreateToken_ext, create(Command_CreateTokenSchema, params)); +import { Data } from '@app/types'; + +export function createToken(gameId: number, params: Data.CreateTokenParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_CreateToken_ext, create(Data.Command_CreateTokenSchema, params)); } diff --git a/webclient/src/websocket/commands/game/deckSelect.ts b/webclient/src/websocket/commands/game/deckSelect.ts index 33905c7ac..4fd8e76c2 100644 --- a/webclient/src/websocket/commands/game/deckSelect.ts +++ b/webclient/src/websocket/commands/game/deckSelect.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_DeckSelectSchema, Command_DeckSelect_ext } from 'generated/proto/command_deck_select_pb'; -import { DeckSelectParams } from 'types'; -export function deckSelect(gameId: number, params: DeckSelectParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_DeckSelect_ext, create(Command_DeckSelectSchema, params)); +import { Data } from '@app/types'; + +export function deckSelect(gameId: number, params: Data.DeckSelectParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_DeckSelect_ext, create(Data.Command_DeckSelectSchema, params)); } diff --git a/webclient/src/websocket/commands/game/delCounter.ts b/webclient/src/websocket/commands/game/delCounter.ts index ba9e10d55..893b93056 100644 --- a/webclient/src/websocket/commands/game/delCounter.ts +++ b/webclient/src/websocket/commands/game/delCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_DelCounterSchema, Command_DelCounter_ext } from 'generated/proto/command_del_counter_pb'; -import { DelCounterParams } from 'types'; -export function delCounter(gameId: number, params: DelCounterParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_DelCounter_ext, create(Command_DelCounterSchema, params)); +import { Data } from '@app/types'; + +export function delCounter(gameId: number, params: Data.DelCounterParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_DelCounter_ext, create(Data.Command_DelCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/deleteArrow.ts b/webclient/src/websocket/commands/game/deleteArrow.ts index dc15a2d00..46b952e5a 100644 --- a/webclient/src/websocket/commands/game/deleteArrow.ts +++ b/webclient/src/websocket/commands/game/deleteArrow.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_DeleteArrowSchema, Command_DeleteArrow_ext } from 'generated/proto/command_delete_arrow_pb'; -import { DeleteArrowParams } from 'types'; -export function deleteArrow(gameId: number, params: DeleteArrowParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_DeleteArrow_ext, create(Command_DeleteArrowSchema, params)); +import { Data } from '@app/types'; + +export function deleteArrow(gameId: number, params: Data.DeleteArrowParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_DeleteArrow_ext, create(Data.Command_DeleteArrowSchema, params)); } diff --git a/webclient/src/websocket/commands/game/drawCards.ts b/webclient/src/websocket/commands/game/drawCards.ts index 1ba96ca8f..2ec92ee5f 100644 --- a/webclient/src/websocket/commands/game/drawCards.ts +++ b/webclient/src/websocket/commands/game/drawCards.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_DrawCardsSchema, Command_DrawCards_ext } from 'generated/proto/command_draw_cards_pb'; -import { DrawCardsParams } from 'types'; -export function drawCards(gameId: number, params: DrawCardsParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_DrawCards_ext, create(Command_DrawCardsSchema, params)); +import { Data } from '@app/types'; + +export function drawCards(gameId: number, params: Data.DrawCardsParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_DrawCards_ext, create(Data.Command_DrawCardsSchema, params)); } diff --git a/webclient/src/websocket/commands/game/dumpZone.ts b/webclient/src/websocket/commands/game/dumpZone.ts index 3cee63e7e..5de3ed76c 100644 --- a/webclient/src/websocket/commands/game/dumpZone.ts +++ b/webclient/src/websocket/commands/game/dumpZone.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_DumpZoneSchema, Command_DumpZone_ext } from 'generated/proto/command_dump_zone_pb'; -import { DumpZoneParams } from 'types'; -export function dumpZone(gameId: number, params: DumpZoneParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_DumpZone_ext, create(Command_DumpZoneSchema, params)); +import { Data } from '@app/types'; + +export function dumpZone(gameId: number, params: Data.DumpZoneParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_DumpZone_ext, create(Data.Command_DumpZoneSchema, params)); } diff --git a/webclient/src/websocket/commands/game/flipCard.ts b/webclient/src/websocket/commands/game/flipCard.ts index b8d1130cf..598874115 100644 --- a/webclient/src/websocket/commands/game/flipCard.ts +++ b/webclient/src/websocket/commands/game/flipCard.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_FlipCardSchema, Command_FlipCard_ext } from 'generated/proto/command_flip_card_pb'; -import { FlipCardParams } from 'types'; -export function flipCard(gameId: number, params: FlipCardParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_FlipCard_ext, create(Command_FlipCardSchema, params)); +import { Data } from '@app/types'; + +export function flipCard(gameId: number, params: Data.FlipCardParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_FlipCard_ext, create(Data.Command_FlipCardSchema, params)); } diff --git a/webclient/src/websocket/commands/game/gameCommands.spec.ts b/webclient/src/websocket/commands/game/gameCommands.spec.ts index ddb671f88..ea32bb658 100644 --- a/webclient/src/websocket/commands/game/gameCommands.spec.ts +++ b/webclient/src/websocket/commands/game/gameCommands.spec.ts @@ -1,37 +1,7 @@ import webClient from '../../WebClient'; import { create, setExtension } from '@bufbuild/protobuf'; -import { GameCommandSchema, Command_Judge_ext } from 'generated/proto/game_commands_pb'; -import { Command_DrawCardsSchema, Command_DrawCards_ext } from 'generated/proto/command_draw_cards_pb'; -import { Command_AttachCard_ext } from 'generated/proto/command_attach_card_pb'; -import { Command_ChangeZoneProperties_ext } from 'generated/proto/command_change_zone_properties_pb'; -import { Command_Concede_ext, Command_Unconcede_ext } from 'generated/proto/command_concede_pb'; -import { Command_CreateArrow_ext } from 'generated/proto/command_create_arrow_pb'; -import { Command_CreateCounter_ext } from 'generated/proto/command_create_counter_pb'; -import { Command_CreateToken_ext } from 'generated/proto/command_create_token_pb'; -import { Command_DeckSelect_ext } from 'generated/proto/command_deck_select_pb'; -import { Command_DelCounter_ext } from 'generated/proto/command_del_counter_pb'; -import { Command_DeleteArrow_ext } from 'generated/proto/command_delete_arrow_pb'; -import { Command_DumpZone_ext } from 'generated/proto/command_dump_zone_pb'; -import { Command_FlipCard_ext } from 'generated/proto/command_flip_card_pb'; -import { Command_GameSay_ext } from 'generated/proto/command_game_say_pb'; -import { Command_IncCardCounter_ext } from 'generated/proto/command_inc_card_counter_pb'; -import { Command_IncCounter_ext } from 'generated/proto/command_inc_counter_pb'; -import { Command_KickFromGame_ext } from 'generated/proto/command_kick_from_game_pb'; -import { Command_LeaveGame_ext } from 'generated/proto/command_leave_game_pb'; -import { Command_MoveCard_ext } from 'generated/proto/command_move_card_pb'; -import { Command_Mulligan_ext } from 'generated/proto/command_mulligan_pb'; -import { Command_NextTurn_ext } from 'generated/proto/command_next_turn_pb'; -import { Command_ReadyStart_ext } from 'generated/proto/command_ready_start_pb'; -import { Command_RevealCards_ext } from 'generated/proto/command_reveal_cards_pb'; -import { Command_ReverseTurn_ext } from 'generated/proto/command_reverse_turn_pb'; -import { Command_SetActivePhase_ext } from 'generated/proto/command_set_active_phase_pb'; -import { Command_SetCardAttr_ext } from 'generated/proto/command_set_card_attr_pb'; -import { Command_SetCardCounter_ext } from 'generated/proto/command_set_card_counter_pb'; -import { Command_SetCounter_ext } from 'generated/proto/command_set_counter_pb'; -import { Command_SetSideboardLock_ext } from 'generated/proto/command_set_sideboard_lock_pb'; -import { Command_SetSideboardPlan_ext } from 'generated/proto/command_set_sideboard_plan_pb'; -import { Command_Shuffle_ext } from 'generated/proto/command_shuffle_pb'; -import { Command_UndoDraw_ext } from 'generated/proto/command_undo_draw_pb'; +import { Data } from '@app/types'; + import { attachCard } from './attachCard'; import { changeZoneProperties } from './changeZoneProperties'; import { concede } from './concede'; @@ -73,128 +43,126 @@ vi.mock('../../WebClient', () => ({ const gameId = 1; -import { Mock } from 'vitest'; - -beforeEach(() => { - (webClient.protobuf.sendGameCommand as Mock).mockClear(); -}); - describe('Game commands — delegate to webClient.protobuf.sendGameCommand', () => { it('attachCard sends Command_AttachCard', () => { attachCard(gameId, { cardId: 10, startZone: 'hand' }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_AttachCard_ext, expect.objectContaining({ cardId: 10, startZone: 'hand' }) + gameId, Data.Command_AttachCard_ext, expect.objectContaining({ cardId: 10, startZone: 'hand' }) ); }); it('changeZoneProperties sends Command_ChangeZoneProperties', () => { changeZoneProperties(gameId, { zoneName: 'side' }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_ChangeZoneProperties_ext, expect.objectContaining({ zoneName: 'side' }) + gameId, Data.Command_ChangeZoneProperties_ext, expect.objectContaining({ zoneName: 'side' }) ); }); it('concede sends Command_Concede with empty object', () => { concede(gameId); - expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Concede_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Data.Command_Concede_ext, expect.any(Object)); }); it('createArrow sends Command_CreateArrow', () => { createArrow(gameId, { startPlayerId: 1, startZone: 'hand' }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_CreateArrow_ext, expect.objectContaining({ startPlayerId: 1, startZone: 'hand' }) + gameId, Data.Command_CreateArrow_ext, expect.objectContaining({ startPlayerId: 1, startZone: 'hand' }) ); }); it('createCounter sends Command_CreateCounter', () => { createCounter(gameId, { counterName: 'life' }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_CreateCounter_ext, expect.objectContaining({ counterName: 'life' }) + gameId, Data.Command_CreateCounter_ext, expect.objectContaining({ counterName: 'life' }) ); }); it('createToken sends Command_CreateToken', () => { createToken(gameId, { cardName: 'Goblin', zone: 'play' }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_CreateToken_ext, expect.objectContaining({ cardName: 'Goblin', zone: 'play' }) + gameId, Data.Command_CreateToken_ext, expect.objectContaining({ cardName: 'Goblin', zone: 'play' }) ); }); it('deckSelect sends Command_DeckSelect', () => { deckSelect(gameId, { deckId: 5 }); - expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DeckSelect_ext, expect.objectContaining({ deckId: 5 })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Data.Command_DeckSelect_ext, expect.objectContaining({ deckId: 5 }) + ); }); it('delCounter sends Command_DelCounter', () => { delCounter(gameId, { counterId: 3 }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_DelCounter_ext, expect.objectContaining({ counterId: 3 }) + gameId, Data.Command_DelCounter_ext, expect.objectContaining({ counterId: 3 }) ); }); it('deleteArrow sends Command_DeleteArrow', () => { deleteArrow(gameId, { arrowId: 2 }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_DeleteArrow_ext, expect.objectContaining({ arrowId: 2 }) + gameId, Data.Command_DeleteArrow_ext, expect.objectContaining({ arrowId: 2 }) ); }); it('drawCards sends Command_DrawCards', () => { drawCards(gameId, { number: 3 }); - expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_DrawCards_ext, expect.objectContaining({ number: 3 })); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( + gameId, Data.Command_DrawCards_ext, expect.objectContaining({ number: 3 }) + ); }); it('dumpZone sends Command_DumpZone', () => { dumpZone(gameId, { playerId: 2, zoneName: 'library' }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_DumpZone_ext, expect.objectContaining({ playerId: 2, zoneName: 'library' }) + gameId, Data.Command_DumpZone_ext, expect.objectContaining({ playerId: 2, zoneName: 'library' }) ); }); it('flipCard sends Command_FlipCard', () => { flipCard(gameId, { cardId: 7, faceDown: false }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_FlipCard_ext, expect.objectContaining({ cardId: 7, faceDown: false }) + gameId, Data.Command_FlipCard_ext, expect.objectContaining({ cardId: 7, faceDown: false }) ); }); it('gameSay sends Command_GameSay', () => { gameSay(gameId, { message: 'hello' }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_GameSay_ext, expect.objectContaining({ message: 'hello' }) + gameId, Data.Command_GameSay_ext, expect.objectContaining({ message: 'hello' }) ); }); it('incCardCounter sends Command_IncCardCounter', () => { incCardCounter(gameId, { cardId: 5, counterId: 1 }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_IncCardCounter_ext, expect.objectContaining({ cardId: 5, counterId: 1 }) + gameId, Data.Command_IncCardCounter_ext, expect.objectContaining({ cardId: 5, counterId: 1 }) ); }); it('incCounter sends Command_IncCounter', () => { incCounter(gameId, { counterId: 1, delta: 5 }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_IncCounter_ext, expect.objectContaining({ counterId: 1, delta: 5 }) + gameId, Data.Command_IncCounter_ext, expect.objectContaining({ counterId: 1, delta: 5 }) ); }); it('kickFromGame sends Command_KickFromGame', () => { kickFromGame(gameId, { playerId: 2 }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_KickFromGame_ext, expect.objectContaining({ playerId: 2 }) + gameId, Data.Command_KickFromGame_ext, expect.objectContaining({ playerId: 2 }) ); }); it('leaveGame sends Command_LeaveGame with empty object', () => { leaveGame(gameId); - expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_LeaveGame_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Data.Command_LeaveGame_ext, expect.any(Object)); }); it('moveCard sends Command_MoveCard', () => { moveCard(gameId, { startZone: 'hand', targetZone: 'graveyard' }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_MoveCard_ext, + gameId, Data.Command_MoveCard_ext, expect.objectContaining({ startZone: 'hand', targetZone: 'graveyard' }) ); }); @@ -202,45 +170,45 @@ describe('Game commands — delegate to webClient.protobuf.sendGameCommand', () it('mulligan sends Command_Mulligan', () => { mulligan(gameId, { number: 7 }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_Mulligan_ext, expect.objectContaining({ number: 7 }) + gameId, Data.Command_Mulligan_ext, expect.objectContaining({ number: 7 }) ); }); it('nextTurn sends Command_NextTurn with empty object', () => { nextTurn(gameId); - expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_NextTurn_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Data.Command_NextTurn_ext, expect.any(Object)); }); it('readyStart sends Command_ReadyStart', () => { readyStart(gameId, { ready: true }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_ReadyStart_ext, expect.objectContaining({ ready: true }) + gameId, Data.Command_ReadyStart_ext, expect.objectContaining({ ready: true }) ); }); it('revealCards sends Command_RevealCards', () => { revealCards(gameId, { zoneName: 'hand', cardId: [1, 2] }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_RevealCards_ext, expect.objectContaining({ zoneName: 'hand', cardId: [1, 2] }) + gameId, Data.Command_RevealCards_ext, expect.objectContaining({ zoneName: 'hand', cardId: [1, 2] }) ); }); it('reverseTurn sends Command_ReverseTurn with empty object', () => { reverseTurn(gameId); - expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_ReverseTurn_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Data.Command_ReverseTurn_ext, expect.any(Object)); }); it('setActivePhase sends Command_SetActivePhase', () => { setActivePhase(gameId, { phase: 2 }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_SetActivePhase_ext, expect.objectContaining({ phase: 2 }) + gameId, Data.Command_SetActivePhase_ext, expect.objectContaining({ phase: 2 }) ); }); it('setCardAttr sends Command_SetCardAttr', () => { setCardAttr(gameId, { zone: 'play', cardId: 5, attrValue: '2' }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_SetCardAttr_ext, + gameId, Data.Command_SetCardAttr_ext, expect.objectContaining({ zone: 'play', cardId: 5, attrValue: '2' }) ); }); @@ -248,56 +216,56 @@ describe('Game commands — delegate to webClient.protobuf.sendGameCommand', () it('setCardCounter sends Command_SetCardCounter', () => { setCardCounter(gameId, { cardId: 5, counterId: 1 }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_SetCardCounter_ext, expect.objectContaining({ cardId: 5, counterId: 1 }) + gameId, Data.Command_SetCardCounter_ext, expect.objectContaining({ cardId: 5, counterId: 1 }) ); }); it('setCounter sends Command_SetCounter', () => { setCounter(gameId, { counterId: 1, value: 10 }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_SetCounter_ext, expect.objectContaining({ counterId: 1, value: 10 }) + gameId, Data.Command_SetCounter_ext, expect.objectContaining({ counterId: 1, value: 10 }) ); }); it('setSideboardLock sends Command_SetSideboardLock', () => { setSideboardLock(gameId, { locked: true }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_SetSideboardLock_ext, expect.objectContaining({ locked: true }) + gameId, Data.Command_SetSideboardLock_ext, expect.objectContaining({ locked: true }) ); }); it('setSideboardPlan sends Command_SetSideboardPlan', () => { setSideboardPlan(gameId, { moveList: [] }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_SetSideboardPlan_ext, expect.objectContaining({ moveList: expect.any(Array) }) + gameId, Data.Command_SetSideboardPlan_ext, expect.objectContaining({ moveList: expect.any(Array) }) ); }); it('shuffle sends Command_Shuffle', () => { shuffle(gameId, { zoneName: 'hand' }); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( - gameId, Command_Shuffle_ext, expect.objectContaining({ zoneName: 'hand' }) + gameId, Data.Command_Shuffle_ext, expect.objectContaining({ zoneName: 'hand' }) ); }); it('undoDraw sends Command_UndoDraw with empty object', () => { undoDraw(gameId); - expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_UndoDraw_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Data.Command_UndoDraw_ext, expect.any(Object)); }); it('unconcede sends Command_Unconcede with empty object', () => { unconcede(gameId); - expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Command_Unconcede_ext, expect.any(Object)); + expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith(gameId, Data.Command_Unconcede_ext, expect.any(Object)); }); it('judge sends Command_Judge with targetId and wrapped gameCommand array', () => { const targetId = 3; - const innerCmd = create(GameCommandSchema); - setExtension(innerCmd, Command_DrawCards_ext, create(Command_DrawCardsSchema, { number: 2 })); + const innerCmd = create(Data.GameCommandSchema); + setExtension(innerCmd, Data.Command_DrawCards_ext, create(Data.Command_DrawCardsSchema, { number: 2 })); judge(gameId, targetId, innerCmd); expect(webClient.protobuf.sendGameCommand).toHaveBeenCalledWith( gameId, - Command_Judge_ext, + Data.Command_Judge_ext, expect.objectContaining({ targetId: 3, gameCommand: expect.any(Array) }) ); }); diff --git a/webclient/src/websocket/commands/game/gameSay.ts b/webclient/src/websocket/commands/game/gameSay.ts index 86a798f9c..44ca6479d 100644 --- a/webclient/src/websocket/commands/game/gameSay.ts +++ b/webclient/src/websocket/commands/game/gameSay.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_GameSaySchema, Command_GameSay_ext } from 'generated/proto/command_game_say_pb'; -import { GameSayParams } from 'types'; -export function gameSay(gameId: number, params: GameSayParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_GameSay_ext, create(Command_GameSaySchema, params)); +import { Data } from '@app/types'; + +export function gameSay(gameId: number, params: Data.GameSayParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_GameSay_ext, create(Data.Command_GameSaySchema, params)); } diff --git a/webclient/src/websocket/commands/game/incCardCounter.ts b/webclient/src/websocket/commands/game/incCardCounter.ts index 6a72d7baa..906622d3e 100644 --- a/webclient/src/websocket/commands/game/incCardCounter.ts +++ b/webclient/src/websocket/commands/game/incCardCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_IncCardCounterSchema, Command_IncCardCounter_ext } from 'generated/proto/command_inc_card_counter_pb'; -import { IncCardCounterParams } from 'types'; -export function incCardCounter(gameId: number, params: IncCardCounterParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_IncCardCounter_ext, create(Command_IncCardCounterSchema, params)); +import { Data } from '@app/types'; + +export function incCardCounter(gameId: number, params: Data.IncCardCounterParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_IncCardCounter_ext, create(Data.Command_IncCardCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/incCounter.ts b/webclient/src/websocket/commands/game/incCounter.ts index f00a358c9..2ef136e03 100644 --- a/webclient/src/websocket/commands/game/incCounter.ts +++ b/webclient/src/websocket/commands/game/incCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_IncCounterSchema, Command_IncCounter_ext } from 'generated/proto/command_inc_counter_pb'; -import { IncCounterParams } from 'types'; -export function incCounter(gameId: number, params: IncCounterParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_IncCounter_ext, create(Command_IncCounterSchema, params)); +import { Data } from '@app/types'; + +export function incCounter(gameId: number, params: Data.IncCounterParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_IncCounter_ext, create(Data.Command_IncCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/judge.ts b/webclient/src/websocket/commands/game/judge.ts index 5142131bb..634392dd6 100644 --- a/webclient/src/websocket/commands/game/judge.ts +++ b/webclient/src/websocket/commands/game/judge.ts @@ -1,10 +1,9 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_JudgeSchema, Command_Judge_ext } from 'generated/proto/game_commands_pb'; -import type { GameCommand } from 'generated/proto/game_commands_pb'; +import { Data } from '@app/types'; -export function judge(gameId: number, targetId: number, innerGameCommand: GameCommand): void { - webClient.protobuf.sendGameCommand(gameId, Command_Judge_ext, create(Command_JudgeSchema, { +export function judge(gameId: number, targetId: number, innerGameCommand: Data.GameCommand): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_Judge_ext, create(Data.Command_JudgeSchema, { targetId, gameCommand: [innerGameCommand], })); diff --git a/webclient/src/websocket/commands/game/kickFromGame.ts b/webclient/src/websocket/commands/game/kickFromGame.ts index ef41a755c..e6214478d 100644 --- a/webclient/src/websocket/commands/game/kickFromGame.ts +++ b/webclient/src/websocket/commands/game/kickFromGame.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_KickFromGameSchema, Command_KickFromGame_ext } from 'generated/proto/command_kick_from_game_pb'; -import { KickFromGameParams } from 'types'; -export function kickFromGame(gameId: number, params: KickFromGameParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_KickFromGame_ext, create(Command_KickFromGameSchema, params)); +import { Data } from '@app/types'; + +export function kickFromGame(gameId: number, params: Data.KickFromGameParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_KickFromGame_ext, create(Data.Command_KickFromGameSchema, params)); } diff --git a/webclient/src/websocket/commands/game/leaveGame.ts b/webclient/src/websocket/commands/game/leaveGame.ts index f46503510..28fa7f0da 100644 --- a/webclient/src/websocket/commands/game/leaveGame.ts +++ b/webclient/src/websocket/commands/game/leaveGame.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_LeaveGameSchema, Command_LeaveGame_ext } from 'generated/proto/command_leave_game_pb'; +import { Data } from '@app/types'; export function leaveGame(gameId: number): void { - webClient.protobuf.sendGameCommand(gameId, Command_LeaveGame_ext, create(Command_LeaveGameSchema)); + webClient.protobuf.sendGameCommand(gameId, Data.Command_LeaveGame_ext, create(Data.Command_LeaveGameSchema)); } diff --git a/webclient/src/websocket/commands/game/moveCard.ts b/webclient/src/websocket/commands/game/moveCard.ts index 400a6171c..eb547531c 100644 --- a/webclient/src/websocket/commands/game/moveCard.ts +++ b/webclient/src/websocket/commands/game/moveCard.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_MoveCardSchema, Command_MoveCard_ext } from 'generated/proto/command_move_card_pb'; -import { MoveCardParams } from 'types'; -export function moveCard(gameId: number, params: MoveCardParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_MoveCard_ext, create(Command_MoveCardSchema, params)); +import { Data } from '@app/types'; + +export function moveCard(gameId: number, params: Data.MoveCardParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_MoveCard_ext, create(Data.Command_MoveCardSchema, params)); } diff --git a/webclient/src/websocket/commands/game/mulligan.ts b/webclient/src/websocket/commands/game/mulligan.ts index d9285ddfc..21b1bbd69 100644 --- a/webclient/src/websocket/commands/game/mulligan.ts +++ b/webclient/src/websocket/commands/game/mulligan.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_MulliganSchema, Command_Mulligan_ext } from 'generated/proto/command_mulligan_pb'; -import { MulliganParams } from 'types'; -export function mulligan(gameId: number, params: MulliganParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_Mulligan_ext, create(Command_MulliganSchema, params)); +import { Data } from '@app/types'; + +export function mulligan(gameId: number, params: Data.MulliganParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_Mulligan_ext, create(Data.Command_MulliganSchema, params)); } diff --git a/webclient/src/websocket/commands/game/nextTurn.ts b/webclient/src/websocket/commands/game/nextTurn.ts index 70b332ef2..892ddc4e7 100644 --- a/webclient/src/websocket/commands/game/nextTurn.ts +++ b/webclient/src/websocket/commands/game/nextTurn.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_NextTurnSchema, Command_NextTurn_ext } from 'generated/proto/command_next_turn_pb'; +import { Data } from '@app/types'; export function nextTurn(gameId: number): void { - webClient.protobuf.sendGameCommand(gameId, Command_NextTurn_ext, create(Command_NextTurnSchema)); + webClient.protobuf.sendGameCommand(gameId, Data.Command_NextTurn_ext, create(Data.Command_NextTurnSchema)); } diff --git a/webclient/src/websocket/commands/game/readyStart.ts b/webclient/src/websocket/commands/game/readyStart.ts index def0a445e..0ec2f0461 100644 --- a/webclient/src/websocket/commands/game/readyStart.ts +++ b/webclient/src/websocket/commands/game/readyStart.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_ReadyStartSchema, Command_ReadyStart_ext } from 'generated/proto/command_ready_start_pb'; -import { ReadyStartParams } from 'types'; -export function readyStart(gameId: number, params: ReadyStartParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_ReadyStart_ext, create(Command_ReadyStartSchema, params)); +import { Data } from '@app/types'; + +export function readyStart(gameId: number, params: Data.ReadyStartParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_ReadyStart_ext, create(Data.Command_ReadyStartSchema, params)); } diff --git a/webclient/src/websocket/commands/game/revealCards.ts b/webclient/src/websocket/commands/game/revealCards.ts index aaa860928..168c91269 100644 --- a/webclient/src/websocket/commands/game/revealCards.ts +++ b/webclient/src/websocket/commands/game/revealCards.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_RevealCardsSchema, Command_RevealCards_ext } from 'generated/proto/command_reveal_cards_pb'; -import { RevealCardsParams } from 'types'; -export function revealCards(gameId: number, params: RevealCardsParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_RevealCards_ext, create(Command_RevealCardsSchema, params)); +import { Data } from '@app/types'; + +export function revealCards(gameId: number, params: Data.RevealCardsParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_RevealCards_ext, create(Data.Command_RevealCardsSchema, params)); } diff --git a/webclient/src/websocket/commands/game/reverseTurn.ts b/webclient/src/websocket/commands/game/reverseTurn.ts index e9be52a5b..89d9f0ac4 100644 --- a/webclient/src/websocket/commands/game/reverseTurn.ts +++ b/webclient/src/websocket/commands/game/reverseTurn.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_ReverseTurnSchema, Command_ReverseTurn_ext } from 'generated/proto/command_reverse_turn_pb'; +import { Data } from '@app/types'; export function reverseTurn(gameId: number): void { - webClient.protobuf.sendGameCommand(gameId, Command_ReverseTurn_ext, create(Command_ReverseTurnSchema)); + webClient.protobuf.sendGameCommand(gameId, Data.Command_ReverseTurn_ext, create(Data.Command_ReverseTurnSchema)); } diff --git a/webclient/src/websocket/commands/game/setActivePhase.ts b/webclient/src/websocket/commands/game/setActivePhase.ts index 9ccb04284..d936f09f7 100644 --- a/webclient/src/websocket/commands/game/setActivePhase.ts +++ b/webclient/src/websocket/commands/game/setActivePhase.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_SetActivePhaseSchema, Command_SetActivePhase_ext } from 'generated/proto/command_set_active_phase_pb'; -import { SetActivePhaseParams } from 'types'; -export function setActivePhase(gameId: number, params: SetActivePhaseParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_SetActivePhase_ext, create(Command_SetActivePhaseSchema, params)); +import { Data } from '@app/types'; + +export function setActivePhase(gameId: number, params: Data.SetActivePhaseParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_SetActivePhase_ext, create(Data.Command_SetActivePhaseSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setCardAttr.ts b/webclient/src/websocket/commands/game/setCardAttr.ts index 5e7e8c57f..1440712a7 100644 --- a/webclient/src/websocket/commands/game/setCardAttr.ts +++ b/webclient/src/websocket/commands/game/setCardAttr.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_SetCardAttrSchema, Command_SetCardAttr_ext } from 'generated/proto/command_set_card_attr_pb'; -import { SetCardAttrParams } from 'types'; -export function setCardAttr(gameId: number, params: SetCardAttrParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_SetCardAttr_ext, create(Command_SetCardAttrSchema, params)); +import { Data } from '@app/types'; + +export function setCardAttr(gameId: number, params: Data.SetCardAttrParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_SetCardAttr_ext, create(Data.Command_SetCardAttrSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setCardCounter.ts b/webclient/src/websocket/commands/game/setCardCounter.ts index 18a8a991f..a889f6bea 100644 --- a/webclient/src/websocket/commands/game/setCardCounter.ts +++ b/webclient/src/websocket/commands/game/setCardCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_SetCardCounterSchema, Command_SetCardCounter_ext } from 'generated/proto/command_set_card_counter_pb'; -import { SetCardCounterParams } from 'types'; -export function setCardCounter(gameId: number, params: SetCardCounterParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_SetCardCounter_ext, create(Command_SetCardCounterSchema, params)); +import { Data } from '@app/types'; + +export function setCardCounter(gameId: number, params: Data.SetCardCounterParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_SetCardCounter_ext, create(Data.Command_SetCardCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setCounter.ts b/webclient/src/websocket/commands/game/setCounter.ts index 579561d62..bf54d0fda 100644 --- a/webclient/src/websocket/commands/game/setCounter.ts +++ b/webclient/src/websocket/commands/game/setCounter.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_SetCounterSchema, Command_SetCounter_ext } from 'generated/proto/command_set_counter_pb'; -import { SetCounterParams } from 'types'; -export function setCounter(gameId: number, params: SetCounterParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_SetCounter_ext, create(Command_SetCounterSchema, params)); +import { Data } from '@app/types'; + +export function setCounter(gameId: number, params: Data.SetCounterParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_SetCounter_ext, create(Data.Command_SetCounterSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setSideboardLock.ts b/webclient/src/websocket/commands/game/setSideboardLock.ts index ff639a461..16b86c024 100644 --- a/webclient/src/websocket/commands/game/setSideboardLock.ts +++ b/webclient/src/websocket/commands/game/setSideboardLock.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_SetSideboardLockSchema, Command_SetSideboardLock_ext } from 'generated/proto/command_set_sideboard_lock_pb'; -import { SetSideboardLockParams } from 'types'; -export function setSideboardLock(gameId: number, params: SetSideboardLockParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_SetSideboardLock_ext, create(Command_SetSideboardLockSchema, params)); +import { Data } from '@app/types'; + +export function setSideboardLock(gameId: number, params: Data.SetSideboardLockParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_SetSideboardLock_ext, create(Data.Command_SetSideboardLockSchema, params)); } diff --git a/webclient/src/websocket/commands/game/setSideboardPlan.ts b/webclient/src/websocket/commands/game/setSideboardPlan.ts index d69d85c89..5f08d30f7 100644 --- a/webclient/src/websocket/commands/game/setSideboardPlan.ts +++ b/webclient/src/websocket/commands/game/setSideboardPlan.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_SetSideboardPlanSchema, Command_SetSideboardPlan_ext } from 'generated/proto/command_set_sideboard_plan_pb'; -import { SetSideboardPlanParams } from 'types'; -export function setSideboardPlan(gameId: number, params: SetSideboardPlanParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_SetSideboardPlan_ext, create(Command_SetSideboardPlanSchema, params)); +import { Data } from '@app/types'; + +export function setSideboardPlan(gameId: number, params: Data.SetSideboardPlanParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_SetSideboardPlan_ext, create(Data.Command_SetSideboardPlanSchema, params)); } diff --git a/webclient/src/websocket/commands/game/shuffle.ts b/webclient/src/websocket/commands/game/shuffle.ts index 943ad4a57..b91d5706a 100644 --- a/webclient/src/websocket/commands/game/shuffle.ts +++ b/webclient/src/websocket/commands/game/shuffle.ts @@ -1,8 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_ShuffleSchema, Command_Shuffle_ext } from 'generated/proto/command_shuffle_pb'; -import { ShuffleParams } from 'types'; -export function shuffle(gameId: number, params: ShuffleParams): void { - webClient.protobuf.sendGameCommand(gameId, Command_Shuffle_ext, create(Command_ShuffleSchema, params)); +import { Data } from '@app/types'; + +export function shuffle(gameId: number, params: Data.ShuffleParams): void { + webClient.protobuf.sendGameCommand(gameId, Data.Command_Shuffle_ext, create(Data.Command_ShuffleSchema, params)); } diff --git a/webclient/src/websocket/commands/game/unconcede.ts b/webclient/src/websocket/commands/game/unconcede.ts index e6888f7ce..e9c4a4c7a 100644 --- a/webclient/src/websocket/commands/game/unconcede.ts +++ b/webclient/src/websocket/commands/game/unconcede.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_UnconcedeSchema, Command_Unconcede_ext } from 'generated/proto/command_concede_pb'; +import { Data } from '@app/types'; export function unconcede(gameId: number): void { - webClient.protobuf.sendGameCommand(gameId, Command_Unconcede_ext, create(Command_UnconcedeSchema)); + webClient.protobuf.sendGameCommand(gameId, Data.Command_Unconcede_ext, create(Data.Command_UnconcedeSchema)); } diff --git a/webclient/src/websocket/commands/game/undoDraw.ts b/webclient/src/websocket/commands/game/undoDraw.ts index dd0a3358f..f8f820b87 100644 --- a/webclient/src/websocket/commands/game/undoDraw.ts +++ b/webclient/src/websocket/commands/game/undoDraw.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_UndoDrawSchema, Command_UndoDraw_ext } from 'generated/proto/command_undo_draw_pb'; +import { Data } from '@app/types'; export function undoDraw(gameId: number): void { - webClient.protobuf.sendGameCommand(gameId, Command_UndoDraw_ext, create(Command_UndoDrawSchema)); + webClient.protobuf.sendGameCommand(gameId, Data.Command_UndoDraw_ext, create(Data.Command_UndoDrawSchema)); } diff --git a/webclient/src/websocket/commands/moderator/banFromServer.ts b/webclient/src/websocket/commands/moderator/banFromServer.ts index c0227fddd..12f04dd9f 100644 --- a/webclient/src/websocket/commands/moderator/banFromServer.ts +++ b/webclient/src/websocket/commands/moderator/banFromServer.ts @@ -1,11 +1,12 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_BanFromServer_ext, Command_BanFromServerSchema } from 'generated/proto/moderator_commands_pb'; + import { ModeratorPersistence } from '../../persistence'; +import { Data } from '@app/types'; export function banFromServer(minutes: number, userName?: string, address?: string, reason?: string, visibleReason?: string, clientid?: string, removeMessages?: number): void { - webClient.protobuf.sendModeratorCommand(Command_BanFromServer_ext, create(Command_BanFromServerSchema, { + webClient.protobuf.sendModeratorCommand(Data.Command_BanFromServer_ext, create(Data.Command_BanFromServerSchema, { minutes, userName, address, reason, visibleReason, clientid, removeMessages }), { onSuccess: () => { diff --git a/webclient/src/websocket/commands/moderator/forceActivateUser.ts b/webclient/src/websocket/commands/moderator/forceActivateUser.ts index b31f8df31..99538a173 100644 --- a/webclient/src/websocket/commands/moderator/forceActivateUser.ts +++ b/webclient/src/websocket/commands/moderator/forceActivateUser.ts @@ -1,13 +1,12 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { - Command_ForceActivateUser_ext, Command_ForceActivateUserSchema, -} from 'generated/proto/moderator_commands_pb'; + import { ModeratorPersistence } from '../../persistence'; +import { Data } from '@app/types'; export function forceActivateUser(usernameToActivate: string, moderatorName: string): void { - const cmd = create(Command_ForceActivateUserSchema, { usernameToActivate, moderatorName }); - webClient.protobuf.sendModeratorCommand(Command_ForceActivateUser_ext, cmd, { + const cmd = create(Data.Command_ForceActivateUserSchema, { usernameToActivate, moderatorName }); + webClient.protobuf.sendModeratorCommand(Data.Command_ForceActivateUser_ext, cmd, { onSuccess: () => { ModeratorPersistence.forceActivateUser(usernameToActivate, moderatorName); }, diff --git a/webclient/src/websocket/commands/moderator/getAdminNotes.ts b/webclient/src/websocket/commands/moderator/getAdminNotes.ts index 9f554e536..f33ae4839 100644 --- a/webclient/src/websocket/commands/moderator/getAdminNotes.ts +++ b/webclient/src/websocket/commands/moderator/getAdminNotes.ts @@ -1,12 +1,12 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_GetAdminNotes_ext, Command_GetAdminNotesSchema } from 'generated/proto/moderator_commands_pb'; + import { ModeratorPersistence } from '../../persistence'; -import { Response_GetAdminNotes_ext } from 'generated/proto/response_get_admin_notes_pb'; +import { Data } from '@app/types'; export function getAdminNotes(userName: string): void { - webClient.protobuf.sendModeratorCommand(Command_GetAdminNotes_ext, create(Command_GetAdminNotesSchema, { userName }), { - responseExt: Response_GetAdminNotes_ext, + webClient.protobuf.sendModeratorCommand(Data.Command_GetAdminNotes_ext, create(Data.Command_GetAdminNotesSchema, { userName }), { + responseExt: Data.Response_GetAdminNotes_ext, onSuccess: (response) => { ModeratorPersistence.getAdminNotes(userName, response.notes); }, diff --git a/webclient/src/websocket/commands/moderator/getBanHistory.ts b/webclient/src/websocket/commands/moderator/getBanHistory.ts index c0fc9253d..8c296c216 100644 --- a/webclient/src/websocket/commands/moderator/getBanHistory.ts +++ b/webclient/src/websocket/commands/moderator/getBanHistory.ts @@ -1,12 +1,12 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_GetBanHistory_ext, Command_GetBanHistorySchema } from 'generated/proto/moderator_commands_pb'; + import { ModeratorPersistence } from '../../persistence'; -import { Response_BanHistory_ext } from 'generated/proto/response_ban_history_pb'; +import { Data } from '@app/types'; export function getBanHistory(userName: string): void { - webClient.protobuf.sendModeratorCommand(Command_GetBanHistory_ext, create(Command_GetBanHistorySchema, { userName }), { - responseExt: Response_BanHistory_ext, + webClient.protobuf.sendModeratorCommand(Data.Command_GetBanHistory_ext, create(Data.Command_GetBanHistorySchema, { userName }), { + responseExt: Data.Response_BanHistory_ext, onSuccess: (response) => { ModeratorPersistence.banHistory(userName, response.banList); }, diff --git a/webclient/src/websocket/commands/moderator/getWarnHistory.ts b/webclient/src/websocket/commands/moderator/getWarnHistory.ts index 4699b9333..c49a8d220 100644 --- a/webclient/src/websocket/commands/moderator/getWarnHistory.ts +++ b/webclient/src/websocket/commands/moderator/getWarnHistory.ts @@ -1,12 +1,12 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_GetWarnHistory_ext, Command_GetWarnHistorySchema } from 'generated/proto/moderator_commands_pb'; + import { ModeratorPersistence } from '../../persistence'; -import { Response_WarnHistory_ext } from 'generated/proto/response_warn_history_pb'; +import { Data } from '@app/types'; export function getWarnHistory(userName: string): void { - webClient.protobuf.sendModeratorCommand(Command_GetWarnHistory_ext, create(Command_GetWarnHistorySchema, { userName }), { - responseExt: Response_WarnHistory_ext, + webClient.protobuf.sendModeratorCommand(Data.Command_GetWarnHistory_ext, create(Data.Command_GetWarnHistorySchema, { userName }), { + responseExt: Data.Response_WarnHistory_ext, onSuccess: (response) => { ModeratorPersistence.warnHistory(userName, response.warnList); }, diff --git a/webclient/src/websocket/commands/moderator/getWarnList.ts b/webclient/src/websocket/commands/moderator/getWarnList.ts index 60c268f10..4ec9eef02 100644 --- a/webclient/src/websocket/commands/moderator/getWarnList.ts +++ b/webclient/src/websocket/commands/moderator/getWarnList.ts @@ -1,14 +1,18 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_GetWarnList_ext, Command_GetWarnListSchema } from 'generated/proto/moderator_commands_pb'; + import { ModeratorPersistence } from '../../persistence'; -import { Response_WarnList_ext } from 'generated/proto/response_warn_list_pb'; +import { Data } from '@app/types'; export function getWarnList(modName: string, userName: string, userClientid: string): void { - webClient.protobuf.sendModeratorCommand(Command_GetWarnList_ext, create(Command_GetWarnListSchema, { modName, userName, userClientid }), { - responseExt: Response_WarnList_ext, - onSuccess: (response) => { - ModeratorPersistence.warnListOptions([response]); - }, - }); + webClient.protobuf.sendModeratorCommand( + Data.Command_GetWarnList_ext, + create(Data.Command_GetWarnListSchema, { modName, userName, userClientid }), + { + responseExt: Data.Response_WarnList_ext, + onSuccess: (response) => { + ModeratorPersistence.warnListOptions([response]); + }, + } + ); } diff --git a/webclient/src/websocket/commands/moderator/grantReplayAccess.ts b/webclient/src/websocket/commands/moderator/grantReplayAccess.ts index 3826dd9d4..ebd59f4f8 100644 --- a/webclient/src/websocket/commands/moderator/grantReplayAccess.ts +++ b/webclient/src/websocket/commands/moderator/grantReplayAccess.ts @@ -1,14 +1,13 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { - Command_GrantReplayAccess_ext, Command_GrantReplayAccessSchema, -} from 'generated/proto/moderator_commands_pb'; + import { ModeratorPersistence } from '../../persistence'; +import { Data } from '@app/types'; export function grantReplayAccess(replayId: number, moderatorName: string): void { webClient.protobuf.sendModeratorCommand( - Command_GrantReplayAccess_ext, - create(Command_GrantReplayAccessSchema, { replayId, moderatorName }), + Data.Command_GrantReplayAccess_ext, + create(Data.Command_GrantReplayAccessSchema, { replayId, moderatorName }), { onSuccess: () => { ModeratorPersistence.grantReplayAccess(replayId, moderatorName); diff --git a/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts b/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts index 70a1bd9ff..e4be3d0c9 100644 --- a/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts +++ b/webclient/src/websocket/commands/moderator/moderatorCommands.spec.ts @@ -20,24 +20,9 @@ vi.mock('../../persistence', () => ({ import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; import webClient from '../../WebClient'; +import { Data } from '@app/types'; import { ModeratorPersistence } from '../../persistence'; -import { - Command_BanFromServer_ext, - Command_ForceActivateUser_ext, - Command_GetAdminNotes_ext, - Command_GetBanHistory_ext, - Command_GetWarnHistory_ext, - Command_GetWarnList_ext, - Command_GrantReplayAccess_ext, - Command_UpdateAdminNotes_ext, - Command_ViewLogHistory_ext, - Command_WarnUser_ext, -} from 'generated/proto/moderator_commands_pb'; -import { Response_GetAdminNotes_ext } from 'generated/proto/response_get_admin_notes_pb'; -import { Response_BanHistory_ext } from 'generated/proto/response_ban_history_pb'; -import { Response_WarnHistory_ext } from 'generated/proto/response_warn_history_pb'; -import { Response_WarnList_ext } from 'generated/proto/response_warn_list_pb'; -import { Response_ViewLogHistory_ext } from 'generated/proto/response_viewlog_history_pb'; + import { banFromServer } from './banFromServer'; import { forceActivateUser } from './forceActivateUser'; import { getAdminNotes } from './getAdminNotes'; @@ -48,7 +33,7 @@ import { grantReplayAccess } from './grantReplayAccess'; import { updateAdminNotes } from './updateAdminNotes'; import { viewLogHistory } from './viewLogHistory'; import { warnUser } from './warnUser'; - +import { create } from '@bufbuild/protobuf'; import { Mock } from 'vitest'; const { invokeOnSuccess } = makeCallbackHelpers( @@ -56,8 +41,6 @@ const { invokeOnSuccess } = makeCallbackHelpers( 2 ); -beforeEach(() => vi.clearAllMocks()); - // ---------------------------------------------------------------- // banFromServer // ---------------------------------------------------------------- @@ -66,7 +49,7 @@ describe('banFromServer', () => { it('calls sendModeratorCommand with Command_BanFromServer', () => { banFromServer(30, 'alice', '1.2.3.4', 'reason', 'visible', 'cid', 1); expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( - Command_BanFromServer_ext, + Data.Command_BanFromServer_ext, expect.objectContaining({ minutes: 30, userName: 'alice' }), expect.any(Object) ); @@ -87,7 +70,7 @@ describe('forceActivateUser', () => { it('calls sendModeratorCommand with Command_ForceActivateUser', () => { forceActivateUser('alice', 'mod1'); expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( - Command_ForceActivateUser_ext, expect.any(Object), expect.any(Object) + Data.Command_ForceActivateUser_ext, expect.any(Object), expect.any(Object) ); }); @@ -106,9 +89,9 @@ describe('getAdminNotes', () => { it('calls sendModeratorCommand with Command_GetAdminNotes', () => { getAdminNotes('alice'); expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( - Command_GetAdminNotes_ext, + Data.Command_GetAdminNotes_ext, expect.any(Object), - expect.objectContaining({ responseExt: Response_GetAdminNotes_ext }) + expect.objectContaining({ responseExt: Data.Response_GetAdminNotes_ext }) ); }); @@ -128,9 +111,9 @@ describe('getBanHistory', () => { it('calls sendModeratorCommand with Command_GetBanHistory', () => { getBanHistory('alice'); expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( - Command_GetBanHistory_ext, + Data.Command_GetBanHistory_ext, expect.any(Object), - expect.objectContaining({ responseExt: Response_BanHistory_ext }) + expect.objectContaining({ responseExt: Data.Response_BanHistory_ext }) ); }); @@ -150,9 +133,9 @@ describe('getWarnHistory', () => { it('calls sendModeratorCommand with Command_GetWarnHistory', () => { getWarnHistory('alice'); expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( - Command_GetWarnHistory_ext, + Data.Command_GetWarnHistory_ext, expect.any(Object), - expect.objectContaining({ responseExt: Response_WarnHistory_ext }) + expect.objectContaining({ responseExt: Data.Response_WarnHistory_ext }) ); }); @@ -172,9 +155,9 @@ describe('getWarnList', () => { it('calls sendModeratorCommand with Command_GetWarnList', () => { getWarnList('mod1', 'alice', 'US'); expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( - Command_GetWarnList_ext, + Data.Command_GetWarnList_ext, expect.any(Object), - expect.objectContaining({ responseExt: Response_WarnList_ext }) + expect.objectContaining({ responseExt: Data.Response_WarnList_ext }) ); }); @@ -194,7 +177,7 @@ describe('grantReplayAccess', () => { it('calls sendModeratorCommand with Command_GrantReplayAccess', () => { grantReplayAccess(10, 'mod1'); expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( - Command_GrantReplayAccess_ext, expect.any(Object), expect.any(Object) + Data.Command_GrantReplayAccess_ext, expect.any(Object), expect.any(Object) ); }); @@ -213,7 +196,7 @@ describe('updateAdminNotes', () => { it('calls sendModeratorCommand with Command_UpdateAdminNotes', () => { updateAdminNotes('alice', 'new notes'); expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( - Command_UpdateAdminNotes_ext, expect.any(Object), expect.any(Object) + Data.Command_UpdateAdminNotes_ext, expect.any(Object), expect.any(Object) ); }); @@ -230,16 +213,18 @@ describe('updateAdminNotes', () => { describe('viewLogHistory', () => { it('calls sendModeratorCommand with Command_ViewLogHistory', () => { - viewLogHistory({ dateRange: 7 } as any); + const filters = create(Data.Command_ViewLogHistorySchema, { dateRange: 7 }); + viewLogHistory(filters); expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith( - Command_ViewLogHistory_ext, + Data.Command_ViewLogHistory_ext, expect.any(Object), - expect.objectContaining({ responseExt: Response_ViewLogHistory_ext }) + expect.objectContaining({ responseExt: Data.Response_ViewLogHistory_ext }) ); }); it('onSuccess calls ModeratorPersistence.viewLogs with logMessage', () => { - viewLogHistory({ dateRange: 7 } as any); + const filters = create(Data.Command_ViewLogHistorySchema, { dateRange: 7 }); + viewLogHistory(filters); const resp = { logMessage: ['log1'] }; invokeOnSuccess(resp, { responseCode: 0 }); expect(ModeratorPersistence.viewLogs).toHaveBeenCalledWith(['log1']); @@ -253,7 +238,7 @@ describe('warnUser', () => { it('calls sendModeratorCommand with Command_WarnUser', () => { warnUser('alice', 'bad behavior', 'cid'); - expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith(Command_WarnUser_ext, expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendModeratorCommand).toHaveBeenCalledWith(Data.Command_WarnUser_ext, expect.any(Object), expect.any(Object)); }); it('onSuccess calls ModeratorPersistence.warnUser', () => { diff --git a/webclient/src/websocket/commands/moderator/updateAdminNotes.ts b/webclient/src/websocket/commands/moderator/updateAdminNotes.ts index 448f58e23..f0a284fb3 100644 --- a/webclient/src/websocket/commands/moderator/updateAdminNotes.ts +++ b/webclient/src/websocket/commands/moderator/updateAdminNotes.ts @@ -1,14 +1,17 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { - Command_UpdateAdminNotes_ext, Command_UpdateAdminNotesSchema, -} from 'generated/proto/moderator_commands_pb'; + import { ModeratorPersistence } from '../../persistence'; +import { Data } from '@app/types'; export function updateAdminNotes(userName: string, notes: string): void { - webClient.protobuf.sendModeratorCommand(Command_UpdateAdminNotes_ext, create(Command_UpdateAdminNotesSchema, { userName, notes }), { - onSuccess: () => { - ModeratorPersistence.updateAdminNotes(userName, notes); - }, - }); + webClient.protobuf.sendModeratorCommand( + Data.Command_UpdateAdminNotes_ext, + create(Data.Command_UpdateAdminNotesSchema, { userName, notes }), + { + onSuccess: () => { + ModeratorPersistence.updateAdminNotes(userName, notes); + }, + } + ); } diff --git a/webclient/src/websocket/commands/moderator/viewLogHistory.ts b/webclient/src/websocket/commands/moderator/viewLogHistory.ts index 264a535b2..f09f2fe0d 100644 --- a/webclient/src/websocket/commands/moderator/viewLogHistory.ts +++ b/webclient/src/websocket/commands/moderator/viewLogHistory.ts @@ -1,13 +1,13 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_ViewLogHistory_ext, Command_ViewLogHistorySchema } from 'generated/proto/moderator_commands_pb'; -import { ModeratorPersistence } from '../../persistence'; -import { Response_ViewLogHistory_ext } from 'generated/proto/response_viewlog_history_pb'; -import { LogFilters } from 'types'; -export function viewLogHistory(filters: LogFilters): void { - webClient.protobuf.sendModeratorCommand(Command_ViewLogHistory_ext, create(Command_ViewLogHistorySchema, filters), { - responseExt: Response_ViewLogHistory_ext, +import { ModeratorPersistence } from '../../persistence'; + +import { Data } from '@app/types'; + +export function viewLogHistory(filters: Data.ViewLogHistoryParams): void { + webClient.protobuf.sendModeratorCommand(Data.Command_ViewLogHistory_ext, create(Data.Command_ViewLogHistorySchema, filters), { + responseExt: Data.Response_ViewLogHistory_ext, onSuccess: (response) => { ModeratorPersistence.viewLogs(response.logMessage); }, diff --git a/webclient/src/websocket/commands/moderator/warnUser.ts b/webclient/src/websocket/commands/moderator/warnUser.ts index dfe2f3211..4a441a074 100644 --- a/webclient/src/websocket/commands/moderator/warnUser.ts +++ b/webclient/src/websocket/commands/moderator/warnUser.ts @@ -1,11 +1,12 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_WarnUser_ext, Command_WarnUserSchema } from 'generated/proto/moderator_commands_pb'; + import { ModeratorPersistence } from '../../persistence'; +import { Data } from '@app/types'; export function warnUser(userName: string, reason: string, clientid?: string, removeMessages?: number): void { - const cmd = create(Command_WarnUserSchema, { userName, reason, clientid, removeMessages }); - webClient.protobuf.sendModeratorCommand(Command_WarnUser_ext, cmd, { + const cmd = create(Data.Command_WarnUserSchema, { userName, reason, clientid, removeMessages }); + webClient.protobuf.sendModeratorCommand(Data.Command_WarnUser_ext, cmd, { onSuccess: () => { ModeratorPersistence.warnUser(userName); }, diff --git a/webclient/src/websocket/commands/room/createGame.ts b/webclient/src/websocket/commands/room/createGame.ts index be1b5b401..a531614fe 100644 --- a/webclient/src/websocket/commands/room/createGame.ts +++ b/webclient/src/websocket/commands/room/createGame.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_CreateGame_ext, Command_CreateGameSchema } from 'generated/proto/room_commands_pb'; -import { RoomPersistence } from '../../persistence'; -import { GameConfig } from 'types'; -export function createGame(roomId: number, gameConfig: GameConfig): void { - webClient.protobuf.sendRoomCommand(roomId, Command_CreateGame_ext, create(Command_CreateGameSchema, gameConfig), { +import { RoomPersistence } from '../../persistence'; +import { Data } from '@app/types'; + +export function createGame(roomId: number, gameConfig: Data.CreateGameParams): void { + webClient.protobuf.sendRoomCommand(roomId, Data.Command_CreateGame_ext, create(Data.Command_CreateGameSchema, gameConfig), { onSuccess: () => { RoomPersistence.gameCreated(roomId); }, diff --git a/webclient/src/websocket/commands/room/joinGame.ts b/webclient/src/websocket/commands/room/joinGame.ts index 9975eccef..97364e0d1 100644 --- a/webclient/src/websocket/commands/room/joinGame.ts +++ b/webclient/src/websocket/commands/room/joinGame.ts @@ -1,11 +1,11 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_JoinGame_ext, Command_JoinGameSchema } from 'generated/proto/room_commands_pb'; -import { RoomPersistence } from '../../persistence'; -import { JoinGameParams } from 'types'; -export function joinGame(roomId: number, joinGameParams: JoinGameParams): void { - webClient.protobuf.sendRoomCommand(roomId, Command_JoinGame_ext, create(Command_JoinGameSchema, joinGameParams), { +import { RoomPersistence } from '../../persistence'; +import { Data } from '@app/types'; + +export function joinGame(roomId: number, joinGameParams: Data.JoinGameParams): void { + webClient.protobuf.sendRoomCommand(roomId, Data.Command_JoinGame_ext, create(Data.Command_JoinGameSchema, joinGameParams), { onSuccess: () => { RoomPersistence.joinedGame(roomId, joinGameParams.gameId); }, diff --git a/webclient/src/websocket/commands/room/leaveRoom.ts b/webclient/src/websocket/commands/room/leaveRoom.ts index 67303f510..7203239f9 100644 --- a/webclient/src/websocket/commands/room/leaveRoom.ts +++ b/webclient/src/websocket/commands/room/leaveRoom.ts @@ -1,10 +1,11 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_LeaveRoom_ext, Command_LeaveRoomSchema } from 'generated/proto/room_commands_pb'; + import { RoomPersistence } from '../../persistence'; +import { Data } from '@app/types'; export function leaveRoom(roomId: number): void { - webClient.protobuf.sendRoomCommand(roomId, Command_LeaveRoom_ext, create(Command_LeaveRoomSchema), { + webClient.protobuf.sendRoomCommand(roomId, Data.Command_LeaveRoom_ext, create(Data.Command_LeaveRoomSchema), { onSuccess: () => { RoomPersistence.leaveRoom(roomId); }, diff --git a/webclient/src/websocket/commands/room/roomCommands.spec.ts b/webclient/src/websocket/commands/room/roomCommands.spec.ts index 0916eb233..be55fadc0 100644 --- a/webclient/src/websocket/commands/room/roomCommands.spec.ts +++ b/webclient/src/websocket/commands/room/roomCommands.spec.ts @@ -13,13 +13,14 @@ vi.mock('../../persistence', () => ({ import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; import webClient from '../../WebClient'; +import { Data } from '@app/types'; import { RoomPersistence } from '../../persistence'; -import { Command_CreateGame_ext, Command_JoinGame_ext, Command_LeaveRoom_ext, Command_RoomSay_ext } from 'generated/proto/room_commands_pb'; + import { createGame } from './createGame'; import { joinGame } from './joinGame'; import { leaveRoom } from './leaveRoom'; import { roomSay } from './roomSay'; - +import { create } from '@bufbuild/protobuf'; import { Mock } from 'vitest'; const { invokeOnSuccess } = makeCallbackHelpers( @@ -28,22 +29,20 @@ const { invokeOnSuccess } = makeCallbackHelpers( 3 ); -beforeEach(() => vi.clearAllMocks()); - // ---------------------------------------------------------------- // createGame // ---------------------------------------------------------------- describe('createGame', () => { it('calls sendRoomCommand with Command_CreateGame', () => { - createGame(5, { maxPlayers: 4 } as any); + createGame(5, create(Data.Command_CreateGameSchema, { maxPlayers: 4 })); expect(webClient.protobuf.sendRoomCommand).toHaveBeenCalledWith( - 5, Command_CreateGame_ext, expect.objectContaining({ maxPlayers: 4 }), expect.any(Object) + 5, Data.Command_CreateGame_ext, expect.objectContaining({ maxPlayers: 4 }), expect.any(Object) ); }); it('onSuccess calls RoomPersistence.gameCreated with roomId', () => { - createGame(5, {} as any); + createGame(5, create(Data.Command_CreateGameSchema, {})); invokeOnSuccess(); expect(RoomPersistence.gameCreated).toHaveBeenCalledWith(5); }); @@ -55,14 +54,14 @@ describe('createGame', () => { describe('joinGame', () => { it('calls sendRoomCommand with Command_JoinGame', () => { - joinGame(7, { gameId: 42, password: '' } as any); + joinGame(7, create(Data.Command_JoinGameSchema, { gameId: 42, password: '' })); expect(webClient.protobuf.sendRoomCommand).toHaveBeenCalledWith( - 7, Command_JoinGame_ext, expect.objectContaining({ gameId: 42, password: '' }), expect.any(Object) + 7, Data.Command_JoinGame_ext, expect.objectContaining({ gameId: 42, password: '' }), expect.any(Object) ); }); it('onSuccess calls RoomPersistence.joinedGame with roomId and gameId', () => { - joinGame(7, { gameId: 42 } as any); + joinGame(7, create(Data.Command_JoinGameSchema, { gameId: 42 })); invokeOnSuccess(); expect(RoomPersistence.joinedGame).toHaveBeenCalledWith(7, 42); }); @@ -75,7 +74,7 @@ describe('leaveRoom', () => { it('calls sendRoomCommand with Command_LeaveRoom', () => { leaveRoom(3); - expect(webClient.protobuf.sendRoomCommand).toHaveBeenCalledWith(3, Command_LeaveRoom_ext, expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendRoomCommand).toHaveBeenCalledWith(3, Data.Command_LeaveRoom_ext, expect.any(Object), expect.any(Object)); }); it('onSuccess calls RoomPersistence.leaveRoom with roomId', () => { @@ -94,7 +93,7 @@ describe('roomSay', () => { roomSay(2, ' hello '); expect(webClient.protobuf.sendRoomCommand).toHaveBeenCalledWith( 2, - Command_RoomSay_ext, + Data.Command_RoomSay_ext, expect.objectContaining({ message: 'hello' }) ); }); diff --git a/webclient/src/websocket/commands/room/roomSay.ts b/webclient/src/websocket/commands/room/roomSay.ts index d847dc527..74c44ada8 100644 --- a/webclient/src/websocket/commands/room/roomSay.ts +++ b/webclient/src/websocket/commands/room/roomSay.ts @@ -1,6 +1,6 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_RoomSay_ext, Command_RoomSaySchema } from 'generated/proto/room_commands_pb'; +import { Data } from '@app/types'; export function roomSay(roomId: number, message: string): void { const trimmed = message.trim(); @@ -9,5 +9,5 @@ export function roomSay(roomId: number, message: string): void { return; } - webClient.protobuf.sendRoomCommand(roomId, Command_RoomSay_ext, create(Command_RoomSaySchema, { message: trimmed })); + webClient.protobuf.sendRoomCommand(roomId, Data.Command_RoomSay_ext, create(Data.Command_RoomSaySchema, { message: trimmed })); } diff --git a/webclient/src/websocket/commands/session/accountEdit.ts b/webclient/src/websocket/commands/session/accountEdit.ts index ad77b4dfb..2dd77f737 100644 --- a/webclient/src/websocket/commands/session/accountEdit.ts +++ b/webclient/src/websocket/commands/session/accountEdit.ts @@ -1,11 +1,12 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_AccountEdit_ext, Command_AccountEditSchema } from 'generated/proto/session_commands_pb'; + import { SessionPersistence } from '../../persistence'; +import { Data } from '@app/types'; export function accountEdit(passwordCheck: string, realName?: string, email?: string, country?: string): void { - const cmd = create(Command_AccountEditSchema, { passwordCheck, realName, email, country }); - webClient.protobuf.sendSessionCommand(Command_AccountEdit_ext, cmd, { + const cmd = create(Data.Command_AccountEditSchema, { passwordCheck, realName, email, country }); + webClient.protobuf.sendSessionCommand(Data.Command_AccountEdit_ext, cmd, { onSuccess: () => { SessionPersistence.accountEditChanged(realName, email, country); }, diff --git a/webclient/src/websocket/commands/session/accountImage.ts b/webclient/src/websocket/commands/session/accountImage.ts index da603055f..36e7a78b4 100644 --- a/webclient/src/websocket/commands/session/accountImage.ts +++ b/webclient/src/websocket/commands/session/accountImage.ts @@ -1,10 +1,11 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_AccountImage_ext, Command_AccountImageSchema } from 'generated/proto/session_commands_pb'; + import { SessionPersistence } from '../../persistence'; +import { Data } from '@app/types'; export function accountImage(image: Uint8Array): void { - webClient.protobuf.sendSessionCommand(Command_AccountImage_ext, create(Command_AccountImageSchema, { image }), { + webClient.protobuf.sendSessionCommand(Data.Command_AccountImage_ext, create(Data.Command_AccountImageSchema, { image }), { onSuccess: () => { SessionPersistence.accountImageChanged(image); }, diff --git a/webclient/src/websocket/commands/session/accountPassword.ts b/webclient/src/websocket/commands/session/accountPassword.ts index 5cc561421..947dc4ad2 100644 --- a/webclient/src/websocket/commands/session/accountPassword.ts +++ b/webclient/src/websocket/commands/session/accountPassword.ts @@ -1,11 +1,12 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_AccountPassword_ext, Command_AccountPasswordSchema } from 'generated/proto/session_commands_pb'; + import { SessionPersistence } from '../../persistence'; +import { Data } from '@app/types'; export function accountPassword(oldPassword: string, newPassword: string, hashedNewPassword: string): void { - const cmd = create(Command_AccountPasswordSchema, { oldPassword, newPassword, hashedNewPassword }); - webClient.protobuf.sendSessionCommand(Command_AccountPassword_ext, cmd, { + const cmd = create(Data.Command_AccountPasswordSchema, { oldPassword, newPassword, hashedNewPassword }); + webClient.protobuf.sendSessionCommand(Data.Command_AccountPassword_ext, cmd, { onSuccess: () => { SessionPersistence.accountPasswordChange(); }, diff --git a/webclient/src/websocket/commands/session/activate.ts b/webclient/src/websocket/commands/session/activate.ts index 49614ab37..45faf1303 100644 --- a/webclient/src/websocket/commands/session/activate.ts +++ b/webclient/src/websocket/commands/session/activate.ts @@ -1,31 +1,34 @@ -import { AccountActivationParams } from 'store'; -import { StatusEnum, WebSocketConnectOptions } from 'types'; +import { App, Enriched, Data } from '@app/types'; import { create } from '@bufbuild/protobuf'; import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { Command_Activate_ext, Command_ActivateSchema } from 'generated/proto/session_commands_pb'; + import { SessionPersistence } from '../../persistence'; -import { Response_ResponseCode } from 'generated/proto/response_pb'; import { disconnect, login, updateStatus } from './'; -export function activate(options: WebSocketConnectOptions, password?: string, passwordSalt?: string): void { - const { userName, token } = options as unknown as AccountActivationParams; +export function activate(options: Omit, password?: string, passwordSalt?: string): void { + const { userName, token } = options; - webClient.protobuf.sendSessionCommand(Command_Activate_ext, create(Command_ActivateSchema, { + webClient.protobuf.sendSessionCommand(Data.Command_Activate_ext, create(Data.Command_ActivateSchema, { ...CLIENT_CONFIG, userName, token, }), { onResponseCode: { - [Response_ResponseCode.RespActivationAccepted]: () => { + [Data.Response_ResponseCode.RespActivationAccepted]: () => { SessionPersistence.accountActivationSuccess(); - login(options, password, passwordSalt); + login({ + host: options.host, + port: options.port, + userName: options.userName, + reason: App.WebSocketConnectReason.LOGIN, + }, password, passwordSalt); }, }, onError: () => { - updateStatus(StatusEnum.DISCONNECTED, 'Account Activation Failed'); + updateStatus(App.StatusEnum.DISCONNECTED, 'Account Activation Failed'); disconnect(); SessionPersistence.accountActivationFailed(); }, diff --git a/webclient/src/websocket/commands/session/addToList.ts b/webclient/src/websocket/commands/session/addToList.ts index 74bdfa23c..dac79b91d 100644 --- a/webclient/src/websocket/commands/session/addToList.ts +++ b/webclient/src/websocket/commands/session/addToList.ts @@ -1,7 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_AddToList_ext, Command_AddToListSchema } from 'generated/proto/session_commands_pb'; + import { SessionPersistence } from '../../persistence'; +import { Data } from '@app/types'; export function addToBuddyList(userName: string): void { addToList('buddy', userName); @@ -12,7 +13,7 @@ export function addToIgnoreList(userName: string): void { } export function addToList(list: string, userName: string): void { - webClient.protobuf.sendSessionCommand(Command_AddToList_ext, create(Command_AddToListSchema, { list, userName }), { + webClient.protobuf.sendSessionCommand(Data.Command_AddToList_ext, create(Data.Command_AddToListSchema, { list, userName }), { onSuccess: () => { SessionPersistence.addToList(list, userName); }, diff --git a/webclient/src/websocket/commands/session/connect.ts b/webclient/src/websocket/commands/session/connect.ts index 5b660fab1..b1b7e2793 100644 --- a/webclient/src/websocket/commands/session/connect.ts +++ b/webclient/src/websocket/commands/session/connect.ts @@ -1,24 +1,25 @@ -import { StatusEnum, WebSocketConnectOptions, WebSocketConnectReason } from 'types'; +import { App, Enriched } from '@app/types'; import webClient from '../../WebClient'; import { updateStatus } from './'; -export function connect(options: WebSocketConnectOptions, reason: WebSocketConnectReason): void { - switch (reason) { - case WebSocketConnectReason.LOGIN: - case WebSocketConnectReason.REGISTER: - case WebSocketConnectReason.ACTIVATE_ACCOUNT: - case WebSocketConnectReason.PASSWORD_RESET_REQUEST: - case WebSocketConnectReason.PASSWORD_RESET_CHALLENGE: - case WebSocketConnectReason.PASSWORD_RESET: - updateStatus(StatusEnum.CONNECTING, 'Connecting...'); - break; - case WebSocketConnectReason.TEST_CONNECTION: - webClient.testConnect({ ...options }); +export function connect(options: Enriched.WebSocketConnectOptions): void { + switch (options.reason) { + case App.WebSocketConnectReason.LOGIN: + case App.WebSocketConnectReason.REGISTER: + case App.WebSocketConnectReason.ACTIVATE_ACCOUNT: + case App.WebSocketConnectReason.PASSWORD_RESET_REQUEST: + case App.WebSocketConnectReason.PASSWORD_RESET_CHALLENGE: + case App.WebSocketConnectReason.PASSWORD_RESET: + updateStatus(App.StatusEnum.CONNECTING, 'Connecting...'); + webClient.connect(options); return; - default: - updateStatus(StatusEnum.DISCONNECTED, 'Unknown Connection Attempt: ' + reason); + case App.WebSocketConnectReason.TEST_CONNECTION: + webClient.testConnect(options); return; + default: { + const { reason } = options as Enriched.WebSocketConnectOptions; + updateStatus(App.StatusEnum.DISCONNECTED, `Unknown Connection Attempt: ${reason}`); + return; + } } - - webClient.connect({ ...options, reason }); } diff --git a/webclient/src/websocket/commands/session/deckDel.ts b/webclient/src/websocket/commands/session/deckDel.ts index bf0bd2672..d75cbdf8a 100644 --- a/webclient/src/websocket/commands/session/deckDel.ts +++ b/webclient/src/websocket/commands/session/deckDel.ts @@ -1,10 +1,11 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_DeckDelSchema, Command_DeckDel_ext } from 'generated/proto/command_deck_del_pb'; + import { SessionPersistence } from '../../persistence'; +import { Data } from '@app/types'; export function deckDel(deckId: number): void { - webClient.protobuf.sendSessionCommand(Command_DeckDel_ext, create(Command_DeckDelSchema, { deckId }), { + webClient.protobuf.sendSessionCommand(Data.Command_DeckDel_ext, create(Data.Command_DeckDelSchema, { deckId }), { onSuccess: () => { SessionPersistence.deleteServerDeck(deckId); }, diff --git a/webclient/src/websocket/commands/session/deckDelDir.ts b/webclient/src/websocket/commands/session/deckDelDir.ts index 7c442b575..30f6b94bc 100644 --- a/webclient/src/websocket/commands/session/deckDelDir.ts +++ b/webclient/src/websocket/commands/session/deckDelDir.ts @@ -1,10 +1,11 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_DeckDelDirSchema, Command_DeckDelDir_ext } from 'generated/proto/command_deck_del_dir_pb'; + import { SessionPersistence } from '../../persistence'; +import { Data } from '@app/types'; export function deckDelDir(path: string): void { - webClient.protobuf.sendSessionCommand(Command_DeckDelDir_ext, create(Command_DeckDelDirSchema, { path }), { + webClient.protobuf.sendSessionCommand(Data.Command_DeckDelDir_ext, create(Data.Command_DeckDelDirSchema, { path }), { onSuccess: () => { SessionPersistence.deleteServerDeckDir(path); }, diff --git a/webclient/src/websocket/commands/session/deckList.ts b/webclient/src/websocket/commands/session/deckList.ts index 71f611a26..12a240232 100644 --- a/webclient/src/websocket/commands/session/deckList.ts +++ b/webclient/src/websocket/commands/session/deckList.ts @@ -1,12 +1,12 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_DeckListSchema, Command_DeckList_ext } from 'generated/proto/command_deck_list_pb'; + import { SessionPersistence } from '../../persistence'; -import { Response_DeckList_ext } from 'generated/proto/response_deck_list_pb'; +import { Data } from '@app/types'; export function deckList(): void { - webClient.protobuf.sendSessionCommand(Command_DeckList_ext, create(Command_DeckListSchema), { - responseExt: Response_DeckList_ext, + webClient.protobuf.sendSessionCommand(Data.Command_DeckList_ext, create(Data.Command_DeckListSchema), { + responseExt: Data.Response_DeckList_ext, onSuccess: (response) => { if (response.root) { SessionPersistence.updateServerDecks(response); diff --git a/webclient/src/websocket/commands/session/deckNewDir.ts b/webclient/src/websocket/commands/session/deckNewDir.ts index d208bf0f4..566baeb15 100644 --- a/webclient/src/websocket/commands/session/deckNewDir.ts +++ b/webclient/src/websocket/commands/session/deckNewDir.ts @@ -1,10 +1,11 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_DeckNewDirSchema, Command_DeckNewDir_ext } from 'generated/proto/command_deck_new_dir_pb'; + import { SessionPersistence } from '../../persistence'; +import { Data } from '@app/types'; export function deckNewDir(path: string, dirName: string): void { - webClient.protobuf.sendSessionCommand(Command_DeckNewDir_ext, create(Command_DeckNewDirSchema, { path, dirName }), { + webClient.protobuf.sendSessionCommand(Data.Command_DeckNewDir_ext, create(Data.Command_DeckNewDirSchema, { path, dirName }), { onSuccess: () => { SessionPersistence.createServerDeckDir(path, dirName); }, diff --git a/webclient/src/websocket/commands/session/deckUpload.ts b/webclient/src/websocket/commands/session/deckUpload.ts index a13596329..84f8b64ef 100644 --- a/webclient/src/websocket/commands/session/deckUpload.ts +++ b/webclient/src/websocket/commands/session/deckUpload.ts @@ -1,12 +1,12 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_DeckUploadSchema, Command_DeckUpload_ext } from 'generated/proto/command_deck_upload_pb'; + import { SessionPersistence } from '../../persistence'; -import { Response_DeckUpload_ext } from 'generated/proto/response_deck_upload_pb'; +import { Data } from '@app/types'; export function deckUpload(path: string, deckId: number, deckList: string): void { - webClient.protobuf.sendSessionCommand(Command_DeckUpload_ext, create(Command_DeckUploadSchema, { path, deckId, deckList }), { - responseExt: Response_DeckUpload_ext, + webClient.protobuf.sendSessionCommand(Data.Command_DeckUpload_ext, create(Data.Command_DeckUploadSchema, { path, deckId, deckList }), { + responseExt: Data.Response_DeckUpload_ext, onSuccess: (response) => { if (response.newFile) { SessionPersistence.uploadServerDeck(path, response.newFile); diff --git a/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts b/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts index 98cfb25ac..8b6842414 100644 --- a/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts +++ b/webclient/src/websocket/commands/session/forgotPasswordChallenge.ts @@ -1,30 +1,27 @@ -import { ForgotPasswordChallengeParams } from 'store'; -import { StatusEnum, WebSocketConnectOptions } from 'types'; +import { App, Enriched, Data } from '@app/types'; import { create } from '@bufbuild/protobuf'; import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { - Command_ForgotPasswordChallenge_ext, Command_ForgotPasswordChallengeSchema, -} from 'generated/proto/session_commands_pb'; + import { SessionPersistence } from '../../persistence'; import { disconnect, updateStatus } from './'; -export function forgotPasswordChallenge(options: WebSocketConnectOptions): void { - const { userName, email } = options as unknown as ForgotPasswordChallengeParams; +export function forgotPasswordChallenge(options: Enriched.PasswordResetChallengeConnectOptions): void { + const { userName, email } = options; - webClient.protobuf.sendSessionCommand(Command_ForgotPasswordChallenge_ext, create(Command_ForgotPasswordChallengeSchema, { + webClient.protobuf.sendSessionCommand(Data.Command_ForgotPasswordChallenge_ext, create(Data.Command_ForgotPasswordChallengeSchema, { ...CLIENT_CONFIG, userName, email, }), { onSuccess: () => { - updateStatus(StatusEnum.DISCONNECTED, null); + updateStatus(App.StatusEnum.DISCONNECTED, null); SessionPersistence.resetPassword(); disconnect(); }, onError: () => { - updateStatus(StatusEnum.DISCONNECTED, null); + updateStatus(App.StatusEnum.DISCONNECTED, null); SessionPersistence.resetPasswordFailed(); disconnect(); }, diff --git a/webclient/src/websocket/commands/session/forgotPasswordRequest.ts b/webclient/src/websocket/commands/session/forgotPasswordRequest.ts index 6760b3908..06f12390f 100644 --- a/webclient/src/websocket/commands/session/forgotPasswordRequest.ts +++ b/webclient/src/websocket/commands/session/forgotPasswordRequest.ts @@ -1,37 +1,33 @@ -import { ForgotPasswordParams } from 'store'; -import { StatusEnum, WebSocketConnectOptions } from 'types'; +import { App, Enriched, Data } from '@app/types'; import { create } from '@bufbuild/protobuf'; import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { - Command_ForgotPasswordRequest_ext, Command_ForgotPasswordRequestSchema, -} from 'generated/proto/session_commands_pb'; + import { SessionPersistence } from '../../persistence'; -import { Response_ForgotPasswordRequest_ext } from 'generated/proto/response_forgotpasswordrequest_pb'; import { disconnect, updateStatus } from './'; -export function forgotPasswordRequest(options: WebSocketConnectOptions): void { - const { userName } = options as unknown as ForgotPasswordParams; +export function forgotPasswordRequest(options: Enriched.PasswordResetRequestConnectOptions): void { + const { userName } = options; - webClient.protobuf.sendSessionCommand(Command_ForgotPasswordRequest_ext, create(Command_ForgotPasswordRequestSchema, { + webClient.protobuf.sendSessionCommand(Data.Command_ForgotPasswordRequest_ext, create(Data.Command_ForgotPasswordRequestSchema, { ...CLIENT_CONFIG, userName, }), { - responseExt: Response_ForgotPasswordRequest_ext, + responseExt: Data.Response_ForgotPasswordRequest_ext, onSuccess: (resp) => { if (resp?.challengeEmail) { - updateStatus(StatusEnum.DISCONNECTED, null); + updateStatus(App.StatusEnum.DISCONNECTED, null); SessionPersistence.resetPasswordChallenge(); } else { - updateStatus(StatusEnum.DISCONNECTED, null); + updateStatus(App.StatusEnum.DISCONNECTED, null); SessionPersistence.resetPassword(); } disconnect(); }, onError: () => { - updateStatus(StatusEnum.DISCONNECTED, null); + updateStatus(App.StatusEnum.DISCONNECTED, null); SessionPersistence.resetPasswordFailed(); disconnect(); }, diff --git a/webclient/src/websocket/commands/session/forgotPasswordReset.ts b/webclient/src/websocket/commands/session/forgotPasswordReset.ts index 90625fd5c..a947d5e7f 100644 --- a/webclient/src/websocket/commands/session/forgotPasswordReset.ts +++ b/webclient/src/websocket/commands/session/forgotPasswordReset.ts @@ -1,22 +1,23 @@ -import { ForgotPasswordResetParams } from 'store'; -import { StatusEnum, WebSocketConnectOptions } from 'types'; +import { App, Enriched, Data } from '@app/types'; import { create } from '@bufbuild/protobuf'; import type { MessageInitShape } from '@bufbuild/protobuf'; import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { - Command_ForgotPasswordReset_ext, Command_ForgotPasswordResetSchema, -} from 'generated/proto/session_commands_pb'; + import { SessionPersistence } from '../../persistence'; import { hashPassword } from '../../utils'; import { disconnect, updateStatus } from '.'; -export function forgotPasswordReset(options: WebSocketConnectOptions, newPassword?: string, passwordSalt?: string): void { - const { userName, token } = options as unknown as ForgotPasswordResetParams; +export function forgotPasswordReset( + options: Omit, + newPassword?: string, + passwordSalt?: string +): void { + const { userName, token } = options; - const params: MessageInitShape = { + const params: MessageInitShape = { ...CLIENT_CONFIG, userName, token, @@ -25,14 +26,14 @@ export function forgotPasswordReset(options: WebSocketConnectOptions, newPasswor : { newPassword }), }; - webClient.protobuf.sendSessionCommand(Command_ForgotPasswordReset_ext, create(Command_ForgotPasswordResetSchema, params), { + webClient.protobuf.sendSessionCommand(Data.Command_ForgotPasswordReset_ext, create(Data.Command_ForgotPasswordResetSchema, params), { onSuccess: () => { - updateStatus(StatusEnum.DISCONNECTED, null); + updateStatus(App.StatusEnum.DISCONNECTED, null); SessionPersistence.resetPasswordSuccess(); disconnect(); }, onError: () => { - updateStatus(StatusEnum.DISCONNECTED, null); + updateStatus(App.StatusEnum.DISCONNECTED, null); SessionPersistence.resetPasswordFailed(); disconnect(); }, diff --git a/webclient/src/websocket/commands/session/getGamesOfUser.ts b/webclient/src/websocket/commands/session/getGamesOfUser.ts index 184a31494..437e04425 100644 --- a/webclient/src/websocket/commands/session/getGamesOfUser.ts +++ b/webclient/src/websocket/commands/session/getGamesOfUser.ts @@ -1,12 +1,12 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_GetGamesOfUser_ext, Command_GetGamesOfUserSchema } from 'generated/proto/session_commands_pb'; + import { SessionPersistence } from '../../persistence'; -import { Response_GetGamesOfUser_ext } from 'generated/proto/response_get_games_of_user_pb'; +import { Data } from '@app/types'; export function getGamesOfUser(userName: string): void { - webClient.protobuf.sendSessionCommand(Command_GetGamesOfUser_ext, create(Command_GetGamesOfUserSchema, { userName }), { - responseExt: Response_GetGamesOfUser_ext, + webClient.protobuf.sendSessionCommand(Data.Command_GetGamesOfUser_ext, create(Data.Command_GetGamesOfUserSchema, { userName }), { + responseExt: Data.Response_GetGamesOfUser_ext, onSuccess: (response) => { SessionPersistence.getGamesOfUser(userName, response); }, diff --git a/webclient/src/websocket/commands/session/getUserInfo.ts b/webclient/src/websocket/commands/session/getUserInfo.ts index cf1bf0f4c..88a2a5c7e 100644 --- a/webclient/src/websocket/commands/session/getUserInfo.ts +++ b/webclient/src/websocket/commands/session/getUserInfo.ts @@ -1,12 +1,12 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_GetUserInfo_ext, Command_GetUserInfoSchema } from 'generated/proto/session_commands_pb'; + import { SessionPersistence } from '../../persistence'; -import { Response_GetUserInfo_ext } from 'generated/proto/response_get_user_info_pb'; +import { Data } from '@app/types'; export function getUserInfo(userName: string): void { - webClient.protobuf.sendSessionCommand(Command_GetUserInfo_ext, create(Command_GetUserInfoSchema, { userName }), { - responseExt: Response_GetUserInfo_ext, + webClient.protobuf.sendSessionCommand(Data.Command_GetUserInfo_ext, create(Data.Command_GetUserInfoSchema, { userName }), { + responseExt: Data.Response_GetUserInfo_ext, onSuccess: (response) => { SessionPersistence.getUserInfo(response.userInfo); }, diff --git a/webclient/src/websocket/commands/session/joinRoom.ts b/webclient/src/websocket/commands/session/joinRoom.ts index 267a7e07b..73c9590eb 100644 --- a/webclient/src/websocket/commands/session/joinRoom.ts +++ b/webclient/src/websocket/commands/session/joinRoom.ts @@ -1,12 +1,12 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_JoinRoom_ext, Command_JoinRoomSchema } from 'generated/proto/session_commands_pb'; + import { RoomPersistence } from '../../persistence'; -import { Response_JoinRoom_ext } from 'generated/proto/response_join_room_pb'; +import { Data } from '@app/types'; export function joinRoom(roomId: number): void { - webClient.protobuf.sendSessionCommand(Command_JoinRoom_ext, create(Command_JoinRoomSchema, { roomId }), { - responseExt: Response_JoinRoom_ext, + webClient.protobuf.sendSessionCommand(Data.Command_JoinRoom_ext, create(Data.Command_JoinRoomSchema, { roomId }), { + responseExt: Data.Response_JoinRoom_ext, onSuccess: (response) => { if (response.roomInfo) { RoomPersistence.joinRoom(response.roomInfo); diff --git a/webclient/src/websocket/commands/session/listRooms.ts b/webclient/src/websocket/commands/session/listRooms.ts index efaeab483..4e54f0e65 100644 --- a/webclient/src/websocket/commands/session/listRooms.ts +++ b/webclient/src/websocket/commands/session/listRooms.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_ListRooms_ext, Command_ListRoomsSchema } from 'generated/proto/session_commands_pb'; +import { Data } from '@app/types'; export function listRooms(): void { - webClient.protobuf.sendSessionCommand(Command_ListRooms_ext, create(Command_ListRoomsSchema)); + webClient.protobuf.sendSessionCommand(Data.Command_ListRooms_ext, create(Data.Command_ListRoomsSchema)); } diff --git a/webclient/src/websocket/commands/session/listUsers.ts b/webclient/src/websocket/commands/session/listUsers.ts index 50f831234..519168875 100644 --- a/webclient/src/websocket/commands/session/listUsers.ts +++ b/webclient/src/websocket/commands/session/listUsers.ts @@ -1,12 +1,12 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_ListUsers_ext, Command_ListUsersSchema } from 'generated/proto/session_commands_pb'; + import { SessionPersistence } from '../../persistence'; -import { Response_ListUsers_ext } from 'generated/proto/response_list_users_pb'; +import { Data } from '@app/types'; export function listUsers(): void { - webClient.protobuf.sendSessionCommand(Command_ListUsers_ext, create(Command_ListUsersSchema), { - responseExt: Response_ListUsers_ext, + webClient.protobuf.sendSessionCommand(Data.Command_ListUsers_ext, create(Data.Command_ListUsersSchema), { + responseExt: Data.Response_ListUsers_ext, onSuccess: (response) => { SessionPersistence.updateUsers(response.userList); }, diff --git a/webclient/src/websocket/commands/session/login.ts b/webclient/src/websocket/commands/session/login.ts index 3d93ec0ec..57332a5a6 100644 --- a/webclient/src/websocket/commands/session/login.ts +++ b/webclient/src/websocket/commands/session/login.ts @@ -1,13 +1,11 @@ -import { StatusEnum, WebSocketConnectOptions } from 'types'; +import { App, Enriched, Data } from '@app/types'; import { create } from '@bufbuild/protobuf'; import type { MessageInitShape } from '@bufbuild/protobuf'; import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { Command_Login_ext, Command_LoginSchema } from 'generated/proto/session_commands_pb'; + import { hashPassword } from '../../utils'; import { SessionPersistence } from '../../persistence'; -import { Response_Login_ext } from 'generated/proto/response_login_pb'; -import { Response_ResponseCode } from 'generated/proto/response_pb'; import { disconnect, @@ -16,10 +14,10 @@ import { updateStatus, } from './'; -export function login(options: WebSocketConnectOptions, password?: string, passwordSalt?: string): void { +export function login(options: Omit, password?: string, passwordSalt?: string): void { const { userName, hashedPassword } = options; - const loginConfig: MessageInitShape = { + const loginConfig: MessageInitShape = { ...CLIENT_CONFIG, clientid: 'webatrice', userName, @@ -29,50 +27,52 @@ export function login(options: WebSocketConnectOptions, password?: string, passw }; const onLoginError = (message: string, extra?: () => void) => { - updateStatus(StatusEnum.DISCONNECTED, message); + updateStatus(App.StatusEnum.DISCONNECTED, message); extra?.(); SessionPersistence.loginFailed(); disconnect(); }; - webClient.protobuf.sendSessionCommand(Command_Login_ext, create(Command_LoginSchema, loginConfig), { - responseExt: Response_Login_ext, + webClient.protobuf.sendSessionCommand(Data.Command_Login_ext, create(Data.Command_LoginSchema, loginConfig), { + responseExt: Data.Response_Login_ext, onSuccess: (resp) => { const { buddyList, ignoreList, userInfo } = resp; SessionPersistence.updateBuddyList(buddyList); SessionPersistence.updateIgnoreList(ignoreList); SessionPersistence.updateUser(userInfo); - const { password: _password, ...safeConfig } = loginConfig; - SessionPersistence.loginSuccessful(safeConfig); + SessionPersistence.loginSuccessful({ hashedPassword: loginConfig.hashedPassword }); listUsers(); listRooms(); - updateStatus(StatusEnum.LOGGED_IN, 'Logged in.'); + updateStatus(App.StatusEnum.LOGGED_IN, 'Logged in.'); }, onResponseCode: { - [Response_ResponseCode.RespClientUpdateRequired]: () => + [Data.Response_ResponseCode.RespClientUpdateRequired]: () => onLoginError('Login failed: missing features'), - [Response_ResponseCode.RespWrongPassword]: () => + [Data.Response_ResponseCode.RespWrongPassword]: () => onLoginError('Login failed: incorrect username or password'), - [Response_ResponseCode.RespUsernameInvalid]: () => + [Data.Response_ResponseCode.RespUsernameInvalid]: () => onLoginError('Login failed: incorrect username or password'), - [Response_ResponseCode.RespWouldOverwriteOldSession]: () => + [Data.Response_ResponseCode.RespWouldOverwriteOldSession]: () => onLoginError('Login failed: duplicated user session'), - [Response_ResponseCode.RespUserIsBanned]: () => + [Data.Response_ResponseCode.RespUserIsBanned]: () => onLoginError('Login failed: banned user'), - [Response_ResponseCode.RespRegistrationRequired]: () => + [Data.Response_ResponseCode.RespRegistrationRequired]: () => onLoginError('Login failed: registration required'), - [Response_ResponseCode.RespClientIdRequired]: () => + [Data.Response_ResponseCode.RespClientIdRequired]: () => onLoginError('Login failed: missing client ID'), - [Response_ResponseCode.RespContextError]: () => + [Data.Response_ResponseCode.RespContextError]: () => onLoginError('Login failed: server error'), - [Response_ResponseCode.RespAccountNotActivated]: () => + [Data.Response_ResponseCode.RespAccountNotActivated]: () => onLoginError('Login failed: account not activated', () => { - const { password: _p, newPassword: _np, ...safeOptions } = options; - SessionPersistence.accountAwaitingActivation(safeOptions); + SessionPersistence.accountAwaitingActivation({ + host: options.host, + port: options.port, + userName: options.userName, + }); } ), }, diff --git a/webclient/src/websocket/commands/session/message.ts b/webclient/src/websocket/commands/session/message.ts index b310010be..4c0aba16b 100644 --- a/webclient/src/websocket/commands/session/message.ts +++ b/webclient/src/websocket/commands/session/message.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_Message_ext, Command_MessageSchema } from 'generated/proto/session_commands_pb'; +import { Data } from '@app/types'; export function message(userName: string, message: string): void { - webClient.protobuf.sendSessionCommand(Command_Message_ext, create(Command_MessageSchema, { userName, message })); + webClient.protobuf.sendSessionCommand(Data.Command_Message_ext, create(Data.Command_MessageSchema, { userName, message })); } diff --git a/webclient/src/websocket/commands/session/ping.ts b/webclient/src/websocket/commands/session/ping.ts index bb015094a..aef81d2b1 100644 --- a/webclient/src/websocket/commands/session/ping.ts +++ b/webclient/src/websocket/commands/session/ping.ts @@ -1,9 +1,9 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_Ping_ext, Command_PingSchema } from 'generated/proto/session_commands_pb'; +import { Data } from '@app/types'; export function ping(pingReceived: () => void): void { - webClient.protobuf.sendSessionCommand(Command_Ping_ext, create(Command_PingSchema), { + webClient.protobuf.sendSessionCommand(Data.Command_Ping_ext, create(Data.Command_PingSchema), { onResponse: () => pingReceived(), }); } diff --git a/webclient/src/websocket/commands/session/register.ts b/webclient/src/websocket/commands/session/register.ts index 8166f9f8e..fa5857fcb 100644 --- a/webclient/src/websocket/commands/session/register.ts +++ b/webclient/src/websocket/commands/session/register.ts @@ -1,22 +1,19 @@ -import { ServerRegisterParams } from 'store'; -import { StatusEnum, WebSocketConnectOptions } from 'types'; +import { App, Enriched, Data } from '@app/types'; import { create, getExtension } from '@bufbuild/protobuf'; import type { MessageInitShape } from '@bufbuild/protobuf'; import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { Command_Register_ext, Command_RegisterSchema } from 'generated/proto/session_commands_pb'; + import { SessionPersistence } from '../../persistence'; import { hashPassword } from '../../utils'; -import { Response_ResponseCode } from 'generated/proto/response_pb'; -import { Response_Register_ext } from 'generated/proto/response_register_pb'; import { login, disconnect, updateStatus } from './'; -export function register(options: WebSocketConnectOptions, password?: string, passwordSalt?: string): void { - const { userName, email, country, realName } = options as ServerRegisterParams; +export function register(options: Omit, password?: string, passwordSalt?: string): void { + const { userName, email, country, realName } = options; - const params: MessageInitShape = { + const params: MessageInitShape = { ...CLIENT_CONFIG, userName, email, @@ -29,45 +26,53 @@ export function register(options: WebSocketConnectOptions, password?: string, pa const onRegistrationError = (action: () => void) => { action(); - updateStatus(StatusEnum.DISCONNECTED, 'Registration failed'); + updateStatus(App.StatusEnum.DISCONNECTED, 'Registration failed'); disconnect(); }; - webClient.protobuf.sendSessionCommand(Command_Register_ext, create(Command_RegisterSchema, params), { + webClient.protobuf.sendSessionCommand(Data.Command_Register_ext, create(Data.Command_RegisterSchema, params), { onResponseCode: { - [Response_ResponseCode.RespRegistrationAccepted]: () => { - login(options, password, passwordSalt); + [Data.Response_ResponseCode.RespRegistrationAccepted]: () => { + login({ + host: options.host, + port: options.port, + userName: options.userName, + reason: App.WebSocketConnectReason.LOGIN, + }, password, passwordSalt); SessionPersistence.registrationSuccess(); }, - [Response_ResponseCode.RespRegistrationAcceptedNeedsActivation]: () => { - updateStatus(StatusEnum.DISCONNECTED, 'Registration accepted, awaiting activation'); - const { password: _p, newPassword: _np, ...safeOptions } = options; - SessionPersistence.accountAwaitingActivation(safeOptions); + [Data.Response_ResponseCode.RespRegistrationAcceptedNeedsActivation]: () => { + updateStatus(App.StatusEnum.DISCONNECTED, 'Registration accepted, awaiting activation'); + SessionPersistence.accountAwaitingActivation({ + host: options.host, + port: options.port, + userName: options.userName, + }); disconnect(); }, - [Response_ResponseCode.RespUserAlreadyExists]: () => onRegistrationError( + [Data.Response_ResponseCode.RespUserAlreadyExists]: () => onRegistrationError( () => SessionPersistence.registrationUserNameError('Username is taken') ), - [Response_ResponseCode.RespUsernameInvalid]: () => onRegistrationError( + [Data.Response_ResponseCode.RespUsernameInvalid]: () => onRegistrationError( () => SessionPersistence.registrationUserNameError('Invalid username') ), - [Response_ResponseCode.RespPasswordTooShort]: () => onRegistrationError( + [Data.Response_ResponseCode.RespPasswordTooShort]: () => onRegistrationError( () => SessionPersistence.registrationPasswordError('Your password was too short') ), - [Response_ResponseCode.RespEmailRequiredToRegister]: () => onRegistrationError( + [Data.Response_ResponseCode.RespEmailRequiredToRegister]: () => onRegistrationError( () => SessionPersistence.registrationRequiresEmail() ), - [Response_ResponseCode.RespEmailBlackListed]: () => onRegistrationError( + [Data.Response_ResponseCode.RespEmailBlackListed]: () => onRegistrationError( () => SessionPersistence.registrationEmailError('This email provider has been blocked') ), - [Response_ResponseCode.RespTooManyRequests]: () => onRegistrationError( + [Data.Response_ResponseCode.RespTooManyRequests]: () => onRegistrationError( () => SessionPersistence.registrationEmailError('Max accounts reached for this email') ), - [Response_ResponseCode.RespRegistrationDisabled]: () => onRegistrationError( + [Data.Response_ResponseCode.RespRegistrationDisabled]: () => onRegistrationError( () => SessionPersistence.registrationFailed('Registration is currently disabled') ), - [Response_ResponseCode.RespUserIsBanned]: (raw) => { - const register = getExtension(raw, Response_Register_ext); + [Data.Response_ResponseCode.RespUserIsBanned]: (raw) => { + const register = getExtension(raw, Data.Response_Register_ext); onRegistrationError( () => SessionPersistence.registrationFailed(register.deniedReasonStr, Number(register.deniedEndTime)) ); diff --git a/webclient/src/websocket/commands/session/removeFromList.ts b/webclient/src/websocket/commands/session/removeFromList.ts index 033701815..d4ef36b01 100644 --- a/webclient/src/websocket/commands/session/removeFromList.ts +++ b/webclient/src/websocket/commands/session/removeFromList.ts @@ -1,7 +1,8 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_RemoveFromList_ext, Command_RemoveFromListSchema } from 'generated/proto/session_commands_pb'; + import { SessionPersistence } from '../../persistence'; +import { Data } from '@app/types'; export function removeFromBuddyList(userName: string): void { removeFromList('buddy', userName); @@ -12,7 +13,7 @@ export function removeFromIgnoreList(userName: string): void { } export function removeFromList(list: string, userName: string): void { - webClient.protobuf.sendSessionCommand(Command_RemoveFromList_ext, create(Command_RemoveFromListSchema, { list, userName }), { + webClient.protobuf.sendSessionCommand(Data.Command_RemoveFromList_ext, create(Data.Command_RemoveFromListSchema, { list, userName }), { onSuccess: () => { SessionPersistence.removeFromList(list, userName); }, diff --git a/webclient/src/websocket/commands/session/replayDeleteMatch.ts b/webclient/src/websocket/commands/session/replayDeleteMatch.ts index 09f99c84a..121890bc4 100644 --- a/webclient/src/websocket/commands/session/replayDeleteMatch.ts +++ b/webclient/src/websocket/commands/session/replayDeleteMatch.ts @@ -1,10 +1,11 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_ReplayDeleteMatchSchema, Command_ReplayDeleteMatch_ext } from 'generated/proto/command_replay_delete_match_pb'; + import { SessionPersistence } from '../../persistence'; +import { Data } from '@app/types'; export function replayDeleteMatch(gameId: number): void { - webClient.protobuf.sendSessionCommand(Command_ReplayDeleteMatch_ext, create(Command_ReplayDeleteMatchSchema, { gameId }), { + webClient.protobuf.sendSessionCommand(Data.Command_ReplayDeleteMatch_ext, create(Data.Command_ReplayDeleteMatchSchema, { gameId }), { onSuccess: () => { SessionPersistence.replayDeleteMatch(gameId); }, diff --git a/webclient/src/websocket/commands/session/replayGetCode.ts b/webclient/src/websocket/commands/session/replayGetCode.ts index 8c5b1121c..8e7a9b9b0 100644 --- a/webclient/src/websocket/commands/session/replayGetCode.ts +++ b/webclient/src/websocket/commands/session/replayGetCode.ts @@ -1,11 +1,10 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_ReplayGetCodeSchema, Command_ReplayGetCode_ext } from 'generated/proto/command_replay_get_code_pb'; -import { Response_ReplayGetCode_ext } from 'generated/proto/response_replay_get_code_pb'; +import { Data } from '@app/types'; export function replayGetCode(gameId: number, onCodeReceived: (code: string) => void): void { - webClient.protobuf.sendSessionCommand(Command_ReplayGetCode_ext, create(Command_ReplayGetCodeSchema, { gameId }), { - responseExt: Response_ReplayGetCode_ext, + webClient.protobuf.sendSessionCommand(Data.Command_ReplayGetCode_ext, create(Data.Command_ReplayGetCodeSchema, { gameId }), { + responseExt: Data.Response_ReplayGetCode_ext, onSuccess: (response) => { onCodeReceived(response.replayCode); }, diff --git a/webclient/src/websocket/commands/session/replayList.ts b/webclient/src/websocket/commands/session/replayList.ts index 7fd45a3b2..c5752267c 100644 --- a/webclient/src/websocket/commands/session/replayList.ts +++ b/webclient/src/websocket/commands/session/replayList.ts @@ -1,12 +1,12 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_ReplayListSchema, Command_ReplayList_ext } from 'generated/proto/command_replay_list_pb'; + import { SessionPersistence } from '../../persistence'; -import { Response_ReplayList_ext } from 'generated/proto/response_replay_list_pb'; +import { Data } from '@app/types'; export function replayList(): void { - webClient.protobuf.sendSessionCommand(Command_ReplayList_ext, create(Command_ReplayListSchema), { - responseExt: Response_ReplayList_ext, + webClient.protobuf.sendSessionCommand(Data.Command_ReplayList_ext, create(Data.Command_ReplayListSchema), { + responseExt: Data.Response_ReplayList_ext, onSuccess: (response) => { SessionPersistence.replayList(response.matchList); }, diff --git a/webclient/src/websocket/commands/session/replayModifyMatch.ts b/webclient/src/websocket/commands/session/replayModifyMatch.ts index b104a34ec..a5fd84fe8 100644 --- a/webclient/src/websocket/commands/session/replayModifyMatch.ts +++ b/webclient/src/websocket/commands/session/replayModifyMatch.ts @@ -1,12 +1,17 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_ReplayModifyMatchSchema, Command_ReplayModifyMatch_ext } from 'generated/proto/command_replay_modify_match_pb'; + import { SessionPersistence } from '../../persistence'; +import { Data } from '@app/types'; export function replayModifyMatch(gameId: number, doNotHide: boolean): void { - webClient.protobuf.sendSessionCommand(Command_ReplayModifyMatch_ext, create(Command_ReplayModifyMatchSchema, { gameId, doNotHide }), { - onSuccess: () => { - SessionPersistence.replayModifyMatch(gameId, doNotHide); - }, - }); + webClient.protobuf.sendSessionCommand( + Data.Command_ReplayModifyMatch_ext, + create(Data.Command_ReplayModifyMatchSchema, { gameId, doNotHide }), + { + onSuccess: () => { + SessionPersistence.replayModifyMatch(gameId, doNotHide); + }, + } + ); } diff --git a/webclient/src/websocket/commands/session/replaySubmitCode.ts b/webclient/src/websocket/commands/session/replaySubmitCode.ts index 0d55a79db..351439fb2 100644 --- a/webclient/src/websocket/commands/session/replaySubmitCode.ts +++ b/webclient/src/websocket/commands/session/replaySubmitCode.ts @@ -1,13 +1,13 @@ import { create } from '@bufbuild/protobuf'; import webClient from '../../WebClient'; -import { Command_ReplaySubmitCodeSchema, Command_ReplaySubmitCode_ext } from 'generated/proto/command_replay_submit_code_pb'; +import { Data } from '@app/types'; export function replaySubmitCode( replayCode: string, onSuccess?: () => void, onError?: (responseCode: number) => void, ): void { - webClient.protobuf.sendSessionCommand(Command_ReplaySubmitCode_ext, create(Command_ReplaySubmitCodeSchema, { replayCode }), { + webClient.protobuf.sendSessionCommand(Data.Command_ReplaySubmitCode_ext, create(Data.Command_ReplaySubmitCodeSchema, { replayCode }), { onSuccess, onError, }); diff --git a/webclient/src/websocket/commands/session/requestPasswordSalt.ts b/webclient/src/websocket/commands/session/requestPasswordSalt.ts index 0e9d40e87..14f8223d4 100644 --- a/webclient/src/websocket/commands/session/requestPasswordSalt.ts +++ b/webclient/src/websocket/commands/session/requestPasswordSalt.ts @@ -1,15 +1,10 @@ -import { RequestPasswordSaltParams } from 'store'; -import { StatusEnum, WebSocketConnectOptions, WebSocketConnectReason } from 'types'; +import { App, Enriched, Data } from '@app/types'; import { create } from '@bufbuild/protobuf'; import { CLIENT_CONFIG } from '../../config'; import webClient from '../../WebClient'; -import { - Command_RequestPasswordSalt_ext, Command_RequestPasswordSaltSchema, -} from 'generated/proto/session_commands_pb'; + import { SessionPersistence } from '../../persistence'; -import { Response_PasswordSalt_ext } from 'generated/proto/response_password_salt_pb'; -import { Response_ResponseCode } from 'generated/proto/response_pb'; import { activate, @@ -19,15 +14,20 @@ import { updateStatus } from './'; -export function requestPasswordSalt(options: WebSocketConnectOptions, password?: string, newPassword?: string): void { - const { userName } = options as RequestPasswordSaltParams; +type PasswordSaltOptions = + | Omit + | Omit + | Omit; + +export function requestPasswordSalt(options: PasswordSaltOptions, password?: string, newPassword?: string): void { + const { userName } = options; const onFailure = () => { switch (options.reason) { - case WebSocketConnectReason.ACTIVATE_ACCOUNT: + case App.WebSocketConnectReason.ACTIVATE_ACCOUNT: SessionPersistence.accountActivationFailed(); break; - case WebSocketConnectReason.PASSWORD_RESET: + case App.WebSocketConnectReason.PASSWORD_RESET: SessionPersistence.resetPasswordFailed(); break; default: @@ -36,19 +36,19 @@ export function requestPasswordSalt(options: WebSocketConnectOptions, password?: disconnect(); }; - webClient.protobuf.sendSessionCommand(Command_RequestPasswordSalt_ext, create(Command_RequestPasswordSaltSchema, { + webClient.protobuf.sendSessionCommand(Data.Command_RequestPasswordSalt_ext, create(Data.Command_RequestPasswordSaltSchema, { ...CLIENT_CONFIG, userName, }), { - responseExt: Response_PasswordSalt_ext, + responseExt: Data.Response_PasswordSalt_ext, onSuccess: (resp) => { const passwordSalt = resp?.passwordSalt; switch (options.reason) { - case WebSocketConnectReason.ACTIVATE_ACCOUNT: + case App.WebSocketConnectReason.ACTIVATE_ACCOUNT: activate(options, password, passwordSalt); break; - case WebSocketConnectReason.PASSWORD_RESET: + case App.WebSocketConnectReason.PASSWORD_RESET: forgotPasswordReset(options, newPassword, passwordSalt); break; default: @@ -56,13 +56,13 @@ export function requestPasswordSalt(options: WebSocketConnectOptions, password?: } }, onResponseCode: { - [Response_ResponseCode.RespRegistrationRequired]: () => { - updateStatus(StatusEnum.DISCONNECTED, 'Login failed: registration required'); + [Data.Response_ResponseCode.RespRegistrationRequired]: () => { + updateStatus(App.StatusEnum.DISCONNECTED, 'Login failed: registration required'); onFailure(); }, }, onError: () => { - updateStatus(StatusEnum.DISCONNECTED, 'Login failed: Unknown Reason'); + updateStatus(App.StatusEnum.DISCONNECTED, 'Login failed: Unknown Reason'); onFailure(); }, }); diff --git a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts index f82dc2b1d..59bd3feb1 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-complex.spec.ts @@ -30,24 +30,11 @@ import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; import { SessionPersistence } from '../../persistence'; import webClient from '../../WebClient'; import * as SessionIndexMocks from './'; -import { StatusEnum, WebSocketConnectReason } from 'types'; +import { App, Enriched, Data } from '@app/types'; import { hashPassword, generateSalt, passwordSaltSupported } from '../../utils'; -import { Response_ResponseCode } from 'generated/proto/response_pb'; -import { - Command_Activate_ext, - Command_ForgotPasswordChallenge_ext, - Command_ForgotPasswordRequest_ext, - Command_ForgotPasswordReset_ext, - Command_Login_ext, - Command_Register_ext, - Command_RequestPasswordSalt_ext, -} from 'generated/proto/session_commands_pb'; -import { Response_ForgotPasswordRequest_ext } from 'generated/proto/response_forgotpasswordrequest_pb'; -import { Response_Login_ext } from 'generated/proto/response_login_pb'; -import { Response_PasswordSalt_ext } from 'generated/proto/response_password_salt_pb'; -import { Response_Register_ext, Response_RegisterSchema } from 'generated/proto/response_register_pb'; + import { create, setExtension } from '@bufbuild/protobuf'; -import { ResponseSchema } from 'generated/proto/response_pb'; + import { connect } from './connect'; import { updateStatus } from './updateStatus'; import { login } from './login'; @@ -63,8 +50,61 @@ const { invokeOnSuccess, invokeResponseCode, invokeOnError } = makeCallbackHelpe 2 ); +const baseTransport = { host: 'h', port: '1' }; +const makeLoginOpts = (overrides: Partial = {}): Enriched.LoginConnectOptions => ({ + ...baseTransport, + userName: 'alice', + reason: App.WebSocketConnectReason.LOGIN, + ...overrides, +}); +const makeRegisterOpts = ( + overrides: Partial = {} +): Enriched.RegisterConnectOptions => ({ + ...baseTransport, + userName: 'alice', + password: 'pw', + email: 'a@b.com', + country: 'US', + realName: 'Al', + reason: App.WebSocketConnectReason.REGISTER, + ...overrides, +}); +const makeActivateOpts = ( + overrides: Partial = {} +): Enriched.ActivateConnectOptions => ({ + ...baseTransport, + userName: 'alice', + token: 'tok', + reason: App.WebSocketConnectReason.ACTIVATE_ACCOUNT, + ...overrides, +}); +const makeForgotRequestOpts = (): Enriched.PasswordResetRequestConnectOptions => ({ + ...baseTransport, + userName: 'alice', + reason: App.WebSocketConnectReason.PASSWORD_RESET_REQUEST, +}); +const makeForgotChallengeOpts = (): Enriched.PasswordResetChallengeConnectOptions => ({ + ...baseTransport, + userName: 'alice', + email: 'a@b.com', + reason: App.WebSocketConnectReason.PASSWORD_RESET_CHALLENGE, +}); +const makeForgotResetOpts = (): Enriched.PasswordResetConnectOptions => ({ + ...baseTransport, + userName: 'alice', + token: 'tok', + newPassword: 'newpw', + reason: App.WebSocketConnectReason.PASSWORD_RESET, +}); +const makeSaltOpts = ( + reason: App.WebSocketConnectReason, + extras: Record = {} +) => ({ ...baseTransport, userName: 'alice', reason, ...extras } as + | Enriched.LoginConnectOptions + | Enriched.ActivateConnectOptions + | Enriched.PasswordResetConnectOptions); + beforeEach(() => { - vi.clearAllMocks(); (hashPassword as Mock).mockReturnValue('hashed_pw'); (generateSalt as Mock).mockReturnValue('randSalt'); (passwordSaltSupported as Mock).mockReturnValue(0); @@ -76,45 +116,46 @@ beforeEach(() => { describe('connect', () => { it('calls updateStatus CONNECTING for LOGIN reason', () => { - connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.LOGIN); - expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...'); + connect({ host: 'h', port: '1', userName: 'u', reason: App.WebSocketConnectReason.LOGIN }); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(App.StatusEnum.CONNECTING, 'Connecting...'); expect(webClient.connect).toHaveBeenCalled(); }); it('calls updateStatus CONNECTING for REGISTER reason', () => { - connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.REGISTER); - expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...'); + connect(makeRegisterOpts({ userName: 'u', realName: 'U' })); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(App.StatusEnum.CONNECTING, 'Connecting...'); }); it('calls updateStatus CONNECTING for ACTIVATE_ACCOUNT reason', () => { - connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.ACTIVATE_ACCOUNT); - expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...'); + connect({ host: 'h', port: '1', userName: 'u', token: 'tok', reason: App.WebSocketConnectReason.ACTIVATE_ACCOUNT }); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(App.StatusEnum.CONNECTING, 'Connecting...'); }); it('calls updateStatus CONNECTING for PASSWORD_RESET_REQUEST reason', () => { - connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.PASSWORD_RESET_REQUEST); - expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...'); + connect({ host: 'h', port: '1', userName: 'u', reason: App.WebSocketConnectReason.PASSWORD_RESET_REQUEST }); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(App.StatusEnum.CONNECTING, 'Connecting...'); }); it('calls updateStatus CONNECTING for PASSWORD_RESET_CHALLENGE reason', () => { - connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.PASSWORD_RESET_CHALLENGE); - expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...'); + connect({ host: 'h', port: '1', userName: 'u', email: 'a@b.com', reason: App.WebSocketConnectReason.PASSWORD_RESET_CHALLENGE }); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(App.StatusEnum.CONNECTING, 'Connecting...'); }); it('calls updateStatus CONNECTING for PASSWORD_RESET reason', () => { - connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.PASSWORD_RESET); - expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...'); + connect({ host: 'h', port: '1', userName: 'u', token: 'tok', newPassword: 'newpw', reason: App.WebSocketConnectReason.PASSWORD_RESET }); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(App.StatusEnum.CONNECTING, 'Connecting...'); }); it('calls testConnect for TEST_CONNECTION reason', () => { - connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.TEST_CONNECTION); + connect({ host: 'h', port: '1', reason: App.WebSocketConnectReason.TEST_CONNECTION }); expect(webClient.testConnect).toHaveBeenCalled(); expect(webClient.connect).not.toHaveBeenCalled(); }); it('calls updateStatus DISCONNECTED for unknown reason', () => { - connect({ host: 'h', port: 1 } as any, 999 as WebSocketConnectReason); - expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, expect.stringContaining('Unknown')); + const bogus = { host: 'h', port: '1', reason: 999 as App.WebSocketConnectReason }; + connect(bogus as Enriched.WebSocketConnectOptions); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(App.StatusEnum.DISCONNECTED, expect.stringContaining('Unknown')); }); }); @@ -124,9 +165,9 @@ describe('connect', () => { describe('updateStatus', () => { it('calls SessionPersistence.updateStatus and webClient.updateStatus', () => { - updateStatus(StatusEnum.CONNECTED, 'OK'); - expect(SessionPersistence.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTED, 'OK'); - expect(webClient.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTED); + updateStatus(App.StatusEnum.CONNECTED, 'OK'); + expect(SessionPersistence.updateStatus).toHaveBeenCalledWith(App.StatusEnum.CONNECTED, 'OK'); + expect(webClient.updateStatus).toHaveBeenCalledWith(App.StatusEnum.CONNECTED); }); }); @@ -136,34 +177,34 @@ describe('updateStatus', () => { describe('login', () => { it('sends Command_Login with plain password when no salt', () => { - login({ userName: 'alice' } as any, 'pw'); + login(makeLoginOpts(), 'pw'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_Login_ext, + Data.Command_Login_ext, expect.objectContaining({ password: 'pw' }), - expect.objectContaining({ responseExt: Response_Login_ext }) + expect.objectContaining({ responseExt: Data.Response_Login_ext }) ); }); it('sends Command_Login with hashedPassword when salt is given', () => { - login({ userName: 'alice' } as any, 'pw', 'salt'); + login(makeLoginOpts(), 'pw', 'salt'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_Login_ext, + Data.Command_Login_ext, expect.objectContaining({ hashedPassword: 'hashed_pw' }), - expect.objectContaining({ responseExt: Response_Login_ext }) + expect.objectContaining({ responseExt: Data.Response_Login_ext }) ); }); it('uses options.hashedPassword if provided', () => { - login({ userName: 'alice', hashedPassword: 'pre_hashed' } as any, 'pw', 'salt'); + login(makeLoginOpts({ hashedPassword: 'pre_hashed' }), 'pw', 'salt'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_Login_ext, + Data.Command_Login_ext, expect.objectContaining({ hashedPassword: 'pre_hashed' }), - expect.objectContaining({ responseExt: Response_Login_ext }) + expect.objectContaining({ responseExt: Data.Response_Login_ext }) ); }); it('onSuccess dispatches buddy/ignore/user and calls listUsers/listRooms', () => { - login({ userName: 'alice' } as any, 'pw'); + login(makeLoginOpts(), 'pw'); const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } }; invokeOnSuccess(loginResp, { responseCode: 0 }); expect(SessionPersistence.updateBuddyList).toHaveBeenCalledWith([]); @@ -172,11 +213,11 @@ describe('login', () => { expect(SessionPersistence.loginSuccessful).toHaveBeenCalled(); expect(SessionIndexMocks.listUsers).toHaveBeenCalled(); expect(SessionIndexMocks.listRooms).toHaveBeenCalled(); - expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.LOGGED_IN, 'Logged in.'); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(App.StatusEnum.LOGGED_IN, 'Logged in.'); }); it('onSuccess does NOT pass plaintext password to loginSuccessful', () => { - login({ userName: 'alice' } as any, 'secret'); + login(makeLoginOpts(), 'secret'); const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } }; invokeOnSuccess(loginResp, { responseCode: 0 }); const calledWith = (SessionPersistence.loginSuccessful as Mock).mock.calls[0][0]; @@ -184,7 +225,7 @@ describe('login', () => { }); it('onSuccess passes hashedPassword to loginSuccessful when salt is used', () => { - login({ userName: 'alice' } as any, 'pw', 'salt'); + login({ host: 'h', port: '1', userName: 'alice', reason: App.WebSocketConnectReason.LOGIN }, 'pw', 'salt'); const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } }; invokeOnSuccess(loginResp, { responseCode: 0 }); const calledWith = (SessionPersistence.loginSuccessful as Mock).mock.calls[0][0]; @@ -192,57 +233,57 @@ describe('login', () => { }); it('onResponseCode RespClientUpdateRequired calls onLoginError', () => { - login({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespClientUpdateRequired); + login(makeLoginOpts(), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespClientUpdateRequired); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); }); it('onResponseCode RespWrongPassword', () => { - login({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespWrongPassword); + login(makeLoginOpts(), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespWrongPassword); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespUsernameInvalid', () => { - login({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespUsernameInvalid); + login(makeLoginOpts(), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespUsernameInvalid); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespWouldOverwriteOldSession', () => { - login({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespWouldOverwriteOldSession); + login(makeLoginOpts(), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespWouldOverwriteOldSession); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespUserIsBanned', () => { - login({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespUserIsBanned); + login(makeLoginOpts(), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespUserIsBanned); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespRegistrationRequired', () => { - login({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespRegistrationRequired); + login(makeLoginOpts(), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespRegistrationRequired); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespClientIdRequired', () => { - login({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespClientIdRequired); + login(makeLoginOpts(), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespClientIdRequired); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespContextError', () => { - login({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespContextError); + login(makeLoginOpts(), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespContextError); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); it('onResponseCode RespAccountNotActivated calls accountAwaitingActivation without password in options', () => { - login({ userName: 'alice', password: 'leaked' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespAccountNotActivated); + login(makeLoginOpts({ password: 'leaked' }), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespAccountNotActivated); expect(SessionPersistence.accountAwaitingActivation).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }) ); @@ -250,7 +291,7 @@ describe('login', () => { }); it('onError calls onLoginError with unknown error message', () => { - login({ userName: 'alice' } as any, 'pw'); + login(makeLoginOpts(), 'pw'); invokeOnError(999); expect(SessionPersistence.loginFailed).toHaveBeenCalled(); }); @@ -262,40 +303,40 @@ describe('login', () => { describe('register', () => { it('sends Command_Register with plain password when no salt', () => { - register({ userName: 'alice', email: 'a@b.com', country: 'US', realName: 'Al' } as any, 'pw'); + register(makeRegisterOpts(), 'pw'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_Register_ext, + Data.Command_Register_ext, expect.objectContaining({ password: 'pw' }), expect.any(Object) ); }); it('uses hashedPassword when salt is provided', () => { - register({ userName: 'alice' } as any, 'pw', 'salt'); + register(makeRegisterOpts(), 'pw', 'salt'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_Register_ext, + Data.Command_Register_ext, expect.objectContaining({ hashedPassword: 'hashed_pw' }), expect.any(Object) ); }); it('RespRegistrationAccepted calls login without salt and registrationSuccess', () => { - register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespRegistrationAccepted); + register(makeRegisterOpts(), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespRegistrationAccepted); expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'pw', undefined); expect(SessionPersistence.registrationSuccess).toHaveBeenCalled(); }); it('RespRegistrationAccepted forwards salt to login', () => { - register({ userName: 'alice' } as any, 'pw', 'mySalt'); - invokeResponseCode(Response_ResponseCode.RespRegistrationAccepted); + register(makeRegisterOpts(), 'pw', 'mySalt'); + invokeResponseCode(Data.Response_ResponseCode.RespRegistrationAccepted); expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'pw', 'mySalt'); expect(SessionPersistence.registrationSuccess).toHaveBeenCalled(); }); it('RespRegistrationAcceptedNeedsActivation calls accountAwaitingActivation without password in options', () => { - register({ userName: 'alice', password: 'leaked' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespRegistrationAcceptedNeedsActivation); + register(makeRegisterOpts({ password: 'leaked' }), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespRegistrationAcceptedNeedsActivation); expect(SessionPersistence.accountAwaitingActivation).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }) ); @@ -303,57 +344,59 @@ describe('register', () => { }); it('RespUserAlreadyExists calls registrationUserNameError', () => { - register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespUserAlreadyExists); + register(makeRegisterOpts(), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespUserAlreadyExists); expect(SessionPersistence.registrationUserNameError).toHaveBeenCalled(); }); it('RespUsernameInvalid calls registrationUserNameError', () => { - register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespUsernameInvalid); + register(makeRegisterOpts(), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespUsernameInvalid); expect(SessionPersistence.registrationUserNameError).toHaveBeenCalled(); }); it('RespPasswordTooShort calls registrationPasswordError', () => { - register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespPasswordTooShort); + register(makeRegisterOpts(), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespPasswordTooShort); expect(SessionPersistence.registrationPasswordError).toHaveBeenCalled(); }); it('RespEmailRequiredToRegister calls registrationRequiresEmail', () => { - register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespEmailRequiredToRegister); + register(makeRegisterOpts(), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespEmailRequiredToRegister); expect(SessionPersistence.registrationRequiresEmail).toHaveBeenCalled(); }); it('RespEmailBlackListed calls registrationEmailError', () => { - register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespEmailBlackListed); + register(makeRegisterOpts(), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespEmailBlackListed); expect(SessionPersistence.registrationEmailError).toHaveBeenCalled(); }); it('RespTooManyRequests calls registrationEmailError', () => { - register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespTooManyRequests); + register(makeRegisterOpts(), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespTooManyRequests); expect(SessionPersistence.registrationEmailError).toHaveBeenCalled(); }); it('RespRegistrationDisabled calls registrationFailed', () => { - register({ userName: 'alice' } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespRegistrationDisabled); + register(makeRegisterOpts(), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespRegistrationDisabled); expect(SessionPersistence.registrationFailed).toHaveBeenCalled(); }); it('RespUserIsBanned calls registrationFailed with deniedReasonStr and deniedEndTime', () => { - register({ userName: 'alice' } as any, 'pw'); - const raw = create(ResponseSchema, { responseCode: Response_ResponseCode.RespUserIsBanned }); - setExtension(raw, Response_Register_ext, create(Response_RegisterSchema, { deniedReasonStr: 'bad user', deniedEndTime: 9999n })); - invokeResponseCode(Response_ResponseCode.RespUserIsBanned, raw); + register(makeRegisterOpts(), 'pw'); + const raw = create(Data.ResponseSchema, { responseCode: Data.Response_ResponseCode.RespUserIsBanned }); + setExtension(raw, Data.Response_Register_ext, create(Data.Response_RegisterSchema, { + deniedReasonStr: 'bad user', deniedEndTime: 9999n, + })); + invokeResponseCode(Data.Response_ResponseCode.RespUserIsBanned, raw); expect(SessionPersistence.registrationFailed).toHaveBeenCalledWith('bad user', 9999); }); it('onError calls registrationFailed', () => { - register({ userName: 'alice' } as any, 'pw'); + register(makeRegisterOpts(), 'pw'); invokeOnError(); expect(SessionPersistence.registrationFailed).toHaveBeenCalled(); }); @@ -365,28 +408,28 @@ describe('register', () => { describe('activate', () => { it('sends Command_Activate with userName and token, not password', () => { - activate({ userName: 'alice', token: 'tok' } as any, 'pw'); + activate(makeActivateOpts(), 'pw'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_Activate_ext, + Data.Command_Activate_ext, expect.objectContaining({ userName: 'alice', token: 'tok' }), expect.any(Object) ); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_Activate_ext, + Data.Command_Activate_ext, expect.not.objectContaining({ password: expect.anything() }), expect.any(Object) ); }); it('RespActivationAccepted calls accountActivationSuccess and forwards password+salt to login', () => { - activate({ userName: 'alice', token: 'tok' } as any, 'pw', 'salt'); - invokeResponseCode(Response_ResponseCode.RespActivationAccepted); + activate(makeActivateOpts(), 'pw', 'salt'); + invokeResponseCode(Data.Response_ResponseCode.RespActivationAccepted); expect(SessionPersistence.accountActivationSuccess).toHaveBeenCalled(); expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'pw', 'salt'); }); it('onError calls accountActivationFailed and disconnect', () => { - activate({ userName: 'alice', token: 'tok' } as any); + activate(makeActivateOpts()); invokeOnError(); expect(SessionPersistence.accountActivationFailed).toHaveBeenCalled(); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); @@ -399,21 +442,21 @@ describe('activate', () => { describe('forgotPasswordChallenge', () => { it('sends Command_ForgotPasswordChallenge', () => { - forgotPasswordChallenge({ userName: 'alice', email: 'a@b.com' } as any); + forgotPasswordChallenge(makeForgotChallengeOpts()); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_ForgotPasswordChallenge_ext, expect.any(Object), expect.any(Object) + Data.Command_ForgotPasswordChallenge_ext, expect.any(Object), expect.any(Object) ); }); it('onSuccess calls resetPassword and disconnect', () => { - forgotPasswordChallenge({ userName: 'alice', email: 'a@b.com' } as any); + forgotPasswordChallenge(makeForgotChallengeOpts()); invokeOnSuccess(); expect(SessionPersistence.resetPassword).toHaveBeenCalled(); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); }); it('onError calls resetPasswordFailed and disconnect', () => { - forgotPasswordChallenge({ userName: 'alice', email: 'a@b.com' } as any); + forgotPasswordChallenge(makeForgotChallengeOpts()); invokeOnError(); expect(SessionPersistence.resetPasswordFailed).toHaveBeenCalled(); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); @@ -426,16 +469,16 @@ describe('forgotPasswordChallenge', () => { describe('forgotPasswordRequest', () => { it('sends Command_ForgotPasswordRequest', () => { - forgotPasswordRequest({ userName: 'alice' } as any); + forgotPasswordRequest(makeForgotRequestOpts()); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_ForgotPasswordRequest_ext, + Data.Command_ForgotPasswordRequest_ext, expect.any(Object), - expect.objectContaining({ responseExt: Response_ForgotPasswordRequest_ext }) + expect.objectContaining({ responseExt: Data.Response_ForgotPasswordRequest_ext }) ); }); it('onSuccess with challengeEmail calls resetPasswordChallenge', () => { - forgotPasswordRequest({ userName: 'alice' } as any); + forgotPasswordRequest(makeForgotRequestOpts()); const resp = { challengeEmail: true }; invokeOnSuccess(resp, { responseCode: 0 }); expect(SessionPersistence.resetPasswordChallenge).toHaveBeenCalled(); @@ -443,7 +486,7 @@ describe('forgotPasswordRequest', () => { }); it('onSuccess without challengeEmail calls resetPassword', () => { - forgotPasswordRequest({ userName: 'alice' } as any); + forgotPasswordRequest(makeForgotRequestOpts()); const resp = { challengeEmail: false }; invokeOnSuccess(resp, { responseCode: 0 }); expect(SessionPersistence.resetPassword).toHaveBeenCalled(); @@ -451,7 +494,7 @@ describe('forgotPasswordRequest', () => { }); it('onError calls resetPasswordFailed and disconnect', () => { - forgotPasswordRequest({ userName: 'alice' } as any); + forgotPasswordRequest(makeForgotRequestOpts()); invokeOnError(); expect(SessionPersistence.resetPasswordFailed).toHaveBeenCalled(); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); @@ -464,32 +507,32 @@ describe('forgotPasswordRequest', () => { describe('forgotPasswordReset', () => { it('sends Command_ForgotPasswordReset with plain newPassword when no salt', () => { - forgotPasswordReset({ userName: 'alice', token: 'tok' } as any, 'newpw'); + forgotPasswordReset(makeForgotResetOpts(), 'newpw'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_ForgotPasswordReset_ext, + Data.Command_ForgotPasswordReset_ext, expect.objectContaining({ newPassword: 'newpw' }), expect.any(Object) ); }); it('sends hashed new password when salt provided', () => { - forgotPasswordReset({ userName: 'alice', token: 'tok' } as any, 'newpw', 'salt'); + forgotPasswordReset(makeForgotResetOpts(), 'newpw', 'salt'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_ForgotPasswordReset_ext, + Data.Command_ForgotPasswordReset_ext, expect.objectContaining({ hashedNewPassword: 'hashed_pw' }), expect.any(Object) ); }); it('onSuccess calls resetPasswordSuccess and disconnect', () => { - forgotPasswordReset({ userName: 'alice', token: 'tok' } as any, 'newpw'); + forgotPasswordReset(makeForgotResetOpts(), 'newpw'); invokeOnSuccess(); expect(SessionPersistence.resetPasswordSuccess).toHaveBeenCalled(); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); }); it('onError calls resetPasswordFailed and disconnect', () => { - forgotPasswordReset({ userName: 'alice', token: 'tok' } as any, 'newpw'); + forgotPasswordReset(makeForgotResetOpts(), 'newpw'); invokeOnError(); expect(SessionPersistence.resetPasswordFailed).toHaveBeenCalled(); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); @@ -502,57 +545,65 @@ describe('forgotPasswordReset', () => { describe('requestPasswordSalt', () => { it('sends Command_RequestPasswordSalt', () => { - requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any, 'pw'); + requestPasswordSalt(makeSaltOpts(App.WebSocketConnectReason.LOGIN), 'pw'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_RequestPasswordSalt_ext, + Data.Command_RequestPasswordSalt_ext, expect.any(Object), - expect.objectContaining({ responseExt: Response_PasswordSalt_ext }) + expect.objectContaining({ responseExt: Data.Response_PasswordSalt_ext }) ); }); it('onSuccess with LOGIN reason forwards password+salt to login', () => { - requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any, 'pw'); + requestPasswordSalt(makeSaltOpts(App.WebSocketConnectReason.LOGIN), 'pw'); const resp = { passwordSalt: 'salt123' }; invokeOnSuccess(resp, { responseCode: 0 }); expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'pw', 'salt123'); }); it('onSuccess with ACTIVATE_ACCOUNT reason forwards password+salt to activate', () => { - requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.ACTIVATE_ACCOUNT } as any, 'pw'); + requestPasswordSalt(makeSaltOpts(App.WebSocketConnectReason.ACTIVATE_ACCOUNT, { token: 'tok' }), 'pw'); const resp = { passwordSalt: 'salt123' }; invokeOnSuccess(resp, { responseCode: 0 }); expect(SessionIndexMocks.activate).toHaveBeenCalledWith(expect.any(Object), 'pw', 'salt123'); }); it('onSuccess with PASSWORD_RESET reason forwards newPassword+salt to forgotPasswordReset', () => { - requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.PASSWORD_RESET } as any, undefined, 'newpw'); + requestPasswordSalt( + makeSaltOpts(App.WebSocketConnectReason.PASSWORD_RESET, { token: 'tok', newPassword: 'newpw' }), + undefined, + 'newpw' + ); const resp = { passwordSalt: 'salt123' }; invokeOnSuccess(resp, { responseCode: 0 }); expect(SessionIndexMocks.forgotPasswordReset).toHaveBeenCalledWith(expect.any(Object), 'newpw', 'salt123'); }); it('onResponseCode RespRegistrationRequired calls updateStatus and disconnect', () => { - requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespRegistrationRequired); - expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, expect.any(String)); + requestPasswordSalt(makeSaltOpts(App.WebSocketConnectReason.LOGIN), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespRegistrationRequired); + expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(App.StatusEnum.DISCONNECTED, expect.any(String)); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); }); it('onResponseCode RespRegistrationRequired with ACTIVATE_ACCOUNT calls accountActivationFailed', () => { - requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.ACTIVATE_ACCOUNT } as any, 'pw'); - invokeResponseCode(Response_ResponseCode.RespRegistrationRequired); + requestPasswordSalt(makeSaltOpts(App.WebSocketConnectReason.ACTIVATE_ACCOUNT, { token: 'tok' }), 'pw'); + invokeResponseCode(Data.Response_ResponseCode.RespRegistrationRequired); expect(SessionPersistence.accountActivationFailed).toHaveBeenCalled(); }); it('onError calls updateStatus DISCONNECTED and disconnect', () => { - requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any, 'pw'); + requestPasswordSalt(makeSaltOpts(App.WebSocketConnectReason.LOGIN), 'pw'); invokeOnError(); expect(SessionIndexMocks.updateStatus).toHaveBeenCalled(); expect(SessionIndexMocks.disconnect).toHaveBeenCalled(); }); it('onError with PASSWORD_RESET reason calls resetPasswordFailed', () => { - requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.PASSWORD_RESET } as any, undefined, 'newpw'); + requestPasswordSalt( + makeSaltOpts(App.WebSocketConnectReason.PASSWORD_RESET, { token: 'tok', newPassword: 'newpw' }), + undefined, + 'newpw' + ); invokeOnError(); expect(SessionPersistence.resetPasswordFailed).toHaveBeenCalled(); }); diff --git a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts index b135e8d15..483dad436 100644 --- a/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts +++ b/webclient/src/websocket/commands/session/sessionCommands-simple.spec.ts @@ -22,7 +22,7 @@ vi.mock('../../utils', async () => { vi.mock('./', async () => { const actual = await vi.importActual('./'); const { makeSessionBarrelMock } = await import('../../__mocks__/sessionCommandMocks'); - return { ...(actual as any), ...makeSessionBarrelMock() }; + return { ...(actual as Record), ...makeSessionBarrelMock() }; }); import { Mock } from 'vitest'; @@ -31,38 +31,7 @@ import { SessionPersistence } from '../../persistence'; import { RoomPersistence } from '../../persistence'; import webClient from '../../WebClient'; import { hashPassword, generateSalt, passwordSaltSupported } from '../../utils'; -import { - Command_AccountEdit_ext, - Command_AccountImage_ext, - Command_AccountPassword_ext, - Command_AddToList_ext, - Command_GetGamesOfUser_ext, - Command_GetUserInfo_ext, - Command_JoinRoom_ext, - Command_ListRooms_ext, - Command_ListUsers_ext, - Command_Message_ext, - Command_Ping_ext, - Command_RemoveFromList_ext, -} from 'generated/proto/session_commands_pb'; -import { Command_DeckDel_ext } from 'generated/proto/command_deck_del_pb'; -import { Command_DeckDelDir_ext } from 'generated/proto/command_deck_del_dir_pb'; -import { Command_DeckList_ext } from 'generated/proto/command_deck_list_pb'; -import { Command_DeckNewDir_ext } from 'generated/proto/command_deck_new_dir_pb'; -import { Command_DeckUpload_ext } from 'generated/proto/command_deck_upload_pb'; -import { Command_ReplayDeleteMatch_ext } from 'generated/proto/command_replay_delete_match_pb'; -import { Command_ReplayGetCode_ext } from 'generated/proto/command_replay_get_code_pb'; -import { Command_ReplayList_ext } from 'generated/proto/command_replay_list_pb'; -import { Command_ReplayModifyMatch_ext } from 'generated/proto/command_replay_modify_match_pb'; -import { Command_ReplaySubmitCode_ext } from 'generated/proto/command_replay_submit_code_pb'; -import { Response_DeckList_ext } from 'generated/proto/response_deck_list_pb'; -import { Response_DeckUpload_ext } from 'generated/proto/response_deck_upload_pb'; -import { Response_GetGamesOfUser_ext } from 'generated/proto/response_get_games_of_user_pb'; -import { Response_GetUserInfo_ext } from 'generated/proto/response_get_user_info_pb'; -import { Response_JoinRoom_ext } from 'generated/proto/response_join_room_pb'; -import { Response_ListUsers_ext } from 'generated/proto/response_list_users_pb'; -import { Response_ReplayGetCode_ext } from 'generated/proto/response_replay_get_code_pb'; -import { Response_ReplayList_ext } from 'generated/proto/response_replay_list_pb'; + import { accountEdit } from './accountEdit'; import { accountImage } from './accountImage'; import { accountPassword } from './accountPassword'; @@ -86,6 +55,7 @@ import { addToList, addToBuddyList, addToIgnoreList } from './addToList'; import { removeFromList, removeFromBuddyList, removeFromIgnoreList } from './removeFromList'; import { replayGetCode } from './replayGetCode'; import { replaySubmitCode } from './replaySubmitCode'; +import { Data } from '@app/types'; const { invokeOnSuccess, invokeCallback } = makeCallbackHelpers( webClient.protobuf.sendSessionCommand as Mock, @@ -93,7 +63,6 @@ const { invokeOnSuccess, invokeCallback } = makeCallbackHelpers( ); beforeEach(() => { - vi.clearAllMocks(); (hashPassword as Mock).mockReturnValue('hashed_pw'); (generateSalt as Mock).mockReturnValue('randSalt'); (passwordSaltSupported as Mock).mockReturnValue(0); @@ -102,12 +71,10 @@ beforeEach(() => { // ---------------------------------------------------------------- describe('accountEdit', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_AccountEdit with correct params', () => { accountEdit('pw', 'Alice', 'a@b.com', 'US'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_AccountEdit_ext, + Data.Command_AccountEdit_ext, expect.objectContaining({ passwordCheck: 'pw', realName: 'Alice', email: 'a@b.com', country: 'US' }), expect.any(Object) ); @@ -121,13 +88,11 @@ describe('accountEdit', () => { }); describe('accountImage', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_AccountImage', () => { const img = new Uint8Array([1, 2]); accountImage(img); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_AccountImage_ext, expect.objectContaining({ image: img }), expect.any(Object) + Data.Command_AccountImage_ext, expect.objectContaining({ image: img }), expect.any(Object) ); }); @@ -140,12 +105,10 @@ describe('accountImage', () => { }); describe('accountPassword', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_AccountPassword', () => { accountPassword('old', 'new', 'hashed'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_AccountPassword_ext, + Data.Command_AccountPassword_ext, expect.objectContaining({ oldPassword: 'old', newPassword: 'new', hashedNewPassword: 'hashed' }), expect.any(Object) ); @@ -159,12 +122,10 @@ describe('accountPassword', () => { }); describe('deckDel', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_DeckDel', () => { deckDel(42); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_DeckDel_ext, + Data.Command_DeckDel_ext, expect.objectContaining({ deckId: 42 }), expect.any(Object) ); @@ -178,12 +139,10 @@ describe('deckDel', () => { }); describe('deckDelDir', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_DeckDelDir', () => { deckDelDir('/path'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_DeckDelDir_ext, expect.objectContaining({ path: '/path' }), expect.any(Object) + Data.Command_DeckDelDir_ext, expect.objectContaining({ path: '/path' }), expect.any(Object) ); }); @@ -195,14 +154,12 @@ describe('deckDelDir', () => { }); describe('deckList', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_DeckList', () => { deckList(); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_DeckList_ext, + Data.Command_DeckList_ext, expect.any(Object), - expect.objectContaining({ responseExt: Response_DeckList_ext }) + expect.objectContaining({ responseExt: Data.Response_DeckList_ext }) ); }); @@ -215,12 +172,10 @@ describe('deckList', () => { }); describe('deckNewDir', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_DeckNewDir', () => { deckNewDir('/path', 'dir'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_DeckNewDir_ext, expect.objectContaining({ path: '/path', dirName: 'dir' }), expect.any(Object) + Data.Command_DeckNewDir_ext, expect.objectContaining({ path: '/path', dirName: 'dir' }), expect.any(Object) ); }); @@ -232,14 +187,12 @@ describe('deckNewDir', () => { }); describe('deckUpload', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_DeckUpload', () => { deckUpload('/path', 1, 'content'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_DeckUpload_ext, + Data.Command_DeckUpload_ext, expect.objectContaining({ path: '/path', deckId: 1, deckList: 'content' }), - expect.objectContaining({ responseExt: Response_DeckUpload_ext }) + expect.objectContaining({ responseExt: Data.Response_DeckUpload_ext }) ); }); @@ -252,8 +205,6 @@ describe('deckUpload', () => { }); describe('disconnect', () => { - beforeEach(() => vi.clearAllMocks()); - it('calls webClient.disconnect', () => { disconnect(); expect(webClient.disconnect).toHaveBeenCalled(); @@ -261,14 +212,12 @@ describe('disconnect', () => { }); describe('getGamesOfUser', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_GetGamesOfUser', () => { getGamesOfUser('alice'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_GetGamesOfUser_ext, + Data.Command_GetGamesOfUser_ext, expect.any(Object), - expect.objectContaining({ responseExt: Response_GetGamesOfUser_ext }) + expect.objectContaining({ responseExt: Data.Response_GetGamesOfUser_ext }) ); }); @@ -281,14 +230,12 @@ describe('getGamesOfUser', () => { }); describe('getUserInfo', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_GetUserInfo', () => { getUserInfo('alice'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_GetUserInfo_ext, + Data.Command_GetUserInfo_ext, expect.any(Object), - expect.objectContaining({ responseExt: Response_GetUserInfo_ext }) + expect.objectContaining({ responseExt: Data.Response_GetUserInfo_ext }) ); }); @@ -301,14 +248,12 @@ describe('getUserInfo', () => { }); describe('joinRoom', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_JoinRoom', () => { joinRoom(5); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_JoinRoom_ext, + Data.Command_JoinRoom_ext, expect.any(Object), - expect.objectContaining({ responseExt: Response_JoinRoom_ext }) + expect.objectContaining({ responseExt: Data.Response_JoinRoom_ext }) ); }); @@ -321,23 +266,19 @@ describe('joinRoom', () => { }); describe('listRooms (command)', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_ListRooms', () => { listRooms(); - expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith(Command_ListRooms_ext, expect.any(Object)); + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith(Data.Command_ListRooms_ext, expect.any(Object)); }); }); describe('listUsers', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_ListUsers', () => { listUsers(); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_ListUsers_ext, + Data.Command_ListUsers_ext, expect.any(Object), - expect.objectContaining({ responseExt: Response_ListUsers_ext }) + expect.objectContaining({ responseExt: Data.Response_ListUsers_ext }) ); }); @@ -350,24 +291,20 @@ describe('listUsers', () => { }); describe('message', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_Message', () => { message('bob', 'hi'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_Message_ext, expect.objectContaining({ userName: 'bob', message: 'hi' }) + Data.Command_Message_ext, expect.objectContaining({ userName: 'bob', message: 'hi' }) ); }); }); describe('ping', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_Ping', () => { const pingReceived = vi.fn(); ping(pingReceived); - expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith(Command_Ping_ext, expect.any(Object), expect.any(Object)); + expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith(Data.Command_Ping_ext, expect.any(Object), expect.any(Object)); }); it('calls pingReceived via onResponse', () => { @@ -379,12 +316,10 @@ describe('ping', () => { }); describe('replayDeleteMatch', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_ReplayDeleteMatch', () => { replayDeleteMatch(7); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_ReplayDeleteMatch_ext, + Data.Command_ReplayDeleteMatch_ext, expect.objectContaining({ gameId: 7 }), expect.any(Object) ); @@ -398,14 +333,12 @@ describe('replayDeleteMatch', () => { }); describe('replayList', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_ReplayList', () => { replayList(); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_ReplayList_ext, + Data.Command_ReplayList_ext, expect.any(Object), - expect.objectContaining({ responseExt: Response_ReplayList_ext }) + expect.objectContaining({ responseExt: Data.Response_ReplayList_ext }) ); }); @@ -418,12 +351,10 @@ describe('replayList', () => { }); describe('replayModifyMatch', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_ReplayModifyMatch', () => { replayModifyMatch(7, true); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_ReplayModifyMatch_ext, expect.objectContaining({ gameId: 7, doNotHide: true }), expect.any(Object) + Data.Command_ReplayModifyMatch_ext, expect.objectContaining({ gameId: 7, doNotHide: true }), expect.any(Object) ); }); @@ -435,12 +366,10 @@ describe('replayModifyMatch', () => { }); describe('addToList / addToBuddyList / addToIgnoreList', () => { - beforeEach(() => vi.clearAllMocks()); - it('addToBuddyList sends Command_AddToList with list=buddy', () => { addToBuddyList('alice'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_AddToList_ext, + Data.Command_AddToList_ext, expect.objectContaining({ list: 'buddy' }), expect.any(Object) ); @@ -449,7 +378,7 @@ describe('addToList / addToBuddyList / addToIgnoreList', () => { it('addToIgnoreList sends Command_AddToList with list=ignore', () => { addToIgnoreList('bob'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_AddToList_ext, + Data.Command_AddToList_ext, expect.objectContaining({ list: 'ignore' }), expect.any(Object) ); @@ -463,12 +392,10 @@ describe('addToList / addToBuddyList / addToIgnoreList', () => { }); describe('removeFromList / removeFromBuddyList / removeFromIgnoreList', () => { - beforeEach(() => vi.clearAllMocks()); - it('removeFromBuddyList sends Command_RemoveFromList with list=buddy', () => { removeFromBuddyList('alice'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_RemoveFromList_ext, + Data.Command_RemoveFromList_ext, expect.objectContaining({ list: 'buddy' }), expect.any(Object) ); @@ -477,7 +404,7 @@ describe('removeFromList / removeFromBuddyList / removeFromIgnoreList', () => { it('removeFromIgnoreList sends Command_RemoveFromList with list=ignore', () => { removeFromIgnoreList('bob'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_RemoveFromList_ext, + Data.Command_RemoveFromList_ext, expect.objectContaining({ list: 'ignore' }), expect.any(Object) ); @@ -491,14 +418,12 @@ describe('removeFromList / removeFromBuddyList / removeFromIgnoreList', () => { }); describe('replayGetCode', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_ReplayGetCode with gameId and responseExt', () => { replayGetCode(42, vi.fn()); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_ReplayGetCode_ext, + Data.Command_ReplayGetCode_ext, expect.any(Object), - expect.objectContaining({ responseExt: Response_ReplayGetCode_ext }) + expect.objectContaining({ responseExt: Data.Response_ReplayGetCode_ext }) ); }); @@ -511,12 +436,10 @@ describe('replayGetCode', () => { }); describe('replaySubmitCode', () => { - beforeEach(() => vi.clearAllMocks()); - it('sends Command_ReplaySubmitCode with replayCode', () => { replaySubmitCode('42-abc123'); expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith( - Command_ReplaySubmitCode_ext, expect.objectContaining({ replayCode: '42-abc123' }), expect.any(Object) + Data.Command_ReplaySubmitCode_ext, expect.objectContaining({ replayCode: '42-abc123' }), expect.any(Object) ); }); diff --git a/webclient/src/websocket/commands/session/updateStatus.ts b/webclient/src/websocket/commands/session/updateStatus.ts index eddd774f8..504156f98 100644 --- a/webclient/src/websocket/commands/session/updateStatus.ts +++ b/webclient/src/websocket/commands/session/updateStatus.ts @@ -1,8 +1,8 @@ -import { StatusEnum } from 'types'; +import { App } from '@app/types'; import webClient from '../../WebClient'; import { SessionPersistence } from '../../persistence'; -export function updateStatus(status: StatusEnum, description: string): void { +export function updateStatus(status: App.StatusEnum, description: string): void { SessionPersistence.updateStatus(status, description); webClient.updateStatus(status); diff --git a/webclient/src/websocket/events/common/index.ts b/webclient/src/websocket/events/common/index.ts index c6832988d..cda3024f3 100644 --- a/webclient/src/websocket/events/common/index.ts +++ b/webclient/src/websocket/events/common/index.ts @@ -1,3 +1,3 @@ -import { SessionExtensionRegistry } from '../../services/protobuf-types'; +import type { SessionExtensionRegistry } from '../session'; export const CommonEvents: SessionExtensionRegistry = []; diff --git a/webclient/src/websocket/events/game/attachCard.ts b/webclient/src/websocket/events/game/attachCard.ts index 14c6e0354..389759f9f 100644 --- a/webclient/src/websocket/events/game/attachCard.ts +++ b/webclient/src/websocket/events/game/attachCard.ts @@ -1,7 +1,6 @@ -import type { Event_AttachCard } from 'generated/proto/event_attach_card_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function attachCard(data: Event_AttachCard, meta: GameEventMeta): void { +export function attachCard(data: Data.Event_AttachCard, meta: Enriched.GameEventMeta): void { GamePersistence.cardAttached(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/changeZoneProperties.ts b/webclient/src/websocket/events/game/changeZoneProperties.ts index 8454cb254..8bd066a8b 100644 --- a/webclient/src/websocket/events/game/changeZoneProperties.ts +++ b/webclient/src/websocket/events/game/changeZoneProperties.ts @@ -1,7 +1,6 @@ -import type { Event_ChangeZoneProperties } from 'generated/proto/event_change_zone_properties_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function changeZoneProperties(data: Event_ChangeZoneProperties, meta: GameEventMeta): void { +export function changeZoneProperties(data: Data.Event_ChangeZoneProperties, meta: Enriched.GameEventMeta): void { GamePersistence.zonePropertiesChanged(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/createArrow.ts b/webclient/src/websocket/events/game/createArrow.ts index 4685621d0..66933d636 100644 --- a/webclient/src/websocket/events/game/createArrow.ts +++ b/webclient/src/websocket/events/game/createArrow.ts @@ -1,7 +1,6 @@ -import type { Event_CreateArrow } from 'generated/proto/event_create_arrow_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function createArrow(data: Event_CreateArrow, meta: GameEventMeta): void { +export function createArrow(data: Data.Event_CreateArrow, meta: Enriched.GameEventMeta): void { GamePersistence.arrowCreated(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/createCounter.ts b/webclient/src/websocket/events/game/createCounter.ts index f7237ac06..500523179 100644 --- a/webclient/src/websocket/events/game/createCounter.ts +++ b/webclient/src/websocket/events/game/createCounter.ts @@ -1,7 +1,6 @@ -import type { Event_CreateCounter } from 'generated/proto/event_create_counter_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function createCounter(data: Event_CreateCounter, meta: GameEventMeta): void { +export function createCounter(data: Data.Event_CreateCounter, meta: Enriched.GameEventMeta): void { GamePersistence.counterCreated(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/createToken.ts b/webclient/src/websocket/events/game/createToken.ts index f4935661b..1dce664ff 100644 --- a/webclient/src/websocket/events/game/createToken.ts +++ b/webclient/src/websocket/events/game/createToken.ts @@ -1,7 +1,6 @@ -import type { Event_CreateToken } from 'generated/proto/event_create_token_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function createToken(data: Event_CreateToken, meta: GameEventMeta): void { +export function createToken(data: Data.Event_CreateToken, meta: Enriched.GameEventMeta): void { GamePersistence.tokenCreated(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/delCounter.ts b/webclient/src/websocket/events/game/delCounter.ts index 533425d03..f550f3bdd 100644 --- a/webclient/src/websocket/events/game/delCounter.ts +++ b/webclient/src/websocket/events/game/delCounter.ts @@ -1,7 +1,6 @@ -import type { Event_DelCounter } from 'generated/proto/event_del_counter_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function delCounter(data: Event_DelCounter, meta: GameEventMeta): void { +export function delCounter(data: Data.Event_DelCounter, meta: Enriched.GameEventMeta): void { GamePersistence.counterDeleted(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/deleteArrow.ts b/webclient/src/websocket/events/game/deleteArrow.ts index 5461fb2f1..a58ad59d3 100644 --- a/webclient/src/websocket/events/game/deleteArrow.ts +++ b/webclient/src/websocket/events/game/deleteArrow.ts @@ -1,7 +1,6 @@ -import type { Event_DeleteArrow } from 'generated/proto/event_delete_arrow_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function deleteArrow(data: Event_DeleteArrow, meta: GameEventMeta): void { +export function deleteArrow(data: Data.Event_DeleteArrow, meta: Enriched.GameEventMeta): void { GamePersistence.arrowDeleted(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/destroyCard.ts b/webclient/src/websocket/events/game/destroyCard.ts index 1c7c69de9..07bf5854a 100644 --- a/webclient/src/websocket/events/game/destroyCard.ts +++ b/webclient/src/websocket/events/game/destroyCard.ts @@ -1,7 +1,6 @@ -import type { Event_DestroyCard } from 'generated/proto/event_destroy_card_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function destroyCard(data: Event_DestroyCard, meta: GameEventMeta): void { +export function destroyCard(data: Data.Event_DestroyCard, meta: Enriched.GameEventMeta): void { GamePersistence.cardDestroyed(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/drawCards.ts b/webclient/src/websocket/events/game/drawCards.ts index e4c43f9f1..921716ba4 100644 --- a/webclient/src/websocket/events/game/drawCards.ts +++ b/webclient/src/websocket/events/game/drawCards.ts @@ -1,7 +1,6 @@ -import type { Event_DrawCards } from 'generated/proto/event_draw_cards_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function drawCards(data: Event_DrawCards, meta: GameEventMeta): void { +export function drawCards(data: Data.Event_DrawCards, meta: Enriched.GameEventMeta): void { GamePersistence.cardsDrawn(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/dumpZone.ts b/webclient/src/websocket/events/game/dumpZone.ts index 9ee513240..daf4a0fc8 100644 --- a/webclient/src/websocket/events/game/dumpZone.ts +++ b/webclient/src/websocket/events/game/dumpZone.ts @@ -1,7 +1,6 @@ -import type { Event_DumpZone } from 'generated/proto/event_dump_zone_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function dumpZone(data: Event_DumpZone, meta: GameEventMeta): void { +export function dumpZone(data: Data.Event_DumpZone, meta: Enriched.GameEventMeta): void { GamePersistence.zoneDumped(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/flipCard.ts b/webclient/src/websocket/events/game/flipCard.ts index 51ac479b3..cccf13906 100644 --- a/webclient/src/websocket/events/game/flipCard.ts +++ b/webclient/src/websocket/events/game/flipCard.ts @@ -1,7 +1,6 @@ -import type { Event_FlipCard } from 'generated/proto/event_flip_card_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function flipCard(data: Event_FlipCard, meta: GameEventMeta): void { +export function flipCard(data: Data.Event_FlipCard, meta: Enriched.GameEventMeta): void { GamePersistence.cardFlipped(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/gameClosed.ts b/webclient/src/websocket/events/game/gameClosed.ts index 9ebdaba5c..d604423df 100644 --- a/webclient/src/websocket/events/game/gameClosed.ts +++ b/webclient/src/websocket/events/game/gameClosed.ts @@ -1,6 +1,6 @@ -import { GameEventMeta } from 'types'; +import { Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function gameClosed(_data: {}, meta: GameEventMeta): void { +export function gameClosed(_data: {}, meta: Enriched.GameEventMeta): void { GamePersistence.gameClosed(meta.gameId); } diff --git a/webclient/src/websocket/events/game/gameEvents.spec.ts b/webclient/src/websocket/events/game/gameEvents.spec.ts index f1fd3571c..bfc94eeac 100644 --- a/webclient/src/websocket/events/game/gameEvents.spec.ts +++ b/webclient/src/websocket/events/game/gameEvents.spec.ts @@ -32,7 +32,10 @@ vi.mock('../../persistence', () => ({ }, })); +import { create } from '@bufbuild/protobuf'; +import { Data } from '@app/types'; import { GamePersistence } from '../../persistence'; + import { attachCard } from './attachCard'; import { changeZoneProperties } from './changeZoneProperties'; import { createArrow } from './createArrow'; @@ -63,14 +66,12 @@ import { setCardCounter } from './setCardCounter'; import { setCounter } from './setCounter'; import { shuffle } from './shuffle'; -beforeEach(() => vi.clearAllMocks()); - const meta = { gameId: 5, playerId: 2, context: null, secondsElapsed: 0, forcedByJudge: 0 }; describe('joinGame event', () => { it('delegates to GamePersistence.playerJoined with gameId from meta', () => { - const playerProperties = { playerId: 1 }; - const data = { playerProperties } as any; + const playerProperties = create(Data.ServerInfo_PlayerPropertiesSchema, { playerId: 1 }); + const data = { playerProperties }; joinGame(data, meta); expect(GamePersistence.playerJoined).toHaveBeenCalledWith(5, playerProperties); }); @@ -107,7 +108,7 @@ describe('kicked event', () => { describe('gameStateChanged event', () => { it('delegates to GamePersistence.gameStateChanged with gameId and full data', () => { - const data = { playerList: [] } as any; + const data = create(Data.Event_GameStateChangedSchema, { playerList: [] }); gameStateChanged(data, meta); expect(GamePersistence.gameStateChanged).toHaveBeenCalledWith(5, data); }); @@ -115,8 +116,8 @@ describe('gameStateChanged event', () => { describe('playerPropertiesChanged event', () => { it('delegates to GamePersistence.playerPropertiesChanged with gameId, playerId, properties', () => { - const playerProperties = { playerId: 2 } as any; - const data = { playerProperties } as any; + const playerProperties = create(Data.ServerInfo_PlayerPropertiesSchema, { playerId: 2 }); + const data = { playerProperties }; playerPropertiesChanged(data, meta); expect(GamePersistence.playerPropertiesChanged).toHaveBeenCalledWith(5, 2, playerProperties); }); @@ -124,7 +125,7 @@ describe('playerPropertiesChanged event', () => { describe('gameSay event', () => { it('delegates to GamePersistence.gameSay with gameId, playerId, message', () => { - const data = { message: 'gg' } as any; + const data = create(Data.Event_GameSaySchema, { message: 'gg' }); gameSay(data, meta); expect(GamePersistence.gameSay).toHaveBeenCalledWith(5, 2, 'gg'); }); @@ -132,7 +133,7 @@ describe('gameSay event', () => { describe('moveCard event', () => { it('delegates to GamePersistence.cardMoved with gameId, playerId and data', () => { - const data = { cardId: 3 } as any; + const data = create(Data.Event_MoveCardSchema, { cardId: 3 }); moveCard(data, meta); expect(GamePersistence.cardMoved).toHaveBeenCalledWith(5, 2, data); }); @@ -140,7 +141,7 @@ describe('moveCard event', () => { describe('flipCard event', () => { it('delegates to GamePersistence.cardFlipped with gameId, playerId and data', () => { - const data = { cardId: 3 } as any; + const data = create(Data.Event_FlipCardSchema, { cardId: 3 }); flipCard(data, meta); expect(GamePersistence.cardFlipped).toHaveBeenCalledWith(5, 2, data); }); @@ -148,7 +149,7 @@ describe('flipCard event', () => { describe('destroyCard event', () => { it('delegates to GamePersistence.cardDestroyed with gameId, playerId and data', () => { - const data = { cardId: 3 } as any; + const data = create(Data.Event_DestroyCardSchema, { cardId: 3 }); destroyCard(data, meta); expect(GamePersistence.cardDestroyed).toHaveBeenCalledWith(5, 2, data); }); @@ -156,7 +157,7 @@ describe('destroyCard event', () => { describe('attachCard event', () => { it('delegates to GamePersistence.cardAttached with gameId, playerId and data', () => { - const data = { cardId: 3 } as any; + const data = create(Data.Event_AttachCardSchema, { cardId: 3 }); attachCard(data, meta); expect(GamePersistence.cardAttached).toHaveBeenCalledWith(5, 2, data); }); @@ -164,7 +165,7 @@ describe('attachCard event', () => { describe('createToken event', () => { it('delegates to GamePersistence.tokenCreated with gameId, playerId and data', () => { - const data = { cardId: 3 } as any; + const data = create(Data.Event_CreateTokenSchema, { cardId: 3 }); createToken(data, meta); expect(GamePersistence.tokenCreated).toHaveBeenCalledWith(5, 2, data); }); @@ -172,7 +173,7 @@ describe('createToken event', () => { describe('setCardAttr event', () => { it('delegates to GamePersistence.cardAttrChanged with gameId, playerId and data', () => { - const data = { cardId: 3 } as any; + const data = create(Data.Event_SetCardAttrSchema, { cardId: 3 }); setCardAttr(data, meta); expect(GamePersistence.cardAttrChanged).toHaveBeenCalledWith(5, 2, data); }); @@ -180,7 +181,7 @@ describe('setCardAttr event', () => { describe('setCardCounter event', () => { it('delegates to GamePersistence.cardCounterChanged with gameId, playerId and data', () => { - const data = { cardId: 3 } as any; + const data = create(Data.Event_SetCardCounterSchema, { cardId: 3 }); setCardCounter(data, meta); expect(GamePersistence.cardCounterChanged).toHaveBeenCalledWith(5, 2, data); }); @@ -188,7 +189,7 @@ describe('setCardCounter event', () => { describe('createArrow event', () => { it('delegates to GamePersistence.arrowCreated with gameId, playerId and data', () => { - const data = { arrowInfo: {} } as any; + const data = create(Data.Event_CreateArrowSchema, {}); createArrow(data, meta); expect(GamePersistence.arrowCreated).toHaveBeenCalledWith(5, 2, data); }); @@ -196,7 +197,7 @@ describe('createArrow event', () => { describe('deleteArrow event', () => { it('delegates to GamePersistence.arrowDeleted with gameId, playerId and data', () => { - const data = { arrowId: 9 } as any; + const data = create(Data.Event_DeleteArrowSchema, { arrowId: 9 }); deleteArrow(data, meta); expect(GamePersistence.arrowDeleted).toHaveBeenCalledWith(5, 2, data); }); @@ -204,7 +205,7 @@ describe('deleteArrow event', () => { describe('createCounter event', () => { it('delegates to GamePersistence.counterCreated with gameId, playerId and data', () => { - const data = { counterInfo: {} } as any; + const data = create(Data.Event_CreateCounterSchema, {}); createCounter(data, meta); expect(GamePersistence.counterCreated).toHaveBeenCalledWith(5, 2, data); }); @@ -212,7 +213,7 @@ describe('createCounter event', () => { describe('setCounter event', () => { it('delegates to GamePersistence.counterSet with gameId, playerId and data', () => { - const data = { counterId: 1, value: 20 } as any; + const data = create(Data.Event_SetCounterSchema, { counterId: 1, value: 20 }); setCounter(data, meta); expect(GamePersistence.counterSet).toHaveBeenCalledWith(5, 2, data); }); @@ -220,7 +221,7 @@ describe('setCounter event', () => { describe('delCounter event', () => { it('delegates to GamePersistence.counterDeleted with gameId, playerId and data', () => { - const data = { counterId: 1 } as any; + const data = create(Data.Event_DelCounterSchema, { counterId: 1 }); delCounter(data, meta); expect(GamePersistence.counterDeleted).toHaveBeenCalledWith(5, 2, data); }); @@ -228,7 +229,7 @@ describe('delCounter event', () => { describe('drawCards event', () => { it('delegates to GamePersistence.cardsDrawn with gameId, playerId and data', () => { - const data = { number: 2, cards: [] } as any; + const data = create(Data.Event_DrawCardsSchema, { number: 2, cards: [] }); drawCards(data, meta); expect(GamePersistence.cardsDrawn).toHaveBeenCalledWith(5, 2, data); }); @@ -236,7 +237,7 @@ describe('drawCards event', () => { describe('revealCards event', () => { it('delegates to GamePersistence.cardsRevealed with gameId, playerId and data', () => { - const data = { zoneName: 'hand', cards: [] } as any; + const data = create(Data.Event_RevealCardsSchema, { zoneName: 'hand', cards: [] }); revealCards(data, meta); expect(GamePersistence.cardsRevealed).toHaveBeenCalledWith(5, 2, data); }); @@ -244,7 +245,7 @@ describe('revealCards event', () => { describe('shuffle event', () => { it('delegates to GamePersistence.zoneShuffled with gameId, playerId and data', () => { - const data = { zoneName: 'deck' } as any; + const data = create(Data.Event_ShuffleSchema, { zoneName: 'deck' }); shuffle(data, meta); expect(GamePersistence.zoneShuffled).toHaveBeenCalledWith(5, 2, data); }); @@ -252,7 +253,7 @@ describe('shuffle event', () => { describe('rollDie event', () => { it('delegates to GamePersistence.dieRolled with gameId, playerId and data', () => { - const data = { die: 6, result: 4 } as any; + const data = create(Data.Event_RollDieSchema, { die: 6, result: 4 }); rollDie(data, meta); expect(GamePersistence.dieRolled).toHaveBeenCalledWith(5, 2, data); }); @@ -260,7 +261,7 @@ describe('rollDie event', () => { describe('setActivePlayer event', () => { it('delegates to GamePersistence.activePlayerSet with gameId and activePlayerId', () => { - const data = { activePlayerId: 3 } as any; + const data = create(Data.Event_SetActivePlayerSchema, { activePlayerId: 3 }); setActivePlayer(data, meta); expect(GamePersistence.activePlayerSet).toHaveBeenCalledWith(5, 3); }); @@ -268,7 +269,7 @@ describe('setActivePlayer event', () => { describe('setActivePhase event', () => { it('delegates to GamePersistence.activePhaseSet with gameId and phase', () => { - const data = { phase: 4 } as any; + const data = create(Data.Event_SetActivePhaseSchema, { phase: 4 }); setActivePhase(data, meta); expect(GamePersistence.activePhaseSet).toHaveBeenCalledWith(5, 4); }); @@ -276,7 +277,7 @@ describe('setActivePhase event', () => { describe('reverseTurn event', () => { it('delegates to GamePersistence.turnReversed with gameId and reversed', () => { - const data = { reversed: true } as any; + const data = create(Data.Event_ReverseTurnSchema, { reversed: true }); reverseTurn(data, meta); expect(GamePersistence.turnReversed).toHaveBeenCalledWith(5, true); }); @@ -284,7 +285,7 @@ describe('reverseTurn event', () => { describe('dumpZone event', () => { it('delegates to GamePersistence.zoneDumped with gameId, playerId and data', () => { - const data = { zoneName: 'hand' } as any; + const data = create(Data.Event_DumpZoneSchema, { zoneName: 'hand' }); dumpZone(data, meta); expect(GamePersistence.zoneDumped).toHaveBeenCalledWith(5, 2, data); }); @@ -292,7 +293,7 @@ describe('dumpZone event', () => { describe('changeZoneProperties event', () => { it('delegates to GamePersistence.zonePropertiesChanged with gameId, playerId and data', () => { - const data = { zoneName: 'hand', alwaysRevealTopCard: true } as any; + const data = create(Data.Event_ChangeZonePropertiesSchema, { zoneName: 'hand', alwaysRevealTopCard: true }); changeZoneProperties(data, meta); expect(GamePersistence.zonePropertiesChanged).toHaveBeenCalledWith(5, 2, data); }); diff --git a/webclient/src/websocket/events/game/gameHostChanged.ts b/webclient/src/websocket/events/game/gameHostChanged.ts index 174b2fca1..212d182b0 100644 --- a/webclient/src/websocket/events/game/gameHostChanged.ts +++ b/webclient/src/websocket/events/game/gameHostChanged.ts @@ -1,10 +1,10 @@ -import { GameEventMeta } from 'types'; +import { Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; /** * Event_GameHostChanged carries no payload fields. * The new host is identified by GameEvent.player_id (meta.playerId). */ -export function gameHostChanged(_data: {}, meta: GameEventMeta): void { +export function gameHostChanged(_data: {}, meta: Enriched.GameEventMeta): void { GamePersistence.gameHostChanged(meta.gameId, meta.playerId); } diff --git a/webclient/src/websocket/events/game/gameSay.ts b/webclient/src/websocket/events/game/gameSay.ts index 66cfc06f9..3552f099f 100644 --- a/webclient/src/websocket/events/game/gameSay.ts +++ b/webclient/src/websocket/events/game/gameSay.ts @@ -1,7 +1,6 @@ -import type { Event_GameSay } from 'generated/proto/event_game_say_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function gameSay(data: Event_GameSay, meta: GameEventMeta): void { +export function gameSay(data: Data.Event_GameSay, meta: Enriched.GameEventMeta): void { GamePersistence.gameSay(meta.gameId, meta.playerId, data.message); } diff --git a/webclient/src/websocket/events/game/gameStateChanged.ts b/webclient/src/websocket/events/game/gameStateChanged.ts index 16f35b136..91342db72 100644 --- a/webclient/src/websocket/events/game/gameStateChanged.ts +++ b/webclient/src/websocket/events/game/gameStateChanged.ts @@ -1,7 +1,6 @@ -import type { Event_GameStateChanged } from 'generated/proto/event_game_state_changed_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function gameStateChanged(data: Event_GameStateChanged, meta: GameEventMeta): void { +export function gameStateChanged(data: Data.Event_GameStateChanged, meta: Enriched.GameEventMeta): void { GamePersistence.gameStateChanged(meta.gameId, data); } diff --git a/webclient/src/websocket/events/game/index.ts b/webclient/src/websocket/events/game/index.ts index 2e75dabc8..e9931880c 100644 --- a/webclient/src/websocket/events/game/index.ts +++ b/webclient/src/websocket/events/game/index.ts @@ -1,4 +1,7 @@ -import { GameExtensionRegistry, makeGameEntry } from '../../services/protobuf-types'; +import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; + +import { Data, Enriched } from '@app/types'; + import { attachCard } from './attachCard'; import { changeZoneProperties } from './changeZoneProperties'; import { createArrow } from './createArrow'; @@ -29,65 +32,44 @@ import { setCardCounter } from './setCardCounter'; import { setCounter } from './setCounter'; import { shuffle } from './shuffle'; -import { Event_Join_ext } from 'generated/proto/event_join_pb'; -import { Event_Leave_ext } from 'generated/proto/event_leave_pb'; -import { Event_GameClosed_ext } from 'generated/proto/event_game_closed_pb'; -import { Event_GameHostChanged_ext } from 'generated/proto/event_game_host_changed_pb'; -import { Event_Kicked_ext } from 'generated/proto/event_kicked_pb'; -import { Event_GameStateChanged_ext } from 'generated/proto/event_game_state_changed_pb'; -import { Event_PlayerPropertiesChanged_ext } from 'generated/proto/event_player_properties_changed_pb'; -import { Event_GameSay_ext } from 'generated/proto/event_game_say_pb'; -import { Event_CreateArrow_ext } from 'generated/proto/event_create_arrow_pb'; -import { Event_DeleteArrow_ext } from 'generated/proto/event_delete_arrow_pb'; -import { Event_CreateCounter_ext } from 'generated/proto/event_create_counter_pb'; -import { Event_SetCounter_ext } from 'generated/proto/event_set_counter_pb'; -import { Event_DelCounter_ext } from 'generated/proto/event_del_counter_pb'; -import { Event_DrawCards_ext } from 'generated/proto/event_draw_cards_pb'; -import { Event_RevealCards_ext } from 'generated/proto/event_reveal_cards_pb'; -import { Event_Shuffle_ext } from 'generated/proto/event_shuffle_pb'; -import { Event_RollDie_ext } from 'generated/proto/event_roll_die_pb'; -import { Event_MoveCard_ext } from 'generated/proto/event_move_card_pb'; -import { Event_FlipCard_ext } from 'generated/proto/event_flip_card_pb'; -import { Event_DestroyCard_ext } from 'generated/proto/event_destroy_card_pb'; -import { Event_AttachCard_ext } from 'generated/proto/event_attach_card_pb'; -import { Event_CreateToken_ext } from 'generated/proto/event_create_token_pb'; -import { Event_SetCardAttr_ext } from 'generated/proto/event_set_card_attr_pb'; -import { Event_SetCardCounter_ext } from 'generated/proto/event_set_card_counter_pb'; -import { Event_SetActivePlayer_ext } from 'generated/proto/event_set_active_player_pb'; -import { Event_SetActivePhase_ext } from 'generated/proto/event_set_active_phase_pb'; -import { Event_DumpZone_ext } from 'generated/proto/event_dump_zone_pb'; -import { Event_ChangeZoneProperties_ext } from 'generated/proto/event_change_zone_properties_pb'; -import { Event_ReverseTurn_ext } from 'generated/proto/event_reverse_turn_pb'; +type GameRegistryEntry = Data.RegistryEntry; +export type GameExtensionRegistry = GameRegistryEntry[]; + +function makeGameEntry( + ext: GenExtension, + handler: (value: V, meta: Enriched.GameEventMeta) => void, +): GameRegistryEntry { + return Data.makeEntry(ext, handler); +} export const GameEvents: GameExtensionRegistry = [ - makeGameEntry(Event_Join_ext, joinGame), - makeGameEntry(Event_Leave_ext, leaveGame), - makeGameEntry(Event_GameClosed_ext, gameClosed), - makeGameEntry(Event_GameHostChanged_ext, gameHostChanged), - makeGameEntry(Event_Kicked_ext, kicked), - makeGameEntry(Event_GameStateChanged_ext, gameStateChanged), - makeGameEntry(Event_PlayerPropertiesChanged_ext, playerPropertiesChanged), - makeGameEntry(Event_GameSay_ext, gameSay), - makeGameEntry(Event_CreateArrow_ext, createArrow), - makeGameEntry(Event_DeleteArrow_ext, deleteArrow), - makeGameEntry(Event_CreateCounter_ext, createCounter), - makeGameEntry(Event_SetCounter_ext, setCounter), - makeGameEntry(Event_DelCounter_ext, delCounter), - makeGameEntry(Event_DrawCards_ext, drawCards), - makeGameEntry(Event_RevealCards_ext, revealCards), - makeGameEntry(Event_Shuffle_ext, shuffle), - makeGameEntry(Event_RollDie_ext, rollDie), - makeGameEntry(Event_MoveCard_ext, moveCard), - makeGameEntry(Event_FlipCard_ext, flipCard), - makeGameEntry(Event_DestroyCard_ext, destroyCard), - makeGameEntry(Event_AttachCard_ext, attachCard), - makeGameEntry(Event_CreateToken_ext, createToken), - makeGameEntry(Event_SetCardAttr_ext, setCardAttr), - makeGameEntry(Event_SetCardCounter_ext, setCardCounter), - makeGameEntry(Event_SetActivePlayer_ext, setActivePlayer), - makeGameEntry(Event_SetActivePhase_ext, setActivePhase), - makeGameEntry(Event_DumpZone_ext, dumpZone), - makeGameEntry(Event_ChangeZoneProperties_ext, changeZoneProperties), - makeGameEntry(Event_ReverseTurn_ext, reverseTurn), + makeGameEntry(Data.Event_Join_ext, joinGame), + makeGameEntry(Data.Event_Leave_ext, leaveGame), + makeGameEntry(Data.Event_GameClosed_ext, gameClosed), + makeGameEntry(Data.Event_GameHostChanged_ext, gameHostChanged), + makeGameEntry(Data.Event_Kicked_ext, kicked), + makeGameEntry(Data.Event_GameStateChanged_ext, gameStateChanged), + makeGameEntry(Data.Event_PlayerPropertiesChanged_ext, playerPropertiesChanged), + makeGameEntry(Data.Event_GameSay_ext, gameSay), + makeGameEntry(Data.Event_CreateArrow_ext, createArrow), + makeGameEntry(Data.Event_DeleteArrow_ext, deleteArrow), + makeGameEntry(Data.Event_CreateCounter_ext, createCounter), + makeGameEntry(Data.Event_SetCounter_ext, setCounter), + makeGameEntry(Data.Event_DelCounter_ext, delCounter), + makeGameEntry(Data.Event_DrawCards_ext, drawCards), + makeGameEntry(Data.Event_RevealCards_ext, revealCards), + makeGameEntry(Data.Event_Shuffle_ext, shuffle), + makeGameEntry(Data.Event_RollDie_ext, rollDie), + makeGameEntry(Data.Event_MoveCard_ext, moveCard), + makeGameEntry(Data.Event_FlipCard_ext, flipCard), + makeGameEntry(Data.Event_DestroyCard_ext, destroyCard), + makeGameEntry(Data.Event_AttachCard_ext, attachCard), + makeGameEntry(Data.Event_CreateToken_ext, createToken), + makeGameEntry(Data.Event_SetCardAttr_ext, setCardAttr), + makeGameEntry(Data.Event_SetCardCounter_ext, setCardCounter), + makeGameEntry(Data.Event_SetActivePlayer_ext, setActivePlayer), + makeGameEntry(Data.Event_SetActivePhase_ext, setActivePhase), + makeGameEntry(Data.Event_DumpZone_ext, dumpZone), + makeGameEntry(Data.Event_ChangeZoneProperties_ext, changeZoneProperties), + makeGameEntry(Data.Event_ReverseTurn_ext, reverseTurn), ]; - diff --git a/webclient/src/websocket/events/game/joinGame.ts b/webclient/src/websocket/events/game/joinGame.ts index 640e83e96..376801f73 100644 --- a/webclient/src/websocket/events/game/joinGame.ts +++ b/webclient/src/websocket/events/game/joinGame.ts @@ -1,7 +1,6 @@ import { GamePersistence } from '../../persistence'; -import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; -export function joinGame(data: { playerProperties: ServerInfo_PlayerProperties }, meta: GameEventMeta): void { +export function joinGame(data: { playerProperties: Data.ServerInfo_PlayerProperties }, meta: Enriched.GameEventMeta): void { GamePersistence.playerJoined(meta.gameId, data.playerProperties); } diff --git a/webclient/src/websocket/events/game/kicked.ts b/webclient/src/websocket/events/game/kicked.ts index 7bf94d5ad..a409cb895 100644 --- a/webclient/src/websocket/events/game/kicked.ts +++ b/webclient/src/websocket/events/game/kicked.ts @@ -1,6 +1,6 @@ -import { GameEventMeta } from 'types'; +import { Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function kicked(_data: {}, meta: GameEventMeta): void { +export function kicked(_data: {}, meta: Enriched.GameEventMeta): void { GamePersistence.kicked(meta.gameId); } diff --git a/webclient/src/websocket/events/game/leaveGame.ts b/webclient/src/websocket/events/game/leaveGame.ts index 9367ba5bb..703fcd86b 100644 --- a/webclient/src/websocket/events/game/leaveGame.ts +++ b/webclient/src/websocket/events/game/leaveGame.ts @@ -1,6 +1,6 @@ -import { GameEventMeta } from 'types'; +import { Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function leaveGame(data: { reason: number }, meta: GameEventMeta): void { +export function leaveGame(data: { reason: number }, meta: Enriched.GameEventMeta): void { GamePersistence.playerLeft(meta.gameId, meta.playerId, data.reason ?? 1); } diff --git a/webclient/src/websocket/events/game/moveCard.ts b/webclient/src/websocket/events/game/moveCard.ts index a15666d57..01e33d570 100644 --- a/webclient/src/websocket/events/game/moveCard.ts +++ b/webclient/src/websocket/events/game/moveCard.ts @@ -1,7 +1,6 @@ -import type { Event_MoveCard } from 'generated/proto/event_move_card_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function moveCard(data: Event_MoveCard, meta: GameEventMeta): void { +export function moveCard(data: Data.Event_MoveCard, meta: Enriched.GameEventMeta): void { GamePersistence.cardMoved(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/playerPropertiesChanged.ts b/webclient/src/websocket/events/game/playerPropertiesChanged.ts index 0dbe440ed..dcc0acef6 100644 --- a/webclient/src/websocket/events/game/playerPropertiesChanged.ts +++ b/webclient/src/websocket/events/game/playerPropertiesChanged.ts @@ -1,7 +1,6 @@ -import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function playerPropertiesChanged(data: { playerProperties: ServerInfo_PlayerProperties }, meta: GameEventMeta): void { +export function playerPropertiesChanged(data: { playerProperties: Data.ServerInfo_PlayerProperties }, meta: Enriched.GameEventMeta): void { GamePersistence.playerPropertiesChanged(meta.gameId, meta.playerId, data.playerProperties); } diff --git a/webclient/src/websocket/events/game/revealCards.ts b/webclient/src/websocket/events/game/revealCards.ts index 3837786f5..42ab882ff 100644 --- a/webclient/src/websocket/events/game/revealCards.ts +++ b/webclient/src/websocket/events/game/revealCards.ts @@ -1,7 +1,6 @@ -import type { Event_RevealCards } from 'generated/proto/event_reveal_cards_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function revealCards(data: Event_RevealCards, meta: GameEventMeta): void { +export function revealCards(data: Data.Event_RevealCards, meta: Enriched.GameEventMeta): void { GamePersistence.cardsRevealed(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/reverseTurn.ts b/webclient/src/websocket/events/game/reverseTurn.ts index 8716612e3..ae7234d8f 100644 --- a/webclient/src/websocket/events/game/reverseTurn.ts +++ b/webclient/src/websocket/events/game/reverseTurn.ts @@ -1,7 +1,6 @@ -import type { Event_ReverseTurn } from 'generated/proto/event_reverse_turn_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function reverseTurn(data: Event_ReverseTurn, meta: GameEventMeta): void { +export function reverseTurn(data: Data.Event_ReverseTurn, meta: Enriched.GameEventMeta): void { GamePersistence.turnReversed(meta.gameId, data.reversed); } diff --git a/webclient/src/websocket/events/game/rollDie.ts b/webclient/src/websocket/events/game/rollDie.ts index 4da34387d..2c262bb8b 100644 --- a/webclient/src/websocket/events/game/rollDie.ts +++ b/webclient/src/websocket/events/game/rollDie.ts @@ -1,7 +1,6 @@ -import type { Event_RollDie } from 'generated/proto/event_roll_die_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function rollDie(data: Event_RollDie, meta: GameEventMeta): void { +export function rollDie(data: Data.Event_RollDie, meta: Enriched.GameEventMeta): void { GamePersistence.dieRolled(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/setActivePhase.ts b/webclient/src/websocket/events/game/setActivePhase.ts index 5981b97bc..2a1f1ef3d 100644 --- a/webclient/src/websocket/events/game/setActivePhase.ts +++ b/webclient/src/websocket/events/game/setActivePhase.ts @@ -1,7 +1,6 @@ -import type { Event_SetActivePhase } from 'generated/proto/event_set_active_phase_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function setActivePhase(data: Event_SetActivePhase, meta: GameEventMeta): void { +export function setActivePhase(data: Data.Event_SetActivePhase, meta: Enriched.GameEventMeta): void { GamePersistence.activePhaseSet(meta.gameId, data.phase); } diff --git a/webclient/src/websocket/events/game/setActivePlayer.ts b/webclient/src/websocket/events/game/setActivePlayer.ts index 0f529b6e4..7966fe7e5 100644 --- a/webclient/src/websocket/events/game/setActivePlayer.ts +++ b/webclient/src/websocket/events/game/setActivePlayer.ts @@ -1,7 +1,6 @@ -import type { Event_SetActivePlayer } from 'generated/proto/event_set_active_player_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function setActivePlayer(data: Event_SetActivePlayer, meta: GameEventMeta): void { +export function setActivePlayer(data: Data.Event_SetActivePlayer, meta: Enriched.GameEventMeta): void { GamePersistence.activePlayerSet(meta.gameId, data.activePlayerId); } diff --git a/webclient/src/websocket/events/game/setCardAttr.ts b/webclient/src/websocket/events/game/setCardAttr.ts index aa15043fb..688b0d1f2 100644 --- a/webclient/src/websocket/events/game/setCardAttr.ts +++ b/webclient/src/websocket/events/game/setCardAttr.ts @@ -1,7 +1,6 @@ -import type { Event_SetCardAttr } from 'generated/proto/event_set_card_attr_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function setCardAttr(data: Event_SetCardAttr, meta: GameEventMeta): void { +export function setCardAttr(data: Data.Event_SetCardAttr, meta: Enriched.GameEventMeta): void { GamePersistence.cardAttrChanged(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/setCardCounter.ts b/webclient/src/websocket/events/game/setCardCounter.ts index e595b65a7..19e4ff236 100644 --- a/webclient/src/websocket/events/game/setCardCounter.ts +++ b/webclient/src/websocket/events/game/setCardCounter.ts @@ -1,7 +1,6 @@ -import type { Event_SetCardCounter } from 'generated/proto/event_set_card_counter_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function setCardCounter(data: Event_SetCardCounter, meta: GameEventMeta): void { +export function setCardCounter(data: Data.Event_SetCardCounter, meta: Enriched.GameEventMeta): void { GamePersistence.cardCounterChanged(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/setCounter.ts b/webclient/src/websocket/events/game/setCounter.ts index a7e6863ae..3de940761 100644 --- a/webclient/src/websocket/events/game/setCounter.ts +++ b/webclient/src/websocket/events/game/setCounter.ts @@ -1,7 +1,6 @@ -import type { Event_SetCounter } from 'generated/proto/event_set_counter_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function setCounter(data: Event_SetCounter, meta: GameEventMeta): void { +export function setCounter(data: Data.Event_SetCounter, meta: Enriched.GameEventMeta): void { GamePersistence.counterSet(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/game/shuffle.ts b/webclient/src/websocket/events/game/shuffle.ts index a1ec8ba05..8b242e38f 100644 --- a/webclient/src/websocket/events/game/shuffle.ts +++ b/webclient/src/websocket/events/game/shuffle.ts @@ -1,7 +1,6 @@ -import type { Event_Shuffle } from 'generated/proto/event_shuffle_pb'; -import type { GameEventMeta } from 'types'; +import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function shuffle(data: Event_Shuffle, meta: GameEventMeta): void { +export function shuffle(data: Data.Event_Shuffle, meta: Enriched.GameEventMeta): void { GamePersistence.zoneShuffled(meta.gameId, meta.playerId, data); } diff --git a/webclient/src/websocket/events/room/index.ts b/webclient/src/websocket/events/room/index.ts index f1832faa9..c02805659 100644 --- a/webclient/src/websocket/events/room/index.ts +++ b/webclient/src/websocket/events/room/index.ts @@ -1,4 +1,6 @@ -import { RoomExtensionRegistry, makeRoomEntry } from '../../services/protobuf-types'; +import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; + +import { Data } from '@app/types'; import { joinRoom } from './joinRoom'; import { leaveRoom } from './leaveRoom'; @@ -6,17 +8,20 @@ import { listGames } from './listGames'; import { roomSay } from './roomSay'; import { removeMessages } from './removeMessages'; -import { Event_JoinRoom_ext } from 'generated/proto/event_join_room_pb'; -import { Event_LeaveRoom_ext } from 'generated/proto/event_leave_room_pb'; -import { Event_ListGames_ext } from 'generated/proto/event_list_games_pb'; -import { Event_RemoveMessages_ext } from 'generated/proto/event_remove_messages_pb'; -import { Event_RoomSay_ext } from 'generated/proto/event_room_say_pb'; +type RoomRegistryEntry = Data.RegistryEntry; +export type RoomExtensionRegistry = RoomRegistryEntry[]; + +function makeRoomEntry( + ext: GenExtension, + handler: (value: V, roomEvent: Data.RoomEvent) => void, +): RoomRegistryEntry { + return Data.makeEntry(ext, handler); +} export const RoomEvents: RoomExtensionRegistry = [ - makeRoomEntry(Event_JoinRoom_ext, joinRoom), - makeRoomEntry(Event_LeaveRoom_ext, leaveRoom), - makeRoomEntry(Event_ListGames_ext, listGames), - makeRoomEntry(Event_RemoveMessages_ext, removeMessages), - makeRoomEntry(Event_RoomSay_ext, roomSay), + makeRoomEntry(Data.Event_JoinRoom_ext, joinRoom), + makeRoomEntry(Data.Event_LeaveRoom_ext, leaveRoom), + makeRoomEntry(Data.Event_ListGames_ext, listGames), + makeRoomEntry(Data.Event_RemoveMessages_ext, removeMessages), + makeRoomEntry(Data.Event_RoomSay_ext, roomSay), ]; - diff --git a/webclient/src/websocket/events/room/interfaces.ts b/webclient/src/websocket/events/room/interfaces.ts deleted file mode 100644 index 6e8a61b01..000000000 --- a/webclient/src/websocket/events/room/interfaces.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { Event_JoinRoom } from 'generated/proto/event_join_room_pb'; -import type { Event_LeaveRoom } from 'generated/proto/event_leave_room_pb'; -import type { Event_ListGames } from 'generated/proto/event_list_games_pb'; -import type { Event_RemoveMessages } from 'generated/proto/event_remove_messages_pb'; -import type { Event_RoomSay } from 'generated/proto/event_room_say_pb'; -import type { RoomEvent as GeneratedRoomEvent } from 'generated/proto/room_event_pb'; - -export type JoinRoomData = Event_JoinRoom; -export type LeaveRoomData = Event_LeaveRoom; -export type ListGamesData = Event_ListGames; -export type RemoveMessagesData = Event_RemoveMessages; -export type RoomSayData = Event_RoomSay; -export type RoomEvent = GeneratedRoomEvent; diff --git a/webclient/src/websocket/events/room/joinRoom.ts b/webclient/src/websocket/events/room/joinRoom.ts index f45ff18ab..1dab289d7 100644 --- a/webclient/src/websocket/events/room/joinRoom.ts +++ b/webclient/src/websocket/events/room/joinRoom.ts @@ -1,6 +1,6 @@ +import type { Data } from '@app/types'; import { RoomPersistence } from '../../persistence'; -import { JoinRoomData, RoomEvent } from './interfaces'; -export function joinRoom({ userInfo }: JoinRoomData, { roomId }: RoomEvent): void { +export function joinRoom({ userInfo }: Data.Event_JoinRoom, { roomId }: Data.RoomEvent): void { RoomPersistence.userJoined(roomId, userInfo); } diff --git a/webclient/src/websocket/events/room/leaveRoom.ts b/webclient/src/websocket/events/room/leaveRoom.ts index c7564d458..df69c783f 100644 --- a/webclient/src/websocket/events/room/leaveRoom.ts +++ b/webclient/src/websocket/events/room/leaveRoom.ts @@ -1,6 +1,6 @@ +import type { Data } from '@app/types'; import { RoomPersistence } from '../../persistence'; -import { LeaveRoomData, RoomEvent } from './interfaces'; -export function leaveRoom({ name }: LeaveRoomData, { roomId }: RoomEvent): void { +export function leaveRoom({ name }: Data.Event_LeaveRoom, { roomId }: Data.RoomEvent): void { RoomPersistence.userLeft(roomId, name); } diff --git a/webclient/src/websocket/events/room/listGames.ts b/webclient/src/websocket/events/room/listGames.ts index 0f1f20438..a943fed41 100644 --- a/webclient/src/websocket/events/room/listGames.ts +++ b/webclient/src/websocket/events/room/listGames.ts @@ -1,6 +1,6 @@ +import type { Data } from '@app/types'; import { RoomPersistence } from '../../persistence'; -import { ListGamesData, RoomEvent } from './interfaces'; -export function listGames({ gameList }: ListGamesData, { roomId }: RoomEvent): void { +export function listGames({ gameList }: Data.Event_ListGames, { roomId }: Data.RoomEvent): void { RoomPersistence.updateGames(roomId, gameList); } diff --git a/webclient/src/websocket/events/room/removeMessages.ts b/webclient/src/websocket/events/room/removeMessages.ts index 859470c81..b6342e50d 100644 --- a/webclient/src/websocket/events/room/removeMessages.ts +++ b/webclient/src/websocket/events/room/removeMessages.ts @@ -1,6 +1,6 @@ +import type { Data } from '@app/types'; import { RoomPersistence } from '../../persistence'; -import { RemoveMessagesData, RoomEvent } from './interfaces'; -export function removeMessages({ name, amount }: RemoveMessagesData, { roomId }: RoomEvent): void { +export function removeMessages({ name, amount }: Data.Event_RemoveMessages, { roomId }: Data.RoomEvent): void { RoomPersistence.removeMessages(roomId, name, amount); } diff --git a/webclient/src/websocket/events/room/roomEvents.spec.ts b/webclient/src/websocket/events/room/roomEvents.spec.ts index 60690d487..60d8fe765 100644 --- a/webclient/src/websocket/events/room/roomEvents.spec.ts +++ b/webclient/src/websocket/events/room/roomEvents.spec.ts @@ -9,27 +9,20 @@ vi.mock('../../persistence', () => ({ })); import { create } from '@bufbuild/protobuf'; +import { Data } from '@app/types'; import { RoomPersistence } from '../../persistence'; import { joinRoom } from './joinRoom'; import { leaveRoom } from './leaveRoom'; import { listGames } from './listGames'; import { removeMessages } from './removeMessages'; import { roomSay } from './roomSay'; -import { Event_JoinRoomSchema } from 'generated/proto/event_join_room_pb'; -import { Event_LeaveRoomSchema } from 'generated/proto/event_leave_room_pb'; -import { Event_ListGamesSchema } from 'generated/proto/event_list_games_pb'; -import { Event_RemoveMessagesSchema } from 'generated/proto/event_remove_messages_pb'; -import { Event_RoomSaySchema } from 'generated/proto/event_room_say_pb'; -import { RoomEventSchema } from 'generated/proto/room_event_pb'; -const makeRoomEvent = (roomId: number) => create(RoomEventSchema, { roomId }); - -beforeEach(() => vi.clearAllMocks()); +const makeRoomEvent = (roomId: number) => create(Data.RoomEventSchema, { roomId }); describe('joinRoom room event', () => { it('calls RoomPersistence.userJoined with roomId and userInfo', () => { - const data = create(Event_JoinRoomSchema, { userInfo: { name: 'alice' } }); + const data = create(Data.Event_JoinRoomSchema, { userInfo: { name: 'alice' } }); joinRoom(data, makeRoomEvent(3)); expect(RoomPersistence.userJoined).toHaveBeenCalledWith(3, data.userInfo); }); @@ -38,7 +31,7 @@ describe('joinRoom room event', () => { describe('leaveRoom room event', () => { it('calls RoomPersistence.userLeft with roomId and name', () => { - leaveRoom(create(Event_LeaveRoomSchema, { name: 'alice' }), makeRoomEvent(4)); + leaveRoom(create(Data.Event_LeaveRoomSchema, { name: 'alice' }), makeRoomEvent(4)); expect(RoomPersistence.userLeft).toHaveBeenCalledWith(4, 'alice'); }); }); @@ -46,7 +39,7 @@ describe('leaveRoom room event', () => { describe('listGames room event', () => { it('calls RoomPersistence.updateGames with roomId and gameList', () => { - const data = create(Event_ListGamesSchema, { gameList: [{ gameId: 1 }] }); + const data = create(Data.Event_ListGamesSchema, { gameList: [{ gameId: 1 }] }); listGames(data, makeRoomEvent(5)); expect(RoomPersistence.updateGames).toHaveBeenCalledWith(5, data.gameList); }); @@ -55,7 +48,7 @@ describe('listGames room event', () => { describe('removeMessages room event', () => { it('calls RoomPersistence.removeMessages with roomId, name, amount', () => { - removeMessages(create(Event_RemoveMessagesSchema, { name: 'bob', amount: 10 }), makeRoomEvent(6)); + removeMessages(create(Data.Event_RemoveMessagesSchema, { name: 'bob', amount: 10 }), makeRoomEvent(6)); expect(RoomPersistence.removeMessages).toHaveBeenCalledWith(6, 'bob', 10); }); }); @@ -67,7 +60,7 @@ describe('roomSay room event', () => { afterEach(() => vi.useRealTimers()); it('calls RoomPersistence.addMessage with roomId and message', () => { - const data = create(Event_RoomSaySchema, { message: 'hello' }); + const data = create(Data.Event_RoomSaySchema, { message: 'hello' }); roomSay(data, makeRoomEvent(7)); expect(RoomPersistence.addMessage).toHaveBeenCalledWith(7, { ...data, timeReceived: 0 }); }); diff --git a/webclient/src/websocket/events/room/roomSay.ts b/webclient/src/websocket/events/room/roomSay.ts index f6accd8a9..25b35b12b 100644 --- a/webclient/src/websocket/events/room/roomSay.ts +++ b/webclient/src/websocket/events/room/roomSay.ts @@ -1,9 +1,9 @@ -import { Message } from 'types'; +import type { Data } from '@app/types'; +import { Enriched } from '@app/types'; import { RoomPersistence } from '../../persistence'; -import { RoomSayData, RoomEvent } from './interfaces'; -export function roomSay(data: RoomSayData, { roomId }: RoomEvent): void { - const message: Message = { ...data, timeReceived: Date.now() }; +export function roomSay(data: Data.Event_RoomSay, { roomId }: Data.RoomEvent): void { + const message: Enriched.Message = { ...data, timeReceived: Date.now() }; RoomPersistence.addMessage(roomId, message); } diff --git a/webclient/src/websocket/events/session/addToList.ts b/webclient/src/websocket/events/session/addToList.ts index 08b19b45d..3c820037a 100644 --- a/webclient/src/websocket/events/session/addToList.ts +++ b/webclient/src/websocket/events/session/addToList.ts @@ -1,7 +1,7 @@ +import type { Data } from '@app/types'; import { SessionPersistence } from '../../persistence'; -import { AddToListData } from './interfaces'; -export function addToList({ listName, userInfo }: AddToListData): void { +export function addToList({ listName, userInfo }: Data.Event_AddToList): void { switch (listName) { case 'buddy': { SessionPersistence.addToBuddyList(userInfo); diff --git a/webclient/src/websocket/events/session/connectionClosed.ts b/webclient/src/websocket/events/session/connectionClosed.ts index c98080f5f..fcde2fceb 100644 --- a/webclient/src/websocket/events/session/connectionClosed.ts +++ b/webclient/src/websocket/events/session/connectionClosed.ts @@ -1,9 +1,7 @@ -import { StatusEnum } from 'types'; -import { Event_ConnectionClosed_CloseReason } from 'generated/proto/event_connection_closed_pb'; +import { App, Data } from '@app/types'; import { updateStatus } from '../../commands/session'; -import { ConnectionClosedData } from './interfaces'; -export function connectionClosed({ reason, reasonStr, endTime }: ConnectionClosedData): void { +export function connectionClosed({ reason, reasonStr, endTime }: Data.Event_ConnectionClosed): void { let message: string; // @TODO (5) @@ -11,35 +9,35 @@ export function connectionClosed({ reason, reasonStr, endTime }: ConnectionClose message = reasonStr; } else { switch (reason) { - case Event_ConnectionClosed_CloseReason.USER_LIMIT_REACHED: + case Data.Event_ConnectionClosed_CloseReason.USER_LIMIT_REACHED: message = 'The server has reached its maximum user capacity'; break; - case Event_ConnectionClosed_CloseReason.TOO_MANY_CONNECTIONS: + case Data.Event_ConnectionClosed_CloseReason.TOO_MANY_CONNECTIONS: message = 'There are too many concurrent connections from your address'; break; - case Event_ConnectionClosed_CloseReason.BANNED: + case Data.Event_ConnectionClosed_CloseReason.BANNED: message = typeof endTime === 'number' && endTime > 0 && Number.isFinite(endTime) ? `You are banned until ${new Date(endTime * 1000).toLocaleString()}` : 'You are banned'; break; - case Event_ConnectionClosed_CloseReason.DEMOTED: + case Data.Event_ConnectionClosed_CloseReason.DEMOTED: message = 'You were demoted'; break; - case Event_ConnectionClosed_CloseReason.SERVER_SHUTDOWN: + case Data.Event_ConnectionClosed_CloseReason.SERVER_SHUTDOWN: message = 'Scheduled server shutdown'; break; - case Event_ConnectionClosed_CloseReason.USERNAMEINVALID: + case Data.Event_ConnectionClosed_CloseReason.USERNAMEINVALID: message = 'Invalid username'; break; - case Event_ConnectionClosed_CloseReason.LOGGEDINELSEWERE: + case Data.Event_ConnectionClosed_CloseReason.LOGGEDINELSEWERE: message = 'You have been logged out due to logging in at another location'; break; - case Event_ConnectionClosed_CloseReason.OTHER: + case Data.Event_ConnectionClosed_CloseReason.OTHER: default: message = 'Unknown reason'; break; } } - updateStatus(StatusEnum.DISCONNECTED, message); + updateStatus(App.StatusEnum.DISCONNECTED, message); } diff --git a/webclient/src/websocket/events/session/gameJoined.ts b/webclient/src/websocket/events/session/gameJoined.ts index 8c0d49006..4988039dd 100644 --- a/webclient/src/websocket/events/session/gameJoined.ts +++ b/webclient/src/websocket/events/session/gameJoined.ts @@ -1,6 +1,6 @@ +import type { Data } from '@app/types'; import { SessionPersistence } from '../../persistence'; -import { GameJoinedData } from './interfaces'; -export function gameJoined(gameJoined: GameJoinedData): void { +export function gameJoined(gameJoined: Data.Event_GameJoined): void { SessionPersistence.gameJoined(gameJoined); } diff --git a/webclient/src/websocket/events/session/index.ts b/webclient/src/websocket/events/session/index.ts index d2b8cc4e9..3d5935c9f 100644 --- a/webclient/src/websocket/events/session/index.ts +++ b/webclient/src/websocket/events/session/index.ts @@ -1,4 +1,7 @@ -import { SessionExtensionRegistry, makeSessionEntry } from '../../services/protobuf-types'; +import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; + +import { Data } from '@app/types'; + import { addToList } from './addToList'; import { connectionClosed } from './connectionClosed'; import { listRooms } from './listRooms'; @@ -14,35 +17,29 @@ import { userLeft } from './userLeft'; import { userMessage } from './userMessage'; import { gameJoined } from './gameJoined'; -import { Event_AddToList_ext } from 'generated/proto/event_add_to_list_pb'; -import { Event_ConnectionClosed_ext } from 'generated/proto/event_connection_closed_pb'; -import { Event_GameJoined_ext } from 'generated/proto/event_game_joined_pb'; -import { Event_ListRooms_ext } from 'generated/proto/event_list_rooms_pb'; -import { Event_NotifyUser_ext } from 'generated/proto/event_notify_user_pb'; -import { Event_RemoveFromList_ext } from 'generated/proto/event_remove_from_list_pb'; -import { Event_ReplayAdded_ext } from 'generated/proto/event_replay_added_pb'; -import { Event_ServerCompleteList_ext } from 'generated/proto/event_server_complete_list_pb'; -import { Event_ServerIdentification_ext } from 'generated/proto/event_server_identification_pb'; -import { Event_ServerMessage_ext } from 'generated/proto/event_server_message_pb'; -import { Event_ServerShutdown_ext } from 'generated/proto/event_server_shutdown_pb'; -import { Event_UserJoined_ext } from 'generated/proto/event_user_joined_pb'; -import { Event_UserLeft_ext } from 'generated/proto/event_user_left_pb'; -import { Event_UserMessage_ext } from 'generated/proto/event_user_message_pb'; +type SessionRegistryEntry = Data.RegistryEntry; +export type SessionExtensionRegistry = SessionRegistryEntry[]; + +function makeSessionEntry( + ext: GenExtension, + handler: (value: V) => void, +): SessionRegistryEntry { + return Data.makeEntry(ext, handler); +} export const SessionEvents: SessionExtensionRegistry = [ - makeSessionEntry(Event_AddToList_ext, addToList), - makeSessionEntry(Event_ConnectionClosed_ext, connectionClosed), - makeSessionEntry(Event_GameJoined_ext, gameJoined), - makeSessionEntry(Event_ListRooms_ext, listRooms), - makeSessionEntry(Event_NotifyUser_ext, notifyUser), - makeSessionEntry(Event_RemoveFromList_ext, removeFromList), - makeSessionEntry(Event_ReplayAdded_ext, replayAdded), - makeSessionEntry(Event_ServerCompleteList_ext, serverCompleteList), - makeSessionEntry(Event_ServerIdentification_ext, serverIdentification), - makeSessionEntry(Event_ServerMessage_ext, serverMessage), - makeSessionEntry(Event_ServerShutdown_ext, serverShutdown), - makeSessionEntry(Event_UserJoined_ext, userJoined), - makeSessionEntry(Event_UserLeft_ext, userLeft), - makeSessionEntry(Event_UserMessage_ext, userMessage), + makeSessionEntry(Data.Event_AddToList_ext, addToList), + makeSessionEntry(Data.Event_ConnectionClosed_ext, connectionClosed), + makeSessionEntry(Data.Event_GameJoined_ext, gameJoined), + makeSessionEntry(Data.Event_ListRooms_ext, listRooms), + makeSessionEntry(Data.Event_NotifyUser_ext, notifyUser), + makeSessionEntry(Data.Event_RemoveFromList_ext, removeFromList), + makeSessionEntry(Data.Event_ReplayAdded_ext, replayAdded), + makeSessionEntry(Data.Event_ServerCompleteList_ext, serverCompleteList), + makeSessionEntry(Data.Event_ServerIdentification_ext, serverIdentification), + makeSessionEntry(Data.Event_ServerMessage_ext, serverMessage), + makeSessionEntry(Data.Event_ServerShutdown_ext, serverShutdown), + makeSessionEntry(Data.Event_UserJoined_ext, userJoined), + makeSessionEntry(Data.Event_UserLeft_ext, userLeft), + makeSessionEntry(Data.Event_UserMessage_ext, userMessage), ]; - diff --git a/webclient/src/websocket/events/session/interfaces.ts b/webclient/src/websocket/events/session/interfaces.ts deleted file mode 100644 index fcd1fd1b7..000000000 --- a/webclient/src/websocket/events/session/interfaces.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { Event_AddToList } from 'generated/proto/event_add_to_list_pb'; -import type { Event_ConnectionClosed } from 'generated/proto/event_connection_closed_pb'; -import type { Event_GameJoined } from 'generated/proto/event_game_joined_pb'; -import type { Event_ListRooms } from 'generated/proto/event_list_rooms_pb'; -import type { Event_NotifyUser } from 'generated/proto/event_notify_user_pb'; -import type { Event_RemoveFromList } from 'generated/proto/event_remove_from_list_pb'; -import type { Event_ReplayAdded } from 'generated/proto/event_replay_added_pb'; -import type { Event_ServerCompleteList } from 'generated/proto/event_server_complete_list_pb'; -import type { Event_ServerIdentification } from 'generated/proto/event_server_identification_pb'; -import type { Event_ServerMessage } from 'generated/proto/event_server_message_pb'; -import type { Event_ServerShutdown } from 'generated/proto/event_server_shutdown_pb'; -import type { Event_UserJoined } from 'generated/proto/event_user_joined_pb'; -import type { Event_UserLeft } from 'generated/proto/event_user_left_pb'; -import type { Event_UserMessage } from 'generated/proto/event_user_message_pb'; -import type { Event_PlayerPropertiesChanged } from 'generated/proto/event_player_properties_changed_pb'; - -export type AddToListData = Event_AddToList; -export type ConnectionClosedData = Event_ConnectionClosed; -export type GameJoinedData = Event_GameJoined; -export type ListRoomsData = Event_ListRooms; -export type NotifyUserData = Event_NotifyUser; -export type RemoveFromListData = Event_RemoveFromList; -export type ReplayAddedData = Event_ReplayAdded; -export type ServerCompleteListData = Event_ServerCompleteList; -export type ServerIdentificationData = Event_ServerIdentification; -export type ServerMessageData = Event_ServerMessage; -export type ServerShutdownData = Event_ServerShutdown; -export type UserJoinedData = Event_UserJoined; -export type UserLeftData = Event_UserLeft; -export type UserMessageData = Event_UserMessage; -export type PlayerGamePropertiesData = Event_PlayerPropertiesChanged; diff --git a/webclient/src/websocket/events/session/listRooms.ts b/webclient/src/websocket/events/session/listRooms.ts index 697bdaed3..2b94ea01a 100644 --- a/webclient/src/websocket/events/session/listRooms.ts +++ b/webclient/src/websocket/events/session/listRooms.ts @@ -1,9 +1,9 @@ +import type { Data } from '@app/types'; import { CLIENT_OPTIONS } from '../../config'; import { joinRoom } from '../../commands/session'; import { RoomPersistence } from '../../persistence'; -import { ListRoomsData } from './interfaces'; -export function listRooms({ roomList }: ListRoomsData): void { +export function listRooms({ roomList }: Data.Event_ListRooms): void { RoomPersistence.updateRooms(roomList); if (CLIENT_OPTIONS.autojoinrooms) { diff --git a/webclient/src/websocket/events/session/notifyUser.ts b/webclient/src/websocket/events/session/notifyUser.ts index f5673fe3f..438f82264 100644 --- a/webclient/src/websocket/events/session/notifyUser.ts +++ b/webclient/src/websocket/events/session/notifyUser.ts @@ -1,7 +1,6 @@ +import type { Data } from '@app/types'; import { SessionPersistence } from '../../persistence'; -import { NotifyUserData } from './interfaces'; - -export function notifyUser(payload: NotifyUserData): void { +export function notifyUser(payload: Data.Event_NotifyUser): void { SessionPersistence.notifyUser(payload); } diff --git a/webclient/src/websocket/events/session/removeFromList.ts b/webclient/src/websocket/events/session/removeFromList.ts index 20e2d7f54..776ac7c6d 100644 --- a/webclient/src/websocket/events/session/removeFromList.ts +++ b/webclient/src/websocket/events/session/removeFromList.ts @@ -1,7 +1,7 @@ +import type { Data } from '@app/types'; import { SessionPersistence } from '../../persistence'; -import { RemoveFromListData } from './interfaces'; -export function removeFromList({ listName, userName }: RemoveFromListData): void { +export function removeFromList({ listName, userName }: Data.Event_RemoveFromList): void { switch (listName) { case 'buddy': { SessionPersistence.removeFromBuddyList(userName); diff --git a/webclient/src/websocket/events/session/replayAdded.ts b/webclient/src/websocket/events/session/replayAdded.ts index 18a4ea82d..eacf01e86 100644 --- a/webclient/src/websocket/events/session/replayAdded.ts +++ b/webclient/src/websocket/events/session/replayAdded.ts @@ -1,6 +1,6 @@ +import type { Data } from '@app/types'; import { SessionPersistence } from '../../persistence'; -import { ReplayAddedData } from './interfaces'; -export function replayAdded({ matchInfo }: ReplayAddedData): void { +export function replayAdded({ matchInfo }: Data.Event_ReplayAdded): void { SessionPersistence.replayAdded(matchInfo); } diff --git a/webclient/src/websocket/events/session/serverCompleteList.ts b/webclient/src/websocket/events/session/serverCompleteList.ts index 77d37a31e..4d131128c 100644 --- a/webclient/src/websocket/events/session/serverCompleteList.ts +++ b/webclient/src/websocket/events/session/serverCompleteList.ts @@ -1,7 +1,7 @@ +import type { Data } from '@app/types'; import { RoomPersistence, SessionPersistence } from '../../persistence'; -import { ServerCompleteListData } from './interfaces'; -export function serverCompleteList({ userList, roomList }: ServerCompleteListData): void { +export function serverCompleteList({ userList, roomList }: Data.Event_ServerCompleteList): void { SessionPersistence.updateUsers(userList); RoomPersistence.updateRooms(roomList); } diff --git a/webclient/src/websocket/events/session/serverIdentification.ts b/webclient/src/websocket/events/session/serverIdentification.ts index d5998d3d3..d8e7f813d 100644 --- a/webclient/src/websocket/events/session/serverIdentification.ts +++ b/webclient/src/websocket/events/session/serverIdentification.ts @@ -1,4 +1,4 @@ -import { StatusEnum, WebSocketConnectOptions, WebSocketConnectReason } from 'types'; +import { App, Data, Enriched } from '@app/types'; import webClient from '../../WebClient'; import { PROTOCOL_VERSION } from '../../config'; @@ -14,59 +14,77 @@ import { updateStatus, } from '../../commands/session'; import { generateSalt, passwordSaltSupported } from '../../utils'; -import { ServerIdentificationData } from './interfaces'; import { SessionPersistence } from '../../persistence'; -export function serverIdentification(info: ServerIdentificationData): void { +export function serverIdentification(info: Data.Event_ServerIdentification): void { const { serverName, serverVersion, protocolVersion, serverOptions } = info; if (protocolVersion !== PROTOCOL_VERSION) { - updateStatus(StatusEnum.DISCONNECTED, `Protocol version mismatch: ${protocolVersion}`); + updateStatus(App.StatusEnum.DISCONNECTED, `Protocol version mismatch: ${protocolVersion}`); disconnect(); return; } const getPasswordSalt = passwordSaltSupported(serverOptions); - const { password, newPassword, ...connectOptions } = webClient.options; + const options = webClient.options; - switch (connectOptions.reason) { - case WebSocketConnectReason.LOGIN: - updateStatus(StatusEnum.LOGGING_IN, 'Logging In...'); - if (getPasswordSalt) { - requestPasswordSalt(connectOptions, password); - } else { - login(connectOptions, password); - } - break; - case WebSocketConnectReason.REGISTER: - const passwordSalt = getPasswordSalt ? generateSalt() : null; - register(connectOptions, password, passwordSalt); - break; - case WebSocketConnectReason.ACTIVATE_ACCOUNT: - if (getPasswordSalt) { - requestPasswordSalt(connectOptions, password); - } else { - activate(connectOptions, password); - } - break; - case WebSocketConnectReason.PASSWORD_RESET_REQUEST: - forgotPasswordRequest(connectOptions); - break; - case WebSocketConnectReason.PASSWORD_RESET_CHALLENGE: - forgotPasswordChallenge(connectOptions); - break; - case WebSocketConnectReason.PASSWORD_RESET: - if (getPasswordSalt) { - requestPasswordSalt(connectOptions, undefined, newPassword); - } else { - forgotPasswordReset(connectOptions, newPassword); - } - break; - default: - updateStatus(StatusEnum.DISCONNECTED, 'Unknown Connection Reason: ' + connectOptions.reason); - disconnect(); - break; + if (!options) { + updateStatus(App.StatusEnum.DISCONNECTED, 'Missing connection options'); + disconnect(); + return; } - webClient.options = {} as WebSocketConnectOptions; + // Strip credentials before handing off to session commands — they travel as + // separate function args so they can't accidentally ride along in the + // typed options object that flows downstream. + switch (options.reason) { + case App.WebSocketConnectReason.LOGIN: { + const { password, ...rest } = options; + updateStatus(App.StatusEnum.LOGGING_IN, 'Logging In...'); + if (getPasswordSalt) { + requestPasswordSalt(rest, password); + } else { + login(rest, password); + } + break; + } + case App.WebSocketConnectReason.REGISTER: { + const { password, ...rest } = options; + const passwordSalt = getPasswordSalt ? generateSalt() : null; + register(rest, password, passwordSalt); + break; + } + case App.WebSocketConnectReason.ACTIVATE_ACCOUNT: { + const { password, ...rest } = options; + if (getPasswordSalt) { + requestPasswordSalt(rest, password); + } else { + activate(rest, password); + } + break; + } + case App.WebSocketConnectReason.PASSWORD_RESET_REQUEST: + forgotPasswordRequest(options); + break; + case App.WebSocketConnectReason.PASSWORD_RESET_CHALLENGE: + forgotPasswordChallenge(options); + break; + case App.WebSocketConnectReason.PASSWORD_RESET: { + const { newPassword, ...rest } = options; + if (getPasswordSalt) { + requestPasswordSalt(rest, undefined, newPassword); + } else { + forgotPasswordReset(rest, newPassword); + } + break; + } + default: { + const { reason } = options as Enriched.WebSocketConnectOptions; + updateStatus(App.StatusEnum.DISCONNECTED, `Unknown Connection Reason: ${reason}`); + disconnect(); + break; + } + } + + webClient.options = null; SessionPersistence.updateInfo(serverName, serverVersion); } diff --git a/webclient/src/websocket/events/session/serverMessage.ts b/webclient/src/websocket/events/session/serverMessage.ts index f9e52aa7a..08162c823 100644 --- a/webclient/src/websocket/events/session/serverMessage.ts +++ b/webclient/src/websocket/events/session/serverMessage.ts @@ -1,6 +1,6 @@ +import type { Data } from '@app/types'; import { SessionPersistence } from '../../persistence'; -import { ServerMessageData } from './interfaces'; -export function serverMessage({ message }: ServerMessageData): void { +export function serverMessage({ message }: Data.Event_ServerMessage): void { SessionPersistence.serverMessage(message); } diff --git a/webclient/src/websocket/events/session/serverShutdown.ts b/webclient/src/websocket/events/session/serverShutdown.ts index cdda893a4..33fbfad75 100644 --- a/webclient/src/websocket/events/session/serverShutdown.ts +++ b/webclient/src/websocket/events/session/serverShutdown.ts @@ -1,7 +1,6 @@ +import type { Data } from '@app/types'; import { SessionPersistence } from '../../persistence'; -import { ServerShutdownData } from './interfaces'; - -export function serverShutdown(payload: ServerShutdownData): void { +export function serverShutdown(payload: Data.Event_ServerShutdown): void { SessionPersistence.serverShutdown(payload); } diff --git a/webclient/src/websocket/events/session/sessionEvents.spec.ts b/webclient/src/websocket/events/session/sessionEvents.spec.ts index 7b35ad135..37948043d 100644 --- a/webclient/src/websocket/events/session/sessionEvents.spec.ts +++ b/webclient/src/websocket/events/session/sessionEvents.spec.ts @@ -54,22 +54,8 @@ vi.mock('../../utils', () => ({ passwordSaltSupported: vi.fn().mockReturnValue(0), })); -import { WebSocketConnectReason } from 'types'; +import { App, Data, Enriched } from '@app/types'; import { create } from '@bufbuild/protobuf'; -import { Event_ConnectionClosed_CloseReason, Event_ConnectionClosedSchema } from 'generated/proto/event_connection_closed_pb'; -import { Event_GameJoinedSchema } from 'generated/proto/event_game_joined_pb'; -import { Event_NotifyUserSchema } from 'generated/proto/event_notify_user_pb'; -import { Event_ReplayAddedSchema } from 'generated/proto/event_replay_added_pb'; -import { Event_ServerCompleteListSchema } from 'generated/proto/event_server_complete_list_pb'; -import { Event_ServerMessageSchema } from 'generated/proto/event_server_message_pb'; -import { Event_ServerShutdownSchema } from 'generated/proto/event_server_shutdown_pb'; -import { Event_UserJoinedSchema } from 'generated/proto/event_user_joined_pb'; -import { Event_UserLeftSchema } from 'generated/proto/event_user_left_pb'; -import { Event_UserMessageSchema } from 'generated/proto/event_user_message_pb'; -import { Event_AddToListSchema } from 'generated/proto/event_add_to_list_pb'; -import { Event_RemoveFromListSchema } from 'generated/proto/event_remove_from_list_pb'; -import { Event_ListRoomsSchema } from 'generated/proto/event_list_rooms_pb'; -import { Event_ServerIdentificationSchema } from 'generated/proto/event_server_identification_pb'; import { SessionPersistence, RoomPersistence } from '../../persistence'; import webClient from '../../WebClient'; @@ -92,8 +78,9 @@ import { connectionClosed } from './connectionClosed'; import { serverIdentification } from './serverIdentification'; import { Mock } from 'vitest'; +const ConfigMock = Config as { -readonly [K in keyof typeof Config]: (typeof Config)[K] }; + beforeEach(() => { - vi.clearAllMocks(); (Utils.generateSalt as Mock).mockReturnValue('newSalt'); (Utils.passwordSaltSupported as Mock).mockReturnValue(0); }); @@ -104,7 +91,7 @@ beforeEach(() => { describe('gameJoined', () => { it('calls SessionPersistence.gameJoined', () => { - const data = create(Event_GameJoinedSchema, { playerId: 1 }); + const data = create(Data.Event_GameJoinedSchema, { playerId: 1 }); gameJoined(data); expect(SessionPersistence.gameJoined).toHaveBeenCalledWith(data); }); @@ -116,7 +103,7 @@ describe('gameJoined', () => { describe('notifyUser', () => { it('calls SessionPersistence.notifyUser', () => { - const data = create(Event_NotifyUserSchema, { warningReason: 'yo' }); + const data = create(Data.Event_NotifyUserSchema, { warningReason: 'yo' }); notifyUser(data); expect(SessionPersistence.notifyUser).toHaveBeenCalledWith(data); }); @@ -128,8 +115,9 @@ describe('notifyUser', () => { describe('replayAdded', () => { it('calls SessionPersistence.replayAdded with matchInfo', () => { - const data = create(Event_ReplayAddedSchema); - data.matchInfo = { gameId: 42 } as any; + const data = create(Data.Event_ReplayAddedSchema, { + matchInfo: create(Data.ServerInfo_ReplayMatchSchema, { gameId: 42 }), + }); replayAdded(data); expect(SessionPersistence.replayAdded).toHaveBeenCalledWith(data.matchInfo); }); @@ -141,7 +129,7 @@ describe('replayAdded', () => { describe('serverCompleteList', () => { it('calls SessionPersistence.updateUsers and RoomPersistence.updateRooms', () => { - const data = create(Event_ServerCompleteListSchema, { userList: [], roomList: [] }); + const data = create(Data.Event_ServerCompleteListSchema, { userList: [], roomList: [] }); serverCompleteList(data); expect(SessionPersistence.updateUsers).toHaveBeenCalledWith(data.userList); expect(RoomPersistence.updateRooms).toHaveBeenCalledWith(data.roomList); @@ -154,7 +142,7 @@ describe('serverCompleteList', () => { describe('serverMessage', () => { it('calls SessionPersistence.serverMessage with message', () => { - serverMessage(create(Event_ServerMessageSchema, { message: 'hello server' })); + serverMessage(create(Data.Event_ServerMessageSchema, { message: 'hello server' })); expect(SessionPersistence.serverMessage).toHaveBeenCalledWith('hello server'); }); }); @@ -165,7 +153,7 @@ describe('serverMessage', () => { describe('serverShutdown', () => { it('calls SessionPersistence.serverShutdown', () => { - const payload = create(Event_ServerShutdownSchema, { reason: 'maintenance' }); + const payload = create(Data.Event_ServerShutdownSchema, { reason: 'maintenance' }); serverShutdown(payload); expect(SessionPersistence.serverShutdown).toHaveBeenCalledWith(payload); }); @@ -177,8 +165,9 @@ describe('serverShutdown', () => { describe('userJoined', () => { it('calls SessionPersistence.userJoined with userInfo', () => { - const data = create(Event_UserJoinedSchema); - data.userInfo = { name: 'alice' } as any; + const data = create(Data.Event_UserJoinedSchema, { + userInfo: create(Data.ServerInfo_UserSchema, { name: 'alice' }), + }); userJoined(data); expect(SessionPersistence.userJoined).toHaveBeenCalledWith(data.userInfo); }); @@ -190,7 +179,7 @@ describe('userJoined', () => { describe('userLeft', () => { it('calls SessionPersistence.userLeft with name', () => { - userLeft(create(Event_UserLeftSchema, { name: 'bob' })); + userLeft(create(Data.Event_UserLeftSchema, { name: 'bob' })); expect(SessionPersistence.userLeft).toHaveBeenCalledWith('bob'); }); }); @@ -201,7 +190,7 @@ describe('userLeft', () => { describe('userMessage', () => { it('calls SessionPersistence.userMessage', () => { - const payload = create(Event_UserMessageSchema, { senderName: 'alice', message: 'hi' }); + const payload = create(Data.Event_UserMessageSchema, { senderName: 'alice', message: 'hi' }); userMessage(payload); expect(SessionPersistence.userMessage).toHaveBeenCalledWith(payload); }); @@ -217,21 +206,25 @@ describe('addToList', () => { }); it('buddy list → addToBuddyList', () => { - const data = create(Event_AddToListSchema, { listName: 'buddy' }); - data.userInfo = { name: 'alice' } as any; + const data = create(Data.Event_AddToListSchema, { + listName: 'buddy', + userInfo: create(Data.ServerInfo_UserSchema, { name: 'alice' }), + }); addToList(data); expect(SessionPersistence.addToBuddyList).toHaveBeenCalledWith(data.userInfo); }); it('ignore list → addToIgnoreList', () => { - const data = create(Event_AddToListSchema, { listName: 'ignore' }); - data.userInfo = { name: 'bob' } as any; + const data = create(Data.Event_AddToListSchema, { + listName: 'ignore', + userInfo: create(Data.ServerInfo_UserSchema, { name: 'bob' }), + }); addToList(data); expect(SessionPersistence.addToIgnoreList).toHaveBeenCalledWith(data.userInfo); }); it('unknown list → console.log', () => { - addToList(create(Event_AddToListSchema, { listName: 'unknown' })); + addToList(create(Data.Event_AddToListSchema, { listName: 'unknown' })); expect(logSpy).toHaveBeenCalled(); }); }); @@ -242,18 +235,18 @@ describe('addToList', () => { describe('removeFromList', () => { it('buddy list → removeFromBuddyList', () => { - removeFromList(create(Event_RemoveFromListSchema, { listName: 'buddy', userName: 'alice' })); + removeFromList(create(Data.Event_RemoveFromListSchema, { listName: 'buddy', userName: 'alice' })); expect(SessionPersistence.removeFromBuddyList).toHaveBeenCalledWith('alice'); }); it('ignore list → removeFromIgnoreList', () => { - removeFromList(create(Event_RemoveFromListSchema, { listName: 'ignore', userName: 'bob' })); + removeFromList(create(Data.Event_RemoveFromListSchema, { listName: 'ignore', userName: 'bob' })); expect(SessionPersistence.removeFromIgnoreList).toHaveBeenCalledWith('bob'); }); it('unknown list → console.log', () => { const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); - removeFromList(create(Event_RemoveFromListSchema, { listName: 'other', userName: 'x' })); + removeFromList(create(Data.Event_RemoveFromListSchema, { listName: 'other', userName: 'x' })); expect(logSpy).toHaveBeenCalled(); logSpy.mockRestore(); }); @@ -265,19 +258,26 @@ describe('removeFromList', () => { describe('listRooms', () => { it('calls RoomPersistence.updateRooms', () => { - listRooms(create(Event_ListRoomsSchema, { roomList: [] })); + listRooms(create(Data.Event_ListRoomsSchema, { roomList: [] })); expect(RoomPersistence.updateRooms).toHaveBeenCalledWith([]); }); it('does not call joinRoom when autojoinrooms is false', () => { - (Config as any).CLIENT_OPTIONS = { autojoinrooms: false }; - listRooms(create(Event_ListRoomsSchema, { roomList: [{ autoJoin: true, roomId: 1 }] as any[] })); + ConfigMock.CLIENT_OPTIONS = { autojoinrooms: false }; + listRooms(create(Data.Event_ListRoomsSchema, { + roomList: [create(Data.ServerInfo_RoomSchema, { autoJoin: true, roomId: 1 })] + })); expect(SessionCmds.joinRoom).not.toHaveBeenCalled(); }); it('calls joinRoom for autoJoin rooms when autojoinrooms is true', () => { - (Config as any).CLIENT_OPTIONS = { autojoinrooms: true }; - listRooms(create(Event_ListRoomsSchema, { roomList: [{ autoJoin: true, roomId: 2 }, { autoJoin: false, roomId: 3 }] as any[] })); + ConfigMock.CLIENT_OPTIONS = { autojoinrooms: true }; + listRooms(create(Data.Event_ListRoomsSchema, { + roomList: [ + create(Data.ServerInfo_RoomSchema, { autoJoin: true, roomId: 2 }), + create(Data.ServerInfo_RoomSchema, { autoJoin: false, roomId: 3 }) + ] + })); expect(SessionCmds.joinRoom).toHaveBeenCalledTimes(1); expect(SessionCmds.joinRoom).toHaveBeenCalledWith(2); }); @@ -289,12 +289,12 @@ describe('listRooms', () => { describe('connectionClosed', () => { it('uses reasonStr when provided', () => { - connectionClosed(create(Event_ConnectionClosedSchema, { reason: 0, reasonStr: 'custom' })); + connectionClosed(create(Data.Event_ConnectionClosedSchema, { reason: 0, reasonStr: 'custom' })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'custom'); }); it('USER_LIMIT_REACHED → specific message', () => { - connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.USER_LIMIT_REACHED })); + connectionClosed(create(Data.Event_ConnectionClosedSchema, { reason: Data.Event_ConnectionClosed_CloseReason.USER_LIMIT_REACHED })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith( expect.anything(), expect.stringContaining('maximum user capacity') @@ -302,42 +302,44 @@ describe('connectionClosed', () => { }); it('TOO_MANY_CONNECTIONS → specific message', () => { - connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.TOO_MANY_CONNECTIONS })); + connectionClosed(create(Data.Event_ConnectionClosedSchema, { reason: Data.Event_ConnectionClosed_CloseReason.TOO_MANY_CONNECTIONS })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('too many concurrent')); }); it('BANNED → specific message', () => { - connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED })); + connectionClosed(create(Data.Event_ConnectionClosedSchema, { reason: Data.Event_ConnectionClosed_CloseReason.BANNED })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); }); it('DEMOTED → specific message', () => { - connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.DEMOTED })); + connectionClosed(create(Data.Event_ConnectionClosedSchema, { reason: Data.Event_ConnectionClosed_CloseReason.DEMOTED })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('demoted')); }); it('SERVER_SHUTDOWN → specific message', () => { - connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.SERVER_SHUTDOWN })); + connectionClosed(create(Data.Event_ConnectionClosedSchema, { reason: Data.Event_ConnectionClosed_CloseReason.SERVER_SHUTDOWN })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('shutdown')); }); it('USERNAMEINVALID → specific message', () => { - connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.USERNAMEINVALID })); + connectionClosed(create(Data.Event_ConnectionClosedSchema, { reason: Data.Event_ConnectionClosed_CloseReason.USERNAMEINVALID })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('username')); }); it('LOGGEDINELSEWERE → specific message', () => { - connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.LOGGEDINELSEWERE })); + connectionClosed(create(Data.Event_ConnectionClosedSchema, { reason: Data.Event_ConnectionClosed_CloseReason.LOGGEDINELSEWERE })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('logged out')); }); it('OTHER → "Unknown reason"', () => { - connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.OTHER })); + connectionClosed(create(Data.Event_ConnectionClosedSchema, { reason: Data.Event_ConnectionClosed_CloseReason.OTHER })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'Unknown reason'); }); it('BANNED with valid positive endTime → shows formatted date', () => { - connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 1700000000 })); + connectionClosed(create(Data.Event_ConnectionClosedSchema, { + reason: Data.Event_ConnectionClosed_CloseReason.BANNED, endTime: 1700000000, + })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith( expect.anything(), expect.stringContaining('You are banned until') @@ -345,28 +347,30 @@ describe('connectionClosed', () => { }); it('BANNED with endTime = 0 → shows generic banned message', () => { - connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 0 })); + connectionClosed(create(Data.Event_ConnectionClosedSchema, { reason: Data.Event_ConnectionClosed_CloseReason.BANNED, endTime: 0 })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); }); it('BANNED with endTime = -1 → shows generic banned message', () => { - connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: -1 })); + connectionClosed(create(Data.Event_ConnectionClosedSchema, { reason: Data.Event_ConnectionClosed_CloseReason.BANNED, endTime: -1 })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); }); it('BANNED with endTime = NaN → shows generic banned message', () => { - connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: NaN })); + connectionClosed(create(Data.Event_ConnectionClosedSchema, { reason: Data.Event_ConnectionClosed_CloseReason.BANNED, endTime: NaN })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); }); it('BANNED with endTime = Infinity → shows generic banned message', () => { - connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: Infinity })); + connectionClosed(create(Data.Event_ConnectionClosedSchema, { + reason: Data.Event_ConnectionClosed_CloseReason.BANNED, endTime: Infinity, + })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned'); }); it('BANNED with reasonStr → uses reasonStr regardless of endTime', () => { - connectionClosed(create(Event_ConnectionClosedSchema, - { reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 0, reasonStr: 'custom ban reason' })); + connectionClosed(create(Data.Event_ConnectionClosedSchema, + { reason: Data.Event_ConnectionClosed_CloseReason.BANNED, endTime: 0, reasonStr: 'custom ban reason' })); expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'custom ban reason'); }); }); @@ -377,21 +381,21 @@ describe('connectionClosed', () => { describe('serverIdentification', () => { beforeEach(() => { - (Config as any).PROTOCOL_VERSION = 14; - (webClient as any).options = {}; + ConfigMock.PROTOCOL_VERSION = 14; + webClient.options = null; }); it('disconnects when protocolVersion mismatches', () => { - serverIdentification(create(Event_ServerIdentificationSchema, + serverIdentification(create(Data.Event_ServerIdentificationSchema, { serverName: 's', serverVersion: '1', protocolVersion: 99, serverOptions: 0 })); expect(SessionCmds.updateStatus).toHaveBeenCalled(); expect(SessionCmds.disconnect).toHaveBeenCalled(); }); it('LOGIN reason without salt → calls login with password as separate param', () => { - (webClient as any).options = { reason: WebSocketConnectReason.LOGIN, password: 'secret' }; + webClient.options = { host: 'h', port: '1', userName: 'u', reason: App.WebSocketConnectReason.LOGIN, password: 'secret' }; (Utils.passwordSaltSupported as Mock).mockReturnValue(0); - serverIdentification(create(Event_ServerIdentificationSchema, + serverIdentification(create(Data.Event_ServerIdentificationSchema, { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 })); expect(SessionCmds.login).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), @@ -400,9 +404,9 @@ describe('serverIdentification', () => { }); it('LOGIN reason with salt → calls requestPasswordSalt with password as separate param', () => { - (webClient as any).options = { reason: WebSocketConnectReason.LOGIN, password: 'secret' }; + webClient.options = { host: 'h', port: '1', userName: 'u', reason: App.WebSocketConnectReason.LOGIN, password: 'secret' }; (Utils.passwordSaltSupported as Mock).mockReturnValue(1); - serverIdentification(create(Event_ServerIdentificationSchema, + serverIdentification(create(Data.Event_ServerIdentificationSchema, { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 })); expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), @@ -411,9 +415,12 @@ describe('serverIdentification', () => { }); it('REGISTER reason without salt → calls register with password and null salt', () => { - (webClient as any).options = { reason: WebSocketConnectReason.REGISTER, password: 'secret' }; + webClient.options = { + host: 'h', port: '1', userName: 'u', email: 'e', country: 'US', realName: 'R', + reason: App.WebSocketConnectReason.REGISTER, password: 'secret', + }; (Utils.passwordSaltSupported as Mock).mockReturnValue(0); - serverIdentification(create(Event_ServerIdentificationSchema, + serverIdentification(create(Data.Event_ServerIdentificationSchema, { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 })); expect(SessionCmds.register).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), @@ -423,9 +430,12 @@ describe('serverIdentification', () => { }); it('REGISTER reason with salt → calls register with password and generated salt', () => { - (webClient as any).options = { reason: WebSocketConnectReason.REGISTER, password: 'secret' }; + webClient.options = { + host: 'h', port: '1', userName: 'u', email: 'e', country: 'US', realName: 'R', + reason: App.WebSocketConnectReason.REGISTER, password: 'secret', + }; (Utils.passwordSaltSupported as Mock).mockReturnValue(1); - serverIdentification(create(Event_ServerIdentificationSchema, + serverIdentification(create(Data.Event_ServerIdentificationSchema, { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 })); expect(SessionCmds.register).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), @@ -435,9 +445,12 @@ describe('serverIdentification', () => { }); it('ACTIVATE_ACCOUNT reason without salt → calls activate with password as separate param', () => { - (webClient as any).options = { reason: WebSocketConnectReason.ACTIVATE_ACCOUNT, password: 'secret' }; + webClient.options = { + host: 'h', port: '1', userName: 'u', token: 'tok', + reason: App.WebSocketConnectReason.ACTIVATE_ACCOUNT, password: 'secret', + }; (Utils.passwordSaltSupported as Mock).mockReturnValue(0); - serverIdentification(create(Event_ServerIdentificationSchema, + serverIdentification(create(Data.Event_ServerIdentificationSchema, { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 })); expect(SessionCmds.activate).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), @@ -446,9 +459,12 @@ describe('serverIdentification', () => { }); it('ACTIVATE_ACCOUNT reason with salt → calls requestPasswordSalt with password as separate param', () => { - (webClient as any).options = { reason: WebSocketConnectReason.ACTIVATE_ACCOUNT, password: 'secret' }; + webClient.options = { + host: 'h', port: '1', userName: 'u', token: 'tok', + reason: App.WebSocketConnectReason.ACTIVATE_ACCOUNT, password: 'secret', + }; (Utils.passwordSaltSupported as Mock).mockReturnValue(1); - serverIdentification(create(Event_ServerIdentificationSchema, + serverIdentification(create(Data.Event_ServerIdentificationSchema, { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 })); expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith( expect.not.objectContaining({ password: expect.anything() }), @@ -457,23 +473,26 @@ describe('serverIdentification', () => { }); it('PASSWORD_RESET_REQUEST reason → calls forgotPasswordRequest', () => { - (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET_REQUEST }; - serverIdentification(create(Event_ServerIdentificationSchema, + webClient.options = { host: 'h', port: '1', userName: 'u', reason: App.WebSocketConnectReason.PASSWORD_RESET_REQUEST }; + serverIdentification(create(Data.Event_ServerIdentificationSchema, { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 })); expect(SessionCmds.forgotPasswordRequest).toHaveBeenCalled(); }); it('PASSWORD_RESET_CHALLENGE reason → calls forgotPasswordChallenge', () => { - (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET_CHALLENGE }; - serverIdentification(create(Event_ServerIdentificationSchema, + webClient.options = { host: 'h', port: '1', userName: 'u', email: 'e', reason: App.WebSocketConnectReason.PASSWORD_RESET_CHALLENGE }; + serverIdentification(create(Data.Event_ServerIdentificationSchema, { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 })); expect(SessionCmds.forgotPasswordChallenge).toHaveBeenCalled(); }); it('PASSWORD_RESET reason without salt → calls forgotPasswordReset with newPassword as separate param', () => { - (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET, newPassword: 'newpw' }; + webClient.options = { + host: 'h', port: '1', userName: 'u', token: 'tok', + reason: App.WebSocketConnectReason.PASSWORD_RESET, newPassword: 'newpw', + }; (Utils.passwordSaltSupported as Mock).mockReturnValue(0); - serverIdentification(create(Event_ServerIdentificationSchema, + serverIdentification(create(Data.Event_ServerIdentificationSchema, { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 })); expect(SessionCmds.forgotPasswordReset).toHaveBeenCalledWith( expect.not.objectContaining({ newPassword: expect.anything() }), @@ -482,9 +501,12 @@ describe('serverIdentification', () => { }); it('PASSWORD_RESET reason with salt → calls requestPasswordSalt with newPassword as separate param', () => { - (webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET, newPassword: 'newpw' }; + webClient.options = { + host: 'h', port: '1', userName: 'u', token: 'tok', + reason: App.WebSocketConnectReason.PASSWORD_RESET, newPassword: 'newpw', + }; (Utils.passwordSaltSupported as Mock).mockReturnValue(1); - serverIdentification(create(Event_ServerIdentificationSchema, + serverIdentification(create(Data.Event_ServerIdentificationSchema, { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 })); expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith( expect.not.objectContaining({ newPassword: expect.anything() }), @@ -494,18 +516,18 @@ describe('serverIdentification', () => { }); it('unknown reason → updateStatus DISCONNECTED and disconnect', () => { - (webClient as any).options = { reason: 999 }; - serverIdentification(create(Event_ServerIdentificationSchema, + webClient.options = { host: 'h', port: '1', reason: 999 as App.WebSocketConnectReason } as Enriched.WebSocketConnectOptions; + serverIdentification(create(Data.Event_ServerIdentificationSchema, { serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 })); expect(SessionCmds.updateStatus).toHaveBeenCalled(); expect(SessionCmds.disconnect).toHaveBeenCalled(); }); - it('updates webClient.options to empty and calls SessionPersistence.updateInfo', () => { - (webClient as any).options = { reason: WebSocketConnectReason.LOGIN }; - serverIdentification(create(Event_ServerIdentificationSchema, + it('resets webClient.options and calls SessionPersistence.updateInfo', () => { + webClient.options = { host: 'h', port: '1', userName: 'u', reason: App.WebSocketConnectReason.LOGIN }; + serverIdentification(create(Data.Event_ServerIdentificationSchema, { serverName: 'myServer', serverVersion: '2.0', protocolVersion: 14, serverOptions: 0 })); expect(SessionPersistence.updateInfo).toHaveBeenCalledWith('myServer', '2.0'); - expect((webClient as any).options).toEqual({}); + expect(webClient.options).toBeNull(); }); }); diff --git a/webclient/src/websocket/events/session/userJoined.ts b/webclient/src/websocket/events/session/userJoined.ts index cb512db60..99fde6008 100644 --- a/webclient/src/websocket/events/session/userJoined.ts +++ b/webclient/src/websocket/events/session/userJoined.ts @@ -1,6 +1,6 @@ +import type { Data } from '@app/types'; import { SessionPersistence } from '../../persistence'; -import { UserJoinedData } from './interfaces'; -export function userJoined({ userInfo }: UserJoinedData): void { +export function userJoined({ userInfo }: Data.Event_UserJoined): void { SessionPersistence.userJoined(userInfo); } diff --git a/webclient/src/websocket/events/session/userLeft.ts b/webclient/src/websocket/events/session/userLeft.ts index 9e00e59e1..83d8404ed 100644 --- a/webclient/src/websocket/events/session/userLeft.ts +++ b/webclient/src/websocket/events/session/userLeft.ts @@ -1,6 +1,6 @@ +import type { Data } from '@app/types'; import { SessionPersistence } from '../../persistence'; -import { UserLeftData } from './interfaces'; -export function userLeft({ name }: UserLeftData): void { +export function userLeft({ name }: Data.Event_UserLeft): void { SessionPersistence.userLeft(name); } diff --git a/webclient/src/websocket/events/session/userMessage.ts b/webclient/src/websocket/events/session/userMessage.ts index bff08460b..073c22de6 100644 --- a/webclient/src/websocket/events/session/userMessage.ts +++ b/webclient/src/websocket/events/session/userMessage.ts @@ -1,8 +1,6 @@ +import type { Data } from '@app/types'; import { SessionPersistence } from '../../persistence'; -import { UserMessageData } from './interfaces'; - - -export function userMessage(payload: UserMessageData): void { +export function userMessage(payload: Data.Event_UserMessage): void { SessionPersistence.userMessage(payload); } diff --git a/webclient/src/websocket/persistence/AdminPersistence.spec.ts b/webclient/src/websocket/persistence/AdminPersistence.spec.ts index 6eb54f7e1..6f08dea8b 100644 --- a/webclient/src/websocket/persistence/AdminPersistence.spec.ts +++ b/webclient/src/websocket/persistence/AdminPersistence.spec.ts @@ -1,4 +1,4 @@ -vi.mock('store', () => ({ +vi.mock('@app/store', () => ({ ServerDispatch: { adjustMod: vi.fn(), reloadConfig: vi.fn(), @@ -8,11 +8,7 @@ vi.mock('store', () => ({ })); import { AdminPersistence } from './AdminPersistence'; -import { ServerDispatch } from 'store'; - -beforeEach(() => { - vi.clearAllMocks(); -}); +import { ServerDispatch } from '@app/store'; describe('AdminPersistence', () => { it('adjustMod passes userName, shouldBeMod, shouldBeJudge', () => { diff --git a/webclient/src/websocket/persistence/AdminPersistence.ts b/webclient/src/websocket/persistence/AdminPersistence.ts index 9552d8abf..0f93fef9d 100644 --- a/webclient/src/websocket/persistence/AdminPersistence.ts +++ b/webclient/src/websocket/persistence/AdminPersistence.ts @@ -1,4 +1,4 @@ -import { ServerDispatch } from 'store'; +import { ServerDispatch } from '@app/store'; export class AdminPersistence { static adjustMod(userName: string, shouldBeMod: boolean, shouldBeJudge: boolean) { diff --git a/webclient/src/websocket/persistence/GamePersistence.spec.ts b/webclient/src/websocket/persistence/GamePersistence.spec.ts index 50d8c6539..996faca09 100644 --- a/webclient/src/websocket/persistence/GamePersistence.spec.ts +++ b/webclient/src/websocket/persistence/GamePersistence.spec.ts @@ -1,7 +1,7 @@ import { create } from '@bufbuild/protobuf'; import { GamePersistence } from './GamePersistence'; -vi.mock('store', () => ({ +vi.mock('@app/store', () => ({ GameDispatch: { gameStateChanged: vi.fn(), playerJoined: vi.fn(), @@ -35,40 +35,19 @@ vi.mock('store', () => ({ }, })); -import { Event_GameStateChangedSchema } from 'generated/proto/event_game_state_changed_pb'; -import { Event_MoveCardSchema } from 'generated/proto/event_move_card_pb'; -import { Event_FlipCardSchema } from 'generated/proto/event_flip_card_pb'; -import { Event_DestroyCardSchema } from 'generated/proto/event_destroy_card_pb'; -import { Event_AttachCardSchema } from 'generated/proto/event_attach_card_pb'; -import { Event_CreateTokenSchema } from 'generated/proto/event_create_token_pb'; -import { Event_SetCardAttrSchema } from 'generated/proto/event_set_card_attr_pb'; -import { Event_SetCardCounterSchema } from 'generated/proto/event_set_card_counter_pb'; -import { Event_CreateArrowSchema } from 'generated/proto/event_create_arrow_pb'; -import { Event_DeleteArrowSchema } from 'generated/proto/event_delete_arrow_pb'; -import { Event_CreateCounterSchema } from 'generated/proto/event_create_counter_pb'; -import { Event_SetCounterSchema } from 'generated/proto/event_set_counter_pb'; -import { Event_DelCounterSchema } from 'generated/proto/event_del_counter_pb'; -import { Event_DrawCardsSchema } from 'generated/proto/event_draw_cards_pb'; -import { Event_RevealCardsSchema } from 'generated/proto/event_reveal_cards_pb'; -import { Event_ShuffleSchema } from 'generated/proto/event_shuffle_pb'; -import { Event_RollDieSchema } from 'generated/proto/event_roll_die_pb'; -import { Event_DumpZoneSchema } from 'generated/proto/event_dump_zone_pb'; -import { Event_ChangeZonePropertiesSchema } from 'generated/proto/event_change_zone_properties_pb'; -import { ServerInfo_PlayerPropertiesSchema } from 'generated/proto/serverinfo_playerproperties_pb'; +import { Data } from '@app/types'; -import { GameDispatch } from 'store'; - -beforeEach(() => vi.clearAllMocks()); +import { GameDispatch } from '@app/store'; describe('GamePersistence', () => { it('gameStateChanged dispatches via GameDispatch', () => { - const data = create(Event_GameStateChangedSchema, { playerList: [] }); + const data = create(Data.Event_GameStateChangedSchema, { playerList: [] }); GamePersistence.gameStateChanged(5, data); expect(GameDispatch.gameStateChanged).toHaveBeenCalledWith(5, data); }); it('playerJoined dispatches via GameDispatch', () => { - const data = create(ServerInfo_PlayerPropertiesSchema, { playerId: 1 }); + const data = create(Data.ServerInfo_PlayerPropertiesSchema, { playerId: 1 }); GamePersistence.playerJoined(5, data); expect(GameDispatch.playerJoined).toHaveBeenCalledWith(5, data); }); @@ -79,7 +58,7 @@ describe('GamePersistence', () => { }); it('playerPropertiesChanged dispatches via GameDispatch', () => { - const props = create(ServerInfo_PlayerPropertiesSchema, { playerId: 2 }); + const props = create(Data.ServerInfo_PlayerPropertiesSchema, { playerId: 2 }); GamePersistence.playerPropertiesChanged(5, 2, props); expect(GameDispatch.playerPropertiesChanged).toHaveBeenCalledWith(5, 2, props); }); @@ -105,97 +84,97 @@ describe('GamePersistence', () => { }); it('cardMoved dispatches via GameDispatch', () => { - const data = create(Event_MoveCardSchema, { cardId: 3 }); + const data = create(Data.Event_MoveCardSchema, { cardId: 3 }); GamePersistence.cardMoved(5, 1, data); expect(GameDispatch.cardMoved).toHaveBeenCalledWith(5, 1, data); }); it('cardFlipped dispatches via GameDispatch', () => { - const data = create(Event_FlipCardSchema, { cardId: 3 }); + const data = create(Data.Event_FlipCardSchema, { cardId: 3 }); GamePersistence.cardFlipped(5, 1, data); expect(GameDispatch.cardFlipped).toHaveBeenCalledWith(5, 1, data); }); it('cardDestroyed dispatches via GameDispatch', () => { - const data = create(Event_DestroyCardSchema, { cardId: 3 }); + const data = create(Data.Event_DestroyCardSchema, { cardId: 3 }); GamePersistence.cardDestroyed(5, 1, data); expect(GameDispatch.cardDestroyed).toHaveBeenCalledWith(5, 1, data); }); it('cardAttached dispatches via GameDispatch', () => { - const data = create(Event_AttachCardSchema, { cardId: 3 }); + const data = create(Data.Event_AttachCardSchema, { cardId: 3 }); GamePersistence.cardAttached(5, 1, data); expect(GameDispatch.cardAttached).toHaveBeenCalledWith(5, 1, data); }); it('tokenCreated dispatches via GameDispatch', () => { - const data = create(Event_CreateTokenSchema, { cardId: 3 }); + const data = create(Data.Event_CreateTokenSchema, { cardId: 3 }); GamePersistence.tokenCreated(5, 1, data); expect(GameDispatch.tokenCreated).toHaveBeenCalledWith(5, 1, data); }); it('cardAttrChanged dispatches via GameDispatch', () => { - const data = create(Event_SetCardAttrSchema, { cardId: 3 }); + const data = create(Data.Event_SetCardAttrSchema, { cardId: 3 }); GamePersistence.cardAttrChanged(5, 1, data); expect(GameDispatch.cardAttrChanged).toHaveBeenCalledWith(5, 1, data); }); it('cardCounterChanged dispatches via GameDispatch', () => { - const data = create(Event_SetCardCounterSchema, { cardId: 3 }); + const data = create(Data.Event_SetCardCounterSchema, { cardId: 3 }); GamePersistence.cardCounterChanged(5, 1, data); expect(GameDispatch.cardCounterChanged).toHaveBeenCalledWith(5, 1, data); }); it('arrowCreated dispatches via GameDispatch', () => { - const data = create(Event_CreateArrowSchema, {}); + const data = create(Data.Event_CreateArrowSchema, {}); GamePersistence.arrowCreated(5, 1, data); expect(GameDispatch.arrowCreated).toHaveBeenCalledWith(5, 1, data); }); it('arrowDeleted dispatches via GameDispatch', () => { - const data = create(Event_DeleteArrowSchema, { arrowId: 9 }); + const data = create(Data.Event_DeleteArrowSchema, { arrowId: 9 }); GamePersistence.arrowDeleted(5, 1, data); expect(GameDispatch.arrowDeleted).toHaveBeenCalledWith(5, 1, data); }); it('counterCreated dispatches via GameDispatch', () => { - const data = create(Event_CreateCounterSchema, {}); + const data = create(Data.Event_CreateCounterSchema, {}); GamePersistence.counterCreated(5, 1, data); expect(GameDispatch.counterCreated).toHaveBeenCalledWith(5, 1, data); }); it('counterSet dispatches via GameDispatch', () => { - const data = create(Event_SetCounterSchema, { counterId: 1, value: 20 }); + const data = create(Data.Event_SetCounterSchema, { counterId: 1, value: 20 }); GamePersistence.counterSet(5, 1, data); expect(GameDispatch.counterSet).toHaveBeenCalledWith(5, 1, data); }); it('counterDeleted dispatches via GameDispatch', () => { - const data = create(Event_DelCounterSchema, { counterId: 1 }); + const data = create(Data.Event_DelCounterSchema, { counterId: 1 }); GamePersistence.counterDeleted(5, 1, data); expect(GameDispatch.counterDeleted).toHaveBeenCalledWith(5, 1, data); }); it('cardsDrawn dispatches via GameDispatch', () => { - const data = create(Event_DrawCardsSchema, { number: 2, cards: [] }); + const data = create(Data.Event_DrawCardsSchema, { number: 2, cards: [] }); GamePersistence.cardsDrawn(5, 1, data); expect(GameDispatch.cardsDrawn).toHaveBeenCalledWith(5, 1, data); }); it('cardsRevealed dispatches via GameDispatch', () => { - const data = create(Event_RevealCardsSchema, { zoneName: 'hand', cards: [] }); + const data = create(Data.Event_RevealCardsSchema, { zoneName: 'hand', cards: [] }); GamePersistence.cardsRevealed(5, 1, data); expect(GameDispatch.cardsRevealed).toHaveBeenCalledWith(5, 1, data); }); it('zoneShuffled dispatches via GameDispatch', () => { - const data = create(Event_ShuffleSchema, { zoneName: 'deck' }); + const data = create(Data.Event_ShuffleSchema, { zoneName: 'deck' }); GamePersistence.zoneShuffled(5, 1, data); expect(GameDispatch.zoneShuffled).toHaveBeenCalledWith(5, 1, data); }); it('dieRolled dispatches via GameDispatch', () => { - const data = create(Event_RollDieSchema, { sides: 6, value: 4 }); + const data = create(Data.Event_RollDieSchema, { sides: 6, value: 4 }); GamePersistence.dieRolled(5, 1, data); expect(GameDispatch.dieRolled).toHaveBeenCalledWith(5, 1, data); }); @@ -216,13 +195,13 @@ describe('GamePersistence', () => { }); it('zoneDumped dispatches via GameDispatch', () => { - const data = create(Event_DumpZoneSchema, { zoneName: 'hand' }); + const data = create(Data.Event_DumpZoneSchema, { zoneName: 'hand' }); GamePersistence.zoneDumped(5, 1, data); expect(GameDispatch.zoneDumped).toHaveBeenCalledWith(5, 1, data); }); it('zonePropertiesChanged dispatches via GameDispatch', () => { - const data = create(Event_ChangeZonePropertiesSchema, { zoneName: 'hand', alwaysRevealTopCard: true }); + const data = create(Data.Event_ChangeZonePropertiesSchema, { zoneName: 'hand', alwaysRevealTopCard: true }); GamePersistence.zonePropertiesChanged(5, 1, data); expect(GameDispatch.zonePropertiesChanged).toHaveBeenCalledWith(5, 1, data); }); diff --git a/webclient/src/websocket/persistence/GamePersistence.ts b/webclient/src/websocket/persistence/GamePersistence.ts index e21a9db45..fd31aa44a 100644 --- a/webclient/src/websocket/persistence/GamePersistence.ts +++ b/webclient/src/websocket/persistence/GamePersistence.ts @@ -1,31 +1,12 @@ -import { GameDispatch } from 'store'; -import type { Event_AttachCard } from 'generated/proto/event_attach_card_pb'; -import type { Event_ChangeZoneProperties } from 'generated/proto/event_change_zone_properties_pb'; -import type { Event_CreateArrow } from 'generated/proto/event_create_arrow_pb'; -import type { Event_CreateCounter } from 'generated/proto/event_create_counter_pb'; -import type { Event_CreateToken } from 'generated/proto/event_create_token_pb'; -import type { Event_DelCounter } from 'generated/proto/event_del_counter_pb'; -import type { Event_DeleteArrow } from 'generated/proto/event_delete_arrow_pb'; -import type { Event_DestroyCard } from 'generated/proto/event_destroy_card_pb'; -import type { Event_DrawCards } from 'generated/proto/event_draw_cards_pb'; -import type { Event_DumpZone } from 'generated/proto/event_dump_zone_pb'; -import type { Event_FlipCard } from 'generated/proto/event_flip_card_pb'; -import type { Event_GameStateChanged } from 'generated/proto/event_game_state_changed_pb'; -import type { Event_MoveCard } from 'generated/proto/event_move_card_pb'; -import type { ServerInfo_PlayerProperties } from 'generated/proto/serverinfo_playerproperties_pb'; -import type { Event_RevealCards } from 'generated/proto/event_reveal_cards_pb'; -import type { Event_RollDie } from 'generated/proto/event_roll_die_pb'; -import type { Event_SetCardAttr } from 'generated/proto/event_set_card_attr_pb'; -import type { Event_SetCardCounter } from 'generated/proto/event_set_card_counter_pb'; -import type { Event_SetCounter } from 'generated/proto/event_set_counter_pb'; -import type { Event_Shuffle } from 'generated/proto/event_shuffle_pb'; +import { GameDispatch } from '@app/store'; +import { Data } from '@app/types'; export class GamePersistence { - static gameStateChanged(gameId: number, data: Event_GameStateChanged): void { + static gameStateChanged(gameId: number, data: Data.Event_GameStateChanged): void { GameDispatch.gameStateChanged(gameId, data); } - static playerJoined(gameId: number, playerProperties: ServerInfo_PlayerProperties): void { + static playerJoined(gameId: number, playerProperties: Data.ServerInfo_PlayerProperties): void { GameDispatch.playerJoined(gameId, playerProperties); } @@ -33,7 +14,7 @@ export class GamePersistence { GameDispatch.playerLeft(gameId, playerId, reason); } - static playerPropertiesChanged(gameId: number, playerId: number, properties: ServerInfo_PlayerProperties): void { + static playerPropertiesChanged(gameId: number, playerId: number, properties: Data.ServerInfo_PlayerProperties): void { GameDispatch.playerPropertiesChanged(gameId, playerId, properties); } @@ -53,67 +34,67 @@ export class GamePersistence { GameDispatch.gameSay(gameId, playerId, message); } - static cardMoved(gameId: number, playerId: number, data: Event_MoveCard): void { + static cardMoved(gameId: number, playerId: number, data: Data.Event_MoveCard): void { GameDispatch.cardMoved(gameId, playerId, data); } - static cardFlipped(gameId: number, playerId: number, data: Event_FlipCard): void { + static cardFlipped(gameId: number, playerId: number, data: Data.Event_FlipCard): void { GameDispatch.cardFlipped(gameId, playerId, data); } - static cardDestroyed(gameId: number, playerId: number, data: Event_DestroyCard): void { + static cardDestroyed(gameId: number, playerId: number, data: Data.Event_DestroyCard): void { GameDispatch.cardDestroyed(gameId, playerId, data); } - static cardAttached(gameId: number, playerId: number, data: Event_AttachCard): void { + static cardAttached(gameId: number, playerId: number, data: Data.Event_AttachCard): void { GameDispatch.cardAttached(gameId, playerId, data); } - static tokenCreated(gameId: number, playerId: number, data: Event_CreateToken): void { + static tokenCreated(gameId: number, playerId: number, data: Data.Event_CreateToken): void { GameDispatch.tokenCreated(gameId, playerId, data); } - static cardAttrChanged(gameId: number, playerId: number, data: Event_SetCardAttr): void { + static cardAttrChanged(gameId: number, playerId: number, data: Data.Event_SetCardAttr): void { GameDispatch.cardAttrChanged(gameId, playerId, data); } - static cardCounterChanged(gameId: number, playerId: number, data: Event_SetCardCounter): void { + static cardCounterChanged(gameId: number, playerId: number, data: Data.Event_SetCardCounter): void { GameDispatch.cardCounterChanged(gameId, playerId, data); } - static arrowCreated(gameId: number, playerId: number, data: Event_CreateArrow): void { + static arrowCreated(gameId: number, playerId: number, data: Data.Event_CreateArrow): void { GameDispatch.arrowCreated(gameId, playerId, data); } - static arrowDeleted(gameId: number, playerId: number, data: Event_DeleteArrow): void { + static arrowDeleted(gameId: number, playerId: number, data: Data.Event_DeleteArrow): void { GameDispatch.arrowDeleted(gameId, playerId, data); } - static counterCreated(gameId: number, playerId: number, data: Event_CreateCounter): void { + static counterCreated(gameId: number, playerId: number, data: Data.Event_CreateCounter): void { GameDispatch.counterCreated(gameId, playerId, data); } - static counterSet(gameId: number, playerId: number, data: Event_SetCounter): void { + static counterSet(gameId: number, playerId: number, data: Data.Event_SetCounter): void { GameDispatch.counterSet(gameId, playerId, data); } - static counterDeleted(gameId: number, playerId: number, data: Event_DelCounter): void { + static counterDeleted(gameId: number, playerId: number, data: Data.Event_DelCounter): void { GameDispatch.counterDeleted(gameId, playerId, data); } - static cardsDrawn(gameId: number, playerId: number, data: Event_DrawCards): void { + static cardsDrawn(gameId: number, playerId: number, data: Data.Event_DrawCards): void { GameDispatch.cardsDrawn(gameId, playerId, data); } - static cardsRevealed(gameId: number, playerId: number, data: Event_RevealCards): void { + static cardsRevealed(gameId: number, playerId: number, data: Data.Event_RevealCards): void { GameDispatch.cardsRevealed(gameId, playerId, data); } - static zoneShuffled(gameId: number, playerId: number, data: Event_Shuffle): void { + static zoneShuffled(gameId: number, playerId: number, data: Data.Event_Shuffle): void { GameDispatch.zoneShuffled(gameId, playerId, data); } - static dieRolled(gameId: number, playerId: number, data: Event_RollDie): void { + static dieRolled(gameId: number, playerId: number, data: Data.Event_RollDie): void { GameDispatch.dieRolled(gameId, playerId, data); } @@ -129,11 +110,11 @@ export class GamePersistence { GameDispatch.turnReversed(gameId, reversed); } - static zoneDumped(gameId: number, playerId: number, data: Event_DumpZone): void { + static zoneDumped(gameId: number, playerId: number, data: Data.Event_DumpZone): void { GameDispatch.zoneDumped(gameId, playerId, data); } - static zonePropertiesChanged(gameId: number, playerId: number, data: Event_ChangeZoneProperties): void { + static zonePropertiesChanged(gameId: number, playerId: number, data: Data.Event_ChangeZoneProperties): void { GameDispatch.zonePropertiesChanged(gameId, playerId, data); } } diff --git a/webclient/src/websocket/persistence/ModeratorPersistence.spec.ts b/webclient/src/websocket/persistence/ModeratorPersistence.spec.ts index bd2971e10..4519a5a31 100644 --- a/webclient/src/websocket/persistence/ModeratorPersistence.spec.ts +++ b/webclient/src/websocket/persistence/ModeratorPersistence.spec.ts @@ -1,4 +1,4 @@ -vi.mock('store', () => ({ +vi.mock('@app/store', () => ({ ServerDispatch: { banFromServer: vi.fn(), banHistory: vi.fn(), @@ -14,11 +14,9 @@ vi.mock('store', () => ({ })); import { ModeratorPersistence } from './ModeratorPersistence'; -import { ServerDispatch } from 'store'; - -beforeEach(() => { - vi.clearAllMocks(); -}); +import { ServerDispatch } from '@app/store'; +import { create } from '@bufbuild/protobuf'; +import { Data } from '@app/types'; describe('ModeratorPersistence', () => { it('banFromServer passes userName', () => { @@ -32,7 +30,7 @@ describe('ModeratorPersistence', () => { }); it('viewLogs dispatches raw logs', () => { - const logs = [{ targetType: 'room' }] as any; + const logs = [create(Data.ServerInfo_ChatMessageSchema, { targetType: 'room' })]; ModeratorPersistence.viewLogs(logs); expect(ServerDispatch.viewLogs).toHaveBeenCalledWith(logs); }); diff --git a/webclient/src/websocket/persistence/ModeratorPersistence.ts b/webclient/src/websocket/persistence/ModeratorPersistence.ts index bb50fe726..2a640a6f3 100644 --- a/webclient/src/websocket/persistence/ModeratorPersistence.ts +++ b/webclient/src/websocket/persistence/ModeratorPersistence.ts @@ -1,27 +1,24 @@ -import { ServerDispatch } from 'store'; -import type { ServerInfo_Ban } from 'generated/proto/serverinfo_ban_pb'; -import type { ServerInfo_ChatMessage } from 'generated/proto/serverinfo_chat_message_pb'; -import type { ServerInfo_Warning } from 'generated/proto/serverinfo_warning_pb'; -import type { Response_WarnList } from 'generated/proto/response_warn_list_pb'; +import { ServerDispatch } from '@app/store'; +import { Data } from '@app/types'; export class ModeratorPersistence { static banFromServer(userName: string): void { ServerDispatch.banFromServer(userName); } - static banHistory(userName: string, banHistory: ServerInfo_Ban[]): void { + static banHistory(userName: string, banHistory: Data.ServerInfo_Ban[]): void { ServerDispatch.banHistory(userName, banHistory); } - static viewLogs(logs: ServerInfo_ChatMessage[]): void { + static viewLogs(logs: Data.ServerInfo_ChatMessage[]): void { ServerDispatch.viewLogs(logs); } - static warnHistory(userName: string, warnHistory: ServerInfo_Warning[]): void { + static warnHistory(userName: string, warnHistory: Data.ServerInfo_Warning[]): void { ServerDispatch.warnHistory(userName, warnHistory); } - static warnListOptions(warnList: Response_WarnList[]): void { + static warnListOptions(warnList: Data.Response_WarnList[]): void { ServerDispatch.warnListOptions(warnList); } diff --git a/webclient/src/websocket/persistence/RoomPersistence.spec.ts b/webclient/src/websocket/persistence/RoomPersistence.spec.ts index 61bc5e7f0..7ce780e09 100644 --- a/webclient/src/websocket/persistence/RoomPersistence.spec.ts +++ b/webclient/src/websocket/persistence/RoomPersistence.spec.ts @@ -1,4 +1,4 @@ -vi.mock('store', () => ({ +vi.mock('@app/store', () => ({ RoomsDispatch: { clearStore: vi.fn(), joinRoom: vi.fn(), @@ -15,11 +15,9 @@ vi.mock('store', () => ({ })); import { RoomPersistence } from './RoomPersistence'; -import { RoomsDispatch } from 'store'; - -beforeEach(() => { - vi.clearAllMocks(); -}); +import { RoomsDispatch } from '@app/store'; +import { create } from '@bufbuild/protobuf'; +import { Data, Enriched } from '@app/types'; describe('RoomPersistence', () => { it('clearStore -> RoomsDispatch.clearStore', () => { @@ -28,7 +26,7 @@ describe('RoomPersistence', () => { }); it('joinRoom dispatches raw roomInfo', () => { - const room = { roomId: 1 } as any; + const room = create(Data.ServerInfo_RoomSchema, { roomId: 1 }); RoomPersistence.joinRoom(room); expect(RoomsDispatch.joinRoom).toHaveBeenCalledWith(room); }); @@ -39,37 +37,37 @@ describe('RoomPersistence', () => { }); it('updateRooms dispatches raw rooms', () => { - const rooms = [{ roomId: 1 }] as any; + const rooms = [create(Data.ServerInfo_RoomSchema, { roomId: 1 })]; RoomPersistence.updateRooms(rooms); expect(RoomsDispatch.updateRooms).toHaveBeenCalledWith(rooms); }); describe('updateGames', () => { it('dispatches raw game list', () => { - const game = { gameTypes: [1] } as any; + const game = create(Data.ServerInfo_GameSchema, { gameTypes: [1] }); RoomPersistence.updateGames(1, [game]); expect(RoomsDispatch.updateGames).toHaveBeenCalledWith(1, [game]); }); it('returns without error when gameList is empty', () => { expect(() => RoomPersistence.updateGames(1, [])).not.toThrow(); - expect(RoomsDispatch.updateGames).not.toHaveBeenCalled(); + expect(RoomsDispatch.updateGames).toHaveBeenCalledWith(1, []); }); it('returns without error when gameList is null', () => { - expect(() => RoomPersistence.updateGames(1, null as any)).not.toThrow(); - expect(RoomsDispatch.updateGames).not.toHaveBeenCalled(); + expect(() => RoomPersistence.updateGames(1, null as unknown as Data.ServerInfo_Game[])).not.toThrow(); + expect(RoomsDispatch.updateGames).toHaveBeenCalledWith(1, null); }); }); it('addMessage dispatches without pre-normalizing', () => { - const msg = { name: 'alice', message: 'hi' } as any; + const msg: Enriched.Message = { ...create(Data.Event_RoomSaySchema), timeReceived: 0, name: 'alice', message: 'hi' }; RoomPersistence.addMessage(1, msg); expect(RoomsDispatch.addMessage).toHaveBeenCalledWith(1, msg); }); it('userJoined -> RoomsDispatch.userJoined', () => { - const user = { name: 'bob' } as any; + const user = create(Data.ServerInfo_UserSchema, { name: 'bob' }); RoomPersistence.userJoined(1, user); expect(RoomsDispatch.userJoined).toHaveBeenCalledWith(1, user); }); diff --git a/webclient/src/websocket/persistence/RoomPersistence.ts b/webclient/src/websocket/persistence/RoomPersistence.ts index 6325bc20a..21969598c 100644 --- a/webclient/src/websocket/persistence/RoomPersistence.ts +++ b/webclient/src/websocket/persistence/RoomPersistence.ts @@ -1,15 +1,12 @@ -import { RoomsDispatch } from 'store'; -import { Message } from 'types'; -import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; -import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; -import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb'; +import { RoomsDispatch } from '@app/store'; +import { Data, Enriched } from '@app/types'; export class RoomPersistence { static clearStore() { RoomsDispatch.clearStore(); } - static joinRoom(roomInfo: ServerInfo_Room) { + static joinRoom(roomInfo: Data.ServerInfo_Room) { RoomsDispatch.joinRoom(roomInfo); } @@ -17,26 +14,19 @@ export class RoomPersistence { RoomsDispatch.leaveRoom(roomId); } - static updateRooms(rooms: ServerInfo_Room[]) { + static updateRooms(rooms: Data.ServerInfo_Room[]) { RoomsDispatch.updateRooms(rooms); } - static updateGames(roomId: number, gameList: ServerInfo_Game[]) { - // Guard: the server never sends an empty gameList to signal "clear all games". - // An empty array here means no game updates — skip the dispatch to avoid - // unnecessarily overwriting the existing game list with an empty one. - if (!gameList?.length) { - return; - } - + static updateGames(roomId: number, gameList: Data.ServerInfo_Game[]) { RoomsDispatch.updateGames(roomId, gameList); } - static addMessage(roomId: number, message: Message) { + static addMessage(roomId: number, message: Enriched.Message) { RoomsDispatch.addMessage(roomId, message); } - static userJoined(roomId: number, user: ServerInfo_User) { + static userJoined(roomId: number, user: Data.ServerInfo_User) { RoomsDispatch.userJoined(roomId, user); } diff --git a/webclient/src/websocket/persistence/SessionPersistence.spec.ts b/webclient/src/websocket/persistence/SessionPersistence.spec.ts index 9dbe4a10f..16f59475c 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.spec.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.spec.ts @@ -1,11 +1,10 @@ -vi.mock('store', () => ({ +vi.mock('@app/store', () => ({ ServerDispatch: { initialized: vi.fn(), connectionAttempted: vi.fn(), clearStore: vi.fn(), loginSuccessful: vi.fn(), loginFailed: vi.fn(), - connectionClosed: vi.fn(), connectionFailed: vi.fn(), testConnectionSuccessful: vi.fn(), testConnectionFailed: vi.fn(), @@ -61,18 +60,19 @@ vi.mock('store', () => ({ }, })); -vi.mock('websocket/utils', () => ({ +vi.mock('../utils', () => ({ sanitizeHtml: vi.fn((msg: string) => `sanitized:${msg}`), })); import { SessionPersistence } from './SessionPersistence'; -import { ServerDispatch, GameDispatch } from 'store'; -import { sanitizeHtml } from 'websocket/utils'; -import { StatusEnum } from 'types'; +import { ServerDispatch, GameDispatch } from '@app/store'; +import { sanitizeHtml } from '../utils'; +import { App, Data, Enriched } from '@app/types'; +import { create } from '@bufbuild/protobuf'; + import { Mock } from 'vitest'; beforeEach(() => { - vi.clearAllMocks(); (sanitizeHtml as Mock).mockImplementation((msg: string) => `sanitized:${msg}`); }); @@ -88,7 +88,7 @@ describe('SessionPersistence', () => { }); it('loginSuccessful passes options', () => { - const opts = { userName: 'alice' } as any; + const opts: Enriched.LoginSuccessContext = { hashedPassword: 'hash' }; SessionPersistence.loginSuccessful(opts); expect(ServerDispatch.loginSuccessful).toHaveBeenCalledWith(opts); }); @@ -98,11 +98,6 @@ describe('SessionPersistence', () => { expect(ServerDispatch.loginFailed).toHaveBeenCalled(); }); - it('connectionClosed passes reason', () => { - SessionPersistence.connectionClosed(3); - expect(ServerDispatch.connectionClosed).toHaveBeenCalledWith(3); - }); - it('connectionFailed -> ServerDispatch.connectionFailed', () => { SessionPersistence.connectionFailed(); expect(ServerDispatch.connectionFailed).toHaveBeenCalled(); @@ -124,7 +119,7 @@ describe('SessionPersistence', () => { }); it('addToBuddyList passes user', () => { - const user = { name: 'bob' } as any; + const user = create(Data.ServerInfo_UserSchema, { name: 'bob' }); SessionPersistence.addToBuddyList(user); expect(ServerDispatch.addToBuddyList).toHaveBeenCalledWith(user); }); @@ -140,7 +135,7 @@ describe('SessionPersistence', () => { }); it('addToIgnoreList passes user', () => { - const user = { name: 'bob' } as any; + const user = create(Data.ServerInfo_UserSchema, { name: 'bob' }); SessionPersistence.addToIgnoreList(user); expect(ServerDispatch.addToIgnoreList).toHaveBeenCalledWith(user); }); @@ -155,19 +150,13 @@ describe('SessionPersistence', () => { expect(ServerDispatch.updateInfo).toHaveBeenCalledWith('Server', '1.0'); }); - it('updateStatus dispatches status and calls connectionClosed when DISCONNECTED', () => { - SessionPersistence.updateStatus(StatusEnum.DISCONNECTED, 'bye'); - expect(ServerDispatch.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'bye'); - expect(ServerDispatch.connectionClosed).toHaveBeenCalledWith(StatusEnum.DISCONNECTED); - }); - - it('updateStatus does not call connectionClosed when not DISCONNECTED', () => { - SessionPersistence.updateStatus(StatusEnum.CONNECTED, 'hi'); - expect(ServerDispatch.connectionClosed).not.toHaveBeenCalled(); + it('updateStatus passes state and description', () => { + SessionPersistence.updateStatus(App.StatusEnum.DISCONNECTED, 'bye'); + expect(ServerDispatch.updateStatus).toHaveBeenCalledWith(App.StatusEnum.DISCONNECTED, 'bye'); }); it('updateUser passes user', () => { - const user = { name: 'alice' } as any; + const user = create(Data.ServerInfo_UserSchema, { name: 'alice' }); SessionPersistence.updateUser(user); expect(ServerDispatch.updateUser).toHaveBeenCalledWith(user); }); @@ -178,7 +167,7 @@ describe('SessionPersistence', () => { }); it('userJoined passes user', () => { - const user = { name: 'carol' } as any; + const user = create(Data.ServerInfo_UserSchema, { name: 'carol' }); SessionPersistence.userJoined(user); expect(ServerDispatch.userJoined).toHaveBeenCalledWith(user); }); @@ -195,7 +184,7 @@ describe('SessionPersistence', () => { }); it('accountAwaitingActivation passes options', () => { - const opts = { userName: 'u' } as any; + const opts: Enriched.PendingActivationContext = { host: 'h', port: '1', userName: 'u' }; SessionPersistence.accountAwaitingActivation(opts); expect(ServerDispatch.accountAwaitingActivation).toHaveBeenCalledWith(opts); }); @@ -282,53 +271,55 @@ describe('SessionPersistence', () => { }); it('getUserInfo passes userInfo', () => { - const user = { name: 'u' } as any; + const user = create(Data.ServerInfo_UserSchema, { name: 'u' }); SessionPersistence.getUserInfo(user); expect(ServerDispatch.getUserInfo).toHaveBeenCalledWith(user); }); - it('getGamesOfUser builds gametypeMap and dispatches raw games with map', () => { - const gt = { gameTypeId: 1, description: 'Standard' }; - const room = { gametypeList: [gt] }; - const game = { gameId: 5, roomId: 1, gameTypes: [1], description: 'My Game', started: false }; - SessionPersistence.getGamesOfUser('alice', { roomList: [room], gameList: [game] } as any); - expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', [game], { 1: 'Standard' }); + it('getGamesOfUser passes response to ServerDispatch', () => { + const response = create(Data.Response_GetGamesOfUserSchema, { + roomList: [create(Data.ServerInfo_RoomSchema, { + gametypeList: [create(Data.ServerInfo_GameTypeSchema, { gameTypeId: 1, description: 'Standard' })] + })], + gameList: [create(Data.ServerInfo_GameSchema, { gameId: 5 })], + }); + SessionPersistence.getGamesOfUser('alice', response); + expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', response); }); it('getGamesOfUser handles empty response', () => { - SessionPersistence.getGamesOfUser('alice', {} as any); - expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', [], {}); + const emptyResponse = create(Data.Response_GetGamesOfUserSchema, {}); + SessionPersistence.getGamesOfUser('alice', emptyResponse); + expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', emptyResponse); }); - it('gameJoined dispatches via GameDispatch.gameJoined', () => { - const gameInfo = { gameId: 10, roomId: 2, description: 'test', started: false }; - SessionPersistence.gameJoined({ gameInfo, hostId: 3, playerId: 4, spectator: false, judge: false, resuming: true } as any); - expect(GameDispatch.gameJoined).toHaveBeenCalledWith( - 10, - expect.objectContaining({ gameId: 10, hostId: 3, localPlayerId: 4, resuming: true }) - ); + it('gameJoined dispatches raw event via GameDispatch.gameJoined', () => { + const gameInfo = create(Data.ServerInfo_GameSchema, { gameId: 10, roomId: 2, description: 'test', started: false }); + const data = create(Data.Event_GameJoinedSchema, { gameInfo, hostId: 3, playerId: 4, spectator: false, judge: false, resuming: true }); + SessionPersistence.gameJoined(data); + expect(GameDispatch.gameJoined).toHaveBeenCalledWith(data); }); it('notifyUser passes notification', () => { - const notif = { type: 1 } as any; + const notif = create(Data.Event_NotifyUserSchema, { type: 1 }); SessionPersistence.notifyUser(notif); expect(ServerDispatch.notifyUser).toHaveBeenCalledWith(notif); }); it('playerPropertiesChanged dispatches via GameDispatch', () => { - const props = { pingTime: 100 }; - SessionPersistence.playerPropertiesChanged(5, 1, { playerProperties: props } as any); + const props = create(Data.ServerInfo_PlayerPropertiesSchema, { pingTime: 100 }); + SessionPersistence.playerPropertiesChanged(5, 1, create(Data.Event_PlayerPropertiesChangedSchema, { playerProperties: props })); expect(GameDispatch.playerPropertiesChanged).toHaveBeenCalledWith(5, 1, props); }); it('serverShutdown passes data', () => { - const data = { gracePeriod: 5 } as any; + const data = create(Data.Event_ServerShutdownSchema, { gracePeriod: 5 }); SessionPersistence.serverShutdown(data); expect(ServerDispatch.serverShutdown).toHaveBeenCalledWith(data); }); it('userMessage passes messageData', () => { - const msg = { message: 'hello' } as any; + const msg = create(Data.Event_UserMessageSchema, { message: 'hello' }); SessionPersistence.userMessage(msg); expect(ServerDispatch.userMessage).toHaveBeenCalledWith(msg); }); @@ -349,13 +340,14 @@ describe('SessionPersistence', () => { }); it('updateServerDecks passes deckList', () => { - SessionPersistence.updateServerDecks({ folders: [] } as any); + SessionPersistence.updateServerDecks(create(Data.Response_DeckListSchema, { folders: [] })); expect(ServerDispatch.backendDecks).toHaveBeenCalled(); }); it('uploadServerDeck passes path and treeItem', () => { - SessionPersistence.uploadServerDeck('/path', { id: 1 } as any); - expect(ServerDispatch.deckUpload).toHaveBeenCalledWith('/path', { id: 1 }); + const treeItem = create(Data.ServerInfo_DeckStorage_TreeItemSchema, { id: 1 }); + SessionPersistence.uploadServerDeck('/path', treeItem); + expect(ServerDispatch.deckUpload).toHaveBeenCalledWith('/path', treeItem); }); it('createServerDeckDir passes path and dirName', () => { @@ -374,7 +366,7 @@ describe('SessionPersistence', () => { }); it('replayAdded passes matchInfo', () => { - const match = { gameId: 1 } as any; + const match = create(Data.ServerInfo_ReplayMatchSchema, { gameId: 1 }); SessionPersistence.replayAdded(match); expect(ServerDispatch.replayAdded).toHaveBeenCalledWith(match); }); @@ -395,14 +387,15 @@ describe('SessionPersistence', () => { }); it('playerPropertiesChanged does nothing when payload has no playerProperties', () => { - SessionPersistence.playerPropertiesChanged(5, 1, {} as any); + SessionPersistence.playerPropertiesChanged(5, 1, create(Data.Event_PlayerPropertiesChangedSchema, {})); expect(GameDispatch.playerPropertiesChanged).not.toHaveBeenCalled(); }); it('getGamesOfUser handles rooms with missing gametypeList', () => { - const room = {} as any; - const game = { gameId: 5 }; - SessionPersistence.getGamesOfUser('alice', { roomList: [room], gameList: [game] } as any); - expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', [game], {}); + const room = create(Data.ServerInfo_RoomSchema, {}); + const game = create(Data.ServerInfo_GameSchema, { gameId: 5 }); + const response = create(Data.Response_GetGamesOfUserSchema, { roomList: [room], gameList: [game] }); + SessionPersistence.getGamesOfUser('alice', response); + expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', response); }); }); diff --git a/webclient/src/websocket/persistence/SessionPersistence.ts b/webclient/src/websocket/persistence/SessionPersistence.ts index 84b5f7b1c..d2071fc1f 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.ts @@ -1,22 +1,6 @@ -import { GameDispatch, ServerDispatch } from 'store'; -import { StatusEnum, WebSocketConnectOptions } from 'types'; -import type { ServerInfo_User } from 'generated/proto/serverinfo_user_pb'; -import type { Response_DeckList } from 'generated/proto/response_deck_list_pb'; -import type { ServerInfo_DeckStorage_TreeItem } from 'generated/proto/serverinfo_deckstorage_pb'; -import type { ServerInfo_ReplayMatch } from 'generated/proto/serverinfo_replay_match_pb'; -import { GameEntry } from 'store/game/game.interfaces'; -import { sanitizeHtml } from 'websocket/utils'; -import { - GameJoinedData, - NotifyUserData, - PlayerGamePropertiesData, - ServerShutdownData, - UserMessageData -} from '../events/session/interfaces'; - -import type { Response_GetGamesOfUser } from 'generated/proto/response_get_games_of_user_pb'; -import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb'; -import type { ServerInfo_GameType } from 'generated/proto/serverinfo_gametype_pb'; +import { GameDispatch, ServerDispatch } from '@app/store'; +import { App, Data, Enriched } from '@app/types'; +import { sanitizeHtml } from '../utils'; export class SessionPersistence { static initialized() { @@ -31,7 +15,7 @@ export class SessionPersistence { ServerDispatch.clearStore(); } - static loginSuccessful(options: WebSocketConnectOptions) { + static loginSuccessful(options: Enriched.LoginSuccessContext) { ServerDispatch.loginSuccessful(options); } @@ -39,10 +23,6 @@ export class SessionPersistence { ServerDispatch.loginFailed(); } - static connectionClosed(reason: number) { - ServerDispatch.connectionClosed(reason); - } - static connectionFailed() { ServerDispatch.connectionFailed(); } @@ -55,11 +35,11 @@ export class SessionPersistence { ServerDispatch.testConnectionFailed(); } - static updateBuddyList(buddyList: ServerInfo_User[]) { + static updateBuddyList(buddyList: Data.ServerInfo_User[]) { ServerDispatch.updateBuddyList(buddyList); } - static addToBuddyList(user: ServerInfo_User) { + static addToBuddyList(user: Data.ServerInfo_User) { ServerDispatch.addToBuddyList(user); } @@ -67,11 +47,11 @@ export class SessionPersistence { ServerDispatch.removeFromBuddyList(userName); } - static updateIgnoreList(ignoreList: ServerInfo_User[]) { + static updateIgnoreList(ignoreList: Data.ServerInfo_User[]) { ServerDispatch.updateIgnoreList(ignoreList); } - static addToIgnoreList(user: ServerInfo_User) { + static addToIgnoreList(user: Data.ServerInfo_User) { ServerDispatch.addToIgnoreList(user); } @@ -83,23 +63,19 @@ export class SessionPersistence { ServerDispatch.updateInfo(name, version); } - static updateStatus(state: number, description: string) { + static updateStatus(state: App.StatusEnum, description: string) { ServerDispatch.updateStatus(state, description); - - if (state === StatusEnum.DISCONNECTED) { - this.connectionClosed(state); - } } - static updateUser(user: ServerInfo_User) { + static updateUser(user: Data.ServerInfo_User) { ServerDispatch.updateUser(user); } - static updateUsers(users: ServerInfo_User[]) { + static updateUsers(users: Data.ServerInfo_User[]) { ServerDispatch.updateUsers(users); } - static userJoined(user: ServerInfo_User) { + static userJoined(user: Data.ServerInfo_User) { ServerDispatch.userJoined(user); } @@ -111,7 +87,7 @@ export class SessionPersistence { ServerDispatch.serverMessage(sanitizeHtml(message)); } - static accountAwaitingActivation(options: WebSocketConnectOptions) { + static accountAwaitingActivation(options: Enriched.PendingActivationContext) { ServerDispatch.accountAwaitingActivation(options); } @@ -175,58 +151,33 @@ export class SessionPersistence { ServerDispatch.accountImageChanged({ avatarBmp }); } - static getUserInfo(userInfo: ServerInfo_User) { + static getUserInfo(userInfo: Data.ServerInfo_User) { ServerDispatch.getUserInfo(userInfo); } - static getGamesOfUser(userName: string, response: Response_GetGamesOfUser): void { - const gametypeMap: Record = {}; - (response.roomList || []).forEach((room: ServerInfo_Room) => { - (room.gametypeList || []).forEach((gt: ServerInfo_GameType) => { - gametypeMap[gt.gameTypeId] = gt.description; - }); - }); - const games = response.gameList || []; - ServerDispatch.gamesOfUser(userName, games, gametypeMap); + static getGamesOfUser(userName: string, response: Data.Response_GetGamesOfUser): void { + ServerDispatch.gamesOfUser(userName, response); } - static gameJoined(gameJoinedData: GameJoinedData): void { - const { gameInfo, hostId, playerId, spectator, judge, resuming } = gameJoinedData; - const gameEntry: GameEntry = { - gameId: gameInfo.gameId, - roomId: gameInfo.roomId, - description: gameInfo.description, - hostId, - localPlayerId: playerId, - spectator, - judge, - resuming, - started: gameInfo.started, - activePlayerId: -1, - activePhase: -1, - secondsElapsed: 0, - reversed: false, - players: {}, - messages: [], - }; - GameDispatch.gameJoined(gameInfo.gameId, gameEntry); + static gameJoined(gameJoinedData: Data.Event_GameJoined): void { + GameDispatch.gameJoined(gameJoinedData); } - static notifyUser(notification: NotifyUserData): void { + static notifyUser(notification: Data.Event_NotifyUser): void { ServerDispatch.notifyUser(notification); } - static playerPropertiesChanged(gameId: number, playerId: number, payload: PlayerGamePropertiesData): void { + static playerPropertiesChanged(gameId: number, playerId: number, payload: Data.Event_PlayerPropertiesChanged): void { if (payload.playerProperties) { GameDispatch.playerPropertiesChanged(gameId, playerId, payload.playerProperties); } } - static serverShutdown(data: ServerShutdownData): void { + static serverShutdown(data: Data.Event_ServerShutdown): void { ServerDispatch.serverShutdown(data); } - static userMessage(messageData: UserMessageData): void { + static userMessage(messageData: Data.Event_UserMessage): void { ServerDispatch.userMessage(messageData); } @@ -242,11 +193,11 @@ export class SessionPersistence { ServerDispatch.deckDelete(deckId); } - static updateServerDecks(deckList: Response_DeckList): void { + static updateServerDecks(deckList: Data.Response_DeckList): void { ServerDispatch.backendDecks(deckList); } - static uploadServerDeck(path: string, treeItem: ServerInfo_DeckStorage_TreeItem): void { + static uploadServerDeck(path: string, treeItem: Data.ServerInfo_DeckStorage_TreeItem): void { ServerDispatch.deckUpload(path, treeItem); } @@ -258,11 +209,11 @@ export class SessionPersistence { ServerDispatch.deckDelDir(path); } - static replayList(matchList: ServerInfo_ReplayMatch[]): void { + static replayList(matchList: Data.ServerInfo_ReplayMatch[]): void { ServerDispatch.replayList(matchList); } - static replayAdded(matchInfo: ServerInfo_ReplayMatch): void { + static replayAdded(matchInfo: Data.ServerInfo_ReplayMatch): void { ServerDispatch.replayAdded(matchInfo); } diff --git a/webclient/src/websocket/services/KeepAliveService.spec.ts b/webclient/src/websocket/services/KeepAliveService.spec.ts index 4d45d6305..644bb588b 100644 --- a/webclient/src/websocket/services/KeepAliveService.spec.ts +++ b/webclient/src/websocket/services/KeepAliveService.spec.ts @@ -1,6 +1,11 @@ import { KeepAliveService } from './KeepAliveService'; import { WebSocketService } from './WebSocketService'; +type KeepAliveInternal = KeepAliveService & { + keepalivecb: NodeJS.Timeout; + lastPingPending: boolean; +}; + vi.mock('./WebSocketService'); describe('KeepAliveService', () => { @@ -38,15 +43,15 @@ describe('KeepAliveService', () => { }); it('should start ping loop', () => { - expect((service as any).keepalivecb).toBeDefined(); - expect((service as any).lastPingPending).toBeTruthy(); + expect((service as KeepAliveInternal).keepalivecb).toBeDefined(); + expect((service as KeepAliveInternal).lastPingPending).toBeTruthy(); }); it('should call ping callback when done', () => { resolvePing(); return promise.then(() => { - expect((service as any).lastPingPending).toBeFalsy(); + expect((service as KeepAliveInternal).lastPingPending).toBeFalsy(); }); }); diff --git a/webclient/src/websocket/services/ProtobufService.spec.ts b/webclient/src/websocket/services/ProtobufService.spec.ts index 4a4ec401d..0c578de77 100644 --- a/webclient/src/websocket/services/ProtobufService.spec.ts +++ b/webclient/src/websocket/services/ProtobufService.spec.ts @@ -1,5 +1,5 @@ vi.mock('@bufbuild/protobuf', () => ({ - create: vi.fn((_schema: any, fields?: any) => ({ ...(fields ?? {}) })), + create: vi.fn((_schema: unknown, fields?: Record) => ({ ...(fields ?? {}) })), fromBinary: vi.fn(), toBinary: vi.fn().mockReturnValue(new Uint8Array()), hasExtension: vi.fn().mockReturnValue(false), @@ -7,11 +7,11 @@ vi.mock('@bufbuild/protobuf', () => ({ setExtension: vi.fn(), })); -vi.mock('generated/proto/commands_pb', () => ({ +vi.mock('../../generated/proto/commands_pb', () => ({ CommandContainerSchema: {}, })); -vi.mock('generated/proto/server_message_pb', () => ({ +vi.mock('../../generated/proto/server_message_pb', () => ({ ServerMessageSchema: {}, ServerMessage_MessageType: { RESPONSE: 1, @@ -32,16 +32,29 @@ vi.mock('../WebClient', () => ({ default: {}, })); -import { fromBinary, hasExtension, getExtension } from '@bufbuild/protobuf'; -import { ServerMessage_MessageType } from 'generated/proto/server_message_pb'; +import { create, fromBinary, hasExtension, getExtension } from '@bufbuild/protobuf'; +import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; + import { ProtobufService } from './ProtobufService'; import { GameEvents, RoomEvents, SessionEvents } from '../events'; +import type { GameExtensionRegistry } from '../events/game'; +import type { RoomExtensionRegistry } from '../events/room'; +import type { SessionExtensionRegistry } from '../events/session'; -let mockSocket: any; +import { Data } from '@app/types'; + +type ProtobufInternal = ProtobufService & { + cmdId: number; + pendingCommands: Map void>; + processGameEvent(container: unknown, extra?: unknown): void; + processRoomEvent(event: unknown): void; + processSessionEvent(event: unknown): void; + processServerResponse(response: unknown): void; +}; + +let mockSocket: { isOpen: ReturnType; send: ReturnType }; beforeEach(() => { - vi.clearAllMocks(); - mockSocket = { isOpen: vi.fn().mockReturnValue(true), send: vi.fn(), @@ -49,14 +62,21 @@ beforeEach(() => { }); describe('ProtobufService', () => { + // Mock extensions for send*Command tests — @bufbuild/protobuf is fully mocked so these are never invoked + const sessionExt = {} as GenExtension>; + const roomExt = {} as GenExtension>; + const gameExt = {} as GenExtension>; + const moderatorExt = {} as GenExtension>; + const adminExt = {} as GenExtension>; + describe('resetCommands', () => { it('resets cmdId and pendingCommands', () => { const service = new ProtobufService(mockSocket); - service.sendSessionCommand({} as any, vi.fn()); - expect((service as any).cmdId).toBe(1); + service.sendSessionCommand(sessionExt, vi.fn()); + expect((service as ProtobufInternal).cmdId).toBe(1); service.resetCommands(); - expect((service as any).cmdId).toBe(0); - expect((service as any).pendingCommands).toEqual(new Map()); + expect((service as ProtobufInternal).cmdId).toBe(0); + expect((service as ProtobufInternal).pendingCommands).toEqual(new Map()); }); }); @@ -64,22 +84,22 @@ describe('ProtobufService', () => { it('increments cmdId and stores callback', () => { const service = new ProtobufService(mockSocket); const cb = vi.fn(); - service.sendCommand({} as any, cb); - expect((service as any).cmdId).toBe(1); - expect((service as any).pendingCommands.get(1)).toBe(cb); + service.sendCommand(create(Data.CommandContainerSchema), cb); + expect((service as ProtobufInternal).cmdId).toBe(1); + expect((service as ProtobufInternal).pendingCommands.get(1)).toBe(cb); }); it('sends encoded data when socket is OPEN', () => { const service = new ProtobufService(mockSocket); mockSocket.isOpen.mockReturnValue(true); - service.sendCommand({} as any, vi.fn()); + service.sendCommand(create(Data.CommandContainerSchema), vi.fn()); expect(mockSocket.send).toHaveBeenCalled(); }); it('does not send when socket is not OPEN', () => { const service = new ProtobufService(mockSocket); mockSocket.isOpen.mockReturnValue(false); - service.sendCommand({} as any, vi.fn()); + service.sendCommand(create(Data.CommandContainerSchema), vi.fn()); expect(mockSocket.send).not.toHaveBeenCalled(); }); }); @@ -87,136 +107,136 @@ describe('ProtobufService', () => { describe('sendSessionCommand', () => { it('stores callback and increments cmdId', () => { const service = new ProtobufService(mockSocket); - service.sendSessionCommand({} as any, {}); - expect((service as any).cmdId).toBe(1); - expect((service as any).pendingCommands.get(1)).toBeTypeOf('function'); + service.sendSessionCommand(sessionExt, {}); + expect((service as ProtobufInternal).cmdId).toBe(1); + expect((service as ProtobufInternal).pendingCommands.get(1)).toBeTypeOf('function'); }); it('invokes onResponse with raw response when the pending command is triggered', () => { const service = new ProtobufService(mockSocket); const cb = vi.fn(); - service.sendSessionCommand({} as any, {}, { onResponse: cb }); + service.sendSessionCommand(sessionExt, {}, { onResponse: cb }); - const storedCb = (service as any).pendingCommands.get(1); - storedCb({ responseData: true }); + const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!; + storedCb(create(Data.ResponseSchema)); - expect(cb).toHaveBeenCalledWith({ responseData: true }); + expect(cb).toHaveBeenCalledWith(create(Data.ResponseSchema)); }); it('does not throw when no callback is provided and pending command is triggered', () => { const service = new ProtobufService(mockSocket); - service.sendSessionCommand({} as any, {}); + service.sendSessionCommand(sessionExt, {}); - const storedCb = (service as any).pendingCommands.get(1); - expect(() => storedCb({ responseData: true })).not.toThrow(); + const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!; + expect(() => storedCb(create(Data.ResponseSchema))).not.toThrow(); }); }); describe('sendRoomCommand', () => { it('stores callback and increments cmdId', () => { const service = new ProtobufService(mockSocket); - service.sendRoomCommand(42, {} as any, {}); - expect((service as any).cmdId).toBe(1); + service.sendRoomCommand(42, roomExt, {}); + expect((service as ProtobufInternal).cmdId).toBe(1); }); it('invokes onResponse with raw response when the pending command is triggered', () => { const service = new ProtobufService(mockSocket); const cb = vi.fn(); - service.sendRoomCommand(42, {} as any, {}, { onResponse: cb }); + service.sendRoomCommand(42, roomExt, {}, { onResponse: cb }); - const storedCb = (service as any).pendingCommands.get(1); - storedCb({ responseData: true }); + const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!; + storedCb(create(Data.ResponseSchema)); - expect(cb).toHaveBeenCalledWith({ responseData: true }); + expect(cb).toHaveBeenCalledWith(create(Data.ResponseSchema)); }); it('does not throw when no callback is provided and pending command is triggered', () => { const service = new ProtobufService(mockSocket); - service.sendRoomCommand(42, {} as any, {}); + service.sendRoomCommand(42, roomExt, {}); - const storedCb = (service as any).pendingCommands.get(1); - expect(() => storedCb({ responseData: true })).not.toThrow(); + const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!; + expect(() => storedCb(create(Data.ResponseSchema))).not.toThrow(); }); }); describe('sendGameCommand', () => { it('stores callback and increments cmdId', () => { const service = new ProtobufService(mockSocket); - service.sendGameCommand(7, {} as any, {}); - expect((service as any).cmdId).toBe(1); + service.sendGameCommand(7, gameExt, {}); + expect((service as ProtobufInternal).cmdId).toBe(1); }); it('invokes onResponse with raw response when the pending command is triggered', () => { const service = new ProtobufService(mockSocket); const cb = vi.fn(); - service.sendGameCommand(7, {} as any, {}, { onResponse: cb }); + service.sendGameCommand(7, gameExt, {}, { onResponse: cb }); - const storedCb = (service as any).pendingCommands.get(1); - storedCb({ responseData: true }); + const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!; + storedCb(create(Data.ResponseSchema)); - expect(cb).toHaveBeenCalledWith({ responseData: true }); + expect(cb).toHaveBeenCalledWith(create(Data.ResponseSchema)); }); it('does not throw when no callback is provided and pending command is triggered', () => { const service = new ProtobufService(mockSocket); - service.sendGameCommand(7, {} as any, {}); + service.sendGameCommand(7, gameExt, {}); - const storedCb = (service as any).pendingCommands.get(1); - expect(() => storedCb({ responseData: true })).not.toThrow(); + const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!; + expect(() => storedCb(create(Data.ResponseSchema))).not.toThrow(); }); }); describe('sendModeratorCommand', () => { it('stores callback and increments cmdId', () => { const service = new ProtobufService(mockSocket); - service.sendModeratorCommand({} as any, {}); - expect((service as any).cmdId).toBe(1); + service.sendModeratorCommand(moderatorExt, {}); + expect((service as ProtobufInternal).cmdId).toBe(1); }); it('invokes onResponse with raw response when the pending command is triggered', () => { const service = new ProtobufService(mockSocket); const cb = vi.fn(); - service.sendModeratorCommand({} as any, {}, { onResponse: cb }); + service.sendModeratorCommand(moderatorExt, {}, { onResponse: cb }); - const storedCb = (service as any).pendingCommands.get(1); - storedCb({ responseData: true }); + const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!; + storedCb(create(Data.ResponseSchema)); - expect(cb).toHaveBeenCalledWith({ responseData: true }); + expect(cb).toHaveBeenCalledWith(create(Data.ResponseSchema)); }); it('does not throw when no callback is provided and pending command is triggered', () => { const service = new ProtobufService(mockSocket); - service.sendModeratorCommand({} as any, {}); + service.sendModeratorCommand(moderatorExt, {}); - const storedCb = (service as any).pendingCommands.get(1); - expect(() => storedCb({ responseData: true })).not.toThrow(); + const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!; + expect(() => storedCb(create(Data.ResponseSchema))).not.toThrow(); }); }); describe('sendAdminCommand', () => { it('stores callback and increments cmdId', () => { const service = new ProtobufService(mockSocket); - service.sendAdminCommand({} as any, {}); - expect((service as any).cmdId).toBe(1); + service.sendAdminCommand(adminExt, {}); + expect((service as ProtobufInternal).cmdId).toBe(1); }); it('invokes onResponse with raw response when the pending command is triggered', () => { const service = new ProtobufService(mockSocket); const cb = vi.fn(); - service.sendAdminCommand({} as any, {}, { onResponse: cb }); + service.sendAdminCommand(adminExt, {}, { onResponse: cb }); - const storedCb = (service as any).pendingCommands.get(1); - storedCb({ responseData: true }); + const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!; + storedCb(create(Data.ResponseSchema)); - expect(cb).toHaveBeenCalledWith({ responseData: true }); + expect(cb).toHaveBeenCalledWith(create(Data.ResponseSchema)); }); it('does not throw when no callback is provided and pending command is triggered', () => { const service = new ProtobufService(mockSocket); - service.sendAdminCommand({} as any, {}); + service.sendAdminCommand(adminExt, {}); - const storedCb = (service as any).pendingCommands.get(1); - expect(() => storedCb({ responseData: true })).not.toThrow(); + const storedCb = (service as ProtobufInternal).pendingCommands.get(1)!; + expect(() => storedCb(create(Data.ResponseSchema))).not.toThrow(); }); }); @@ -224,27 +244,30 @@ describe('ProtobufService', () => { it('routes RESPONSE message to processServerResponse', () => { const service = new ProtobufService(mockSocket); const cb = vi.fn(); - (service as any).cmdId = 1; - (service as any).pendingCommands.set(1, cb); + (service as ProtobufInternal).cmdId = 1; + (service as ProtobufInternal).pendingCommands.set(1, cb); - vi.mocked(fromBinary).mockReturnValue({ - messageType: ServerMessage_MessageType.RESPONSE, - response: { cmdId: BigInt(1) }, - } as any); + vi.mocked(fromBinary).mockReturnValue( + create(Data.ServerMessageSchema, { + messageType: Data.ServerMessage_MessageType.RESPONSE, + response: create(Data.ResponseSchema, { cmdId: BigInt(1) }), + }) + ); service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); - expect(cb).toHaveBeenCalledWith({ cmdId: BigInt(1) }); - expect((service as any).pendingCommands.get(1)).toBeUndefined(); + expect(cb).toHaveBeenCalledWith(expect.objectContaining({ cmdId: BigInt(1) })); + expect((service as ProtobufInternal).pendingCommands.get(1)).toBeUndefined(); }); it('routes ROOM_EVENT message', () => { const service = new ProtobufService(mockSocket); - const processRoomEvent = vi.spyOn(service as any, 'processRoomEvent'); + const processRoomEvent = vi.spyOn(service as ProtobufInternal, 'processRoomEvent'); - vi.mocked(fromBinary).mockReturnValue({ - messageType: ServerMessage_MessageType.ROOM_EVENT, - roomEvent: {}, - } as any); + vi.mocked(fromBinary).mockReturnValue( + create(Data.ServerMessageSchema, { + messageType: Data.ServerMessage_MessageType.ROOM_EVENT, + }) + ); service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); expect(processRoomEvent).toHaveBeenCalled(); @@ -252,12 +275,13 @@ describe('ProtobufService', () => { it('routes SESSION_EVENT message', () => { const service = new ProtobufService(mockSocket); - const processSessionEvent = vi.spyOn(service as any, 'processSessionEvent'); + const processSessionEvent = vi.spyOn(service as ProtobufInternal, 'processSessionEvent'); - vi.mocked(fromBinary).mockReturnValue({ - messageType: ServerMessage_MessageType.SESSION_EVENT, - sessionEvent: {}, - } as any); + vi.mocked(fromBinary).mockReturnValue( + create(Data.ServerMessageSchema, { + messageType: Data.ServerMessage_MessageType.SESSION_EVENT, + }) + ); service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); expect(processSessionEvent).toHaveBeenCalled(); @@ -265,12 +289,13 @@ describe('ProtobufService', () => { it('routes GAME_EVENT_CONTAINER message', () => { const service = new ProtobufService(mockSocket); - const processGameEvent = vi.spyOn(service as any, 'processGameEvent'); + const processGameEvent = vi.spyOn(service as ProtobufInternal, 'processGameEvent'); - vi.mocked(fromBinary).mockReturnValue({ - messageType: ServerMessage_MessageType.GAME_EVENT_CONTAINER, - gameEventContainer: {}, - } as any); + vi.mocked(fromBinary).mockReturnValue( + create(Data.ServerMessageSchema, { + messageType: Data.ServerMessage_MessageType.GAME_EVENT_CONTAINER, + }) + ); service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); expect(processGameEvent).toHaveBeenCalled(); @@ -280,9 +305,11 @@ describe('ProtobufService', () => { const service = new ProtobufService(mockSocket); const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); - vi.mocked(fromBinary).mockReturnValue({ - messageType: 999, - } as any); + vi.mocked(fromBinary).mockReturnValue( + create(Data.ServerMessageSchema, { + messageType: 999, + }) + ); service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent); expect(consoleSpy).toHaveBeenCalled(); @@ -291,7 +318,7 @@ describe('ProtobufService', () => { it('does nothing when decoded message is null', () => { const service = new ProtobufService(mockSocket); - vi.mocked(fromBinary).mockReturnValue(null as any); + vi.mocked(fromBinary).mockReturnValue(null!); expect(() => service.handleMessageEvent({ data: new ArrayBuffer(0) } as MessageEvent)).not.toThrow(); }); @@ -311,56 +338,56 @@ describe('ProtobufService', () => { it('returns early when container has no eventList', () => { const service = new ProtobufService(mockSocket); vi.mocked(hasExtension).mockReturnValue(false); - (service as any).processGameEvent(null, {}); + (service as ProtobufInternal).processGameEvent(null, {}); expect(hasExtension).not.toHaveBeenCalled(); }); it('dispatches to a GameEvents handler when hasExtension returns true', () => { const service = new ProtobufService(mockSocket); const handler = vi.fn(); - const mockExt = {}; + const mockExt = {} as GenExtension; const payload = { someData: 1 }; // Temporarily override GameEvents for this test - (GameEvents as any).push([mockExt, handler]); + (GameEvents as GameExtensionRegistry).push([mockExt, handler]); vi.mocked(hasExtension).mockReturnValue(true); vi.mocked(getExtension).mockReturnValue(payload); - (service as any).processGameEvent({ + (service as ProtobufInternal).processGameEvent({ gameId: 42, eventList: [{ playerId: 5 }], }, {}); expect(handler).toHaveBeenCalledWith(payload, expect.objectContaining({ gameId: 42, playerId: 5 })); - (GameEvents as any).pop(); + (GameEvents as GameExtensionRegistry).pop(); }); it('defaults gameId and playerId to -1 when undefined', () => { const service = new ProtobufService(mockSocket); const handler = vi.fn(); - const mockExt = {}; + const mockExt = {} as GenExtension; const payload = { someData: 1 }; - (GameEvents as any).push([mockExt, handler]); + (GameEvents as GameExtensionRegistry).push([mockExt, handler]); vi.mocked(hasExtension).mockReturnValue(true); vi.mocked(getExtension).mockReturnValue(payload); - (service as any).processGameEvent({ + (service as ProtobufInternal).processGameEvent({ gameId: undefined, eventList: [{ playerId: undefined }], }); expect(handler).toHaveBeenCalledWith(payload, expect.objectContaining({ gameId: -1, playerId: -1 })); - (GameEvents as any).pop(); + (GameEvents as GameExtensionRegistry).pop(); }); }); describe('processServerResponse', () => { it('returns early when response is undefined', () => { const service = new ProtobufService(mockSocket); - (service as any).pendingCommands.set(1, vi.fn()); - (service as any).processServerResponse(undefined); - expect((service as any).pendingCommands.size).toBe(1); + (service as ProtobufInternal).pendingCommands.set(1, vi.fn()); + (service as ProtobufInternal).processServerResponse(undefined); + expect((service as ProtobufInternal).pendingCommands.size).toBe(1); }); }); @@ -368,25 +395,25 @@ describe('ProtobufService', () => { it('returns early when event is undefined', () => { const service = new ProtobufService(mockSocket); vi.mocked(hasExtension).mockReturnValue(false); - (service as any).processRoomEvent(undefined); + (service as ProtobufInternal).processRoomEvent(undefined); expect(hasExtension).not.toHaveBeenCalled(); }); it('dispatches to a RoomEvents handler when hasExtension returns true', () => { const service = new ProtobufService(mockSocket); const handler = vi.fn(); - const mockExt = {}; + const mockExt = {} as GenExtension; const payload = { roomData: 1 }; - (RoomEvents as any).push([mockExt, handler]); + (RoomEvents as RoomExtensionRegistry).push([mockExt, handler]); vi.mocked(hasExtension).mockReturnValue(true); vi.mocked(getExtension).mockReturnValue(payload); const event = { roomId: 10 }; - (service as any).processRoomEvent(event); + (service as ProtobufInternal).processRoomEvent(event); expect(handler).toHaveBeenCalledWith(payload, event); - (RoomEvents as any).pop(); + (RoomEvents as RoomExtensionRegistry).pop(); }); }); @@ -394,24 +421,24 @@ describe('ProtobufService', () => { it('returns early when event is undefined', () => { const service = new ProtobufService(mockSocket); vi.mocked(hasExtension).mockReturnValue(false); - (service as any).processSessionEvent(undefined); + (service as ProtobufInternal).processSessionEvent(undefined); expect(hasExtension).not.toHaveBeenCalled(); }); it('dispatches to a SessionEvents handler when hasExtension returns true', () => { const service = new ProtobufService(mockSocket); const handler = vi.fn(); - const mockExt = {}; + const mockExt = {} as GenExtension; const payload = { sessionData: 1 }; - (SessionEvents as any).push([mockExt, handler]); + (SessionEvents as SessionExtensionRegistry).push([mockExt, handler]); vi.mocked(hasExtension).mockReturnValue(true); vi.mocked(getExtension).mockReturnValue(payload); - (service as any).processSessionEvent({ sessionId: 7 }); + (service as ProtobufInternal).processSessionEvent({ sessionId: 7 }); expect(handler).toHaveBeenCalledWith(payload); - (SessionEvents as any).pop(); + (SessionEvents as SessionExtensionRegistry).pop(); }); }); diff --git a/webclient/src/websocket/services/ProtobufService.ts b/webclient/src/websocket/services/ProtobufService.ts index 7333c58b2..4bc148ece 100644 --- a/webclient/src/websocket/services/ProtobufService.ts +++ b/webclient/src/websocket/services/ProtobufService.ts @@ -1,21 +1,10 @@ import { create, fromBinary, hasExtension, getExtension, setExtension, toBinary } from '@bufbuild/protobuf'; import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; -import type { Response } from 'generated/proto/response_pb'; -import type { RoomEvent } from 'generated/proto/room_event_pb'; -import type { SessionEvent } from 'generated/proto/session_event_pb'; -import type { GameEventContainer } from 'generated/proto/game_event_container_pb'; import { GameEvents, RoomEvents, SessionEvents } from '../events'; -import { GameEventMeta } from 'types'; +import { Data, Enriched } from '@app/types'; -import { CommandContainerSchema, type CommandContainer } from 'generated/proto/commands_pb'; -import { ServerMessageSchema, ServerMessage_MessageType, type ServerMessage } from 'generated/proto/server_message_pb'; -import { SessionCommandSchema, type SessionCommand } from 'generated/proto/session_commands_pb'; -import { GameCommandSchema, type GameCommand } from 'generated/proto/game_commands_pb'; -import { RoomCommandSchema, type RoomCommand } from 'generated/proto/room_commands_pb'; -import { ModeratorCommandSchema, type ModeratorCommand } from 'generated/proto/moderator_commands_pb'; -import { AdminCommandSchema, type AdminCommand } from 'generated/proto/admin_commands_pb'; import { type CommandOptions, handleResponse } from './command-options'; @@ -26,7 +15,7 @@ export interface SocketTransport { export class ProtobufService { private cmdId = 0; - private pendingCommands = new Map void>(); + private pendingCommands = new Map void>(); private transport: SocketTransport; @@ -41,13 +30,13 @@ export class ProtobufService { public sendGameCommand( gameId: number, - ext: GenExtension, + ext: GenExtension, value: V, options?: CommandOptions ): void { - const gameCmd = create(GameCommandSchema); + const gameCmd = create(Data.GameCommandSchema); setExtension(gameCmd, ext, value); - const cmd = create(CommandContainerSchema, { gameId, gameCommand: [gameCmd] }); + const cmd = create(Data.CommandContainerSchema, { gameId, gameCommand: [gameCmd] }); this.sendCommand(cmd, raw => { if (options) { handleResponse(ext.typeName, raw, options); @@ -57,13 +46,13 @@ export class ProtobufService { public sendRoomCommand( roomId: number, - ext: GenExtension, + ext: GenExtension, value: V, options?: CommandOptions ): void { - const roomCmd = create(RoomCommandSchema); + const roomCmd = create(Data.RoomCommandSchema); setExtension(roomCmd, ext, value); - const cmd = create(CommandContainerSchema, { roomId, roomCommand: [roomCmd] }); + const cmd = create(Data.CommandContainerSchema, { roomId, roomCommand: [roomCmd] }); this.sendCommand(cmd, raw => { if (options) { handleResponse(ext.typeName, raw, options); @@ -72,13 +61,13 @@ export class ProtobufService { } public sendSessionCommand( - ext: GenExtension, + ext: GenExtension, value: V, options?: CommandOptions ): void { - const sesCmd = create(SessionCommandSchema); + const sesCmd = create(Data.SessionCommandSchema); setExtension(sesCmd, ext, value); - const cmd = create(CommandContainerSchema, { sessionCommand: [sesCmd] }); + const cmd = create(Data.CommandContainerSchema, { sessionCommand: [sesCmd] }); this.sendCommand(cmd, raw => { if (options) { handleResponse(ext.typeName, raw, options); @@ -87,13 +76,13 @@ export class ProtobufService { } public sendModeratorCommand( - ext: GenExtension, + ext: GenExtension, value: V, options?: CommandOptions ): void { - const modCmd = create(ModeratorCommandSchema); + const modCmd = create(Data.ModeratorCommandSchema); setExtension(modCmd, ext, value); - const cmd = create(CommandContainerSchema, { moderatorCommand: [modCmd] }); + const cmd = create(Data.CommandContainerSchema, { moderatorCommand: [modCmd] }); this.sendCommand(cmd, raw => { if (options) { handleResponse(ext.typeName, raw, options); @@ -102,13 +91,13 @@ export class ProtobufService { } public sendAdminCommand( - ext: GenExtension, + ext: GenExtension, value: V, options?: CommandOptions ): void { - const adminCmd = create(AdminCommandSchema); + const adminCmd = create(Data.AdminCommandSchema); setExtension(adminCmd, ext, value); - const cmd = create(CommandContainerSchema, { adminCommand: [adminCmd] }); + const cmd = create(Data.CommandContainerSchema, { adminCommand: [adminCmd] }); this.sendCommand(cmd, raw => { if (options) { handleResponse(ext.typeName, raw, options); @@ -116,34 +105,34 @@ export class ProtobufService { }); } - public sendCommand(cmd: CommandContainer, callback: (raw: Response) => void) { + public sendCommand(cmd: Data.CommandContainer, callback: (raw: Data.Response) => void) { this.cmdId++; cmd.cmdId = BigInt(this.cmdId); this.pendingCommands.set(this.cmdId, callback); if (this.transport.isOpen()) { - this.transport.send(toBinary(CommandContainerSchema, cmd)); + this.transport.send(toBinary(Data.CommandContainerSchema, cmd)); } } public handleMessageEvent({ data }: MessageEvent): void { try { const uint8msg = new Uint8Array(data); - const msg: ServerMessage = fromBinary(ServerMessageSchema, uint8msg); + const msg: Data.ServerMessage = fromBinary(Data.ServerMessageSchema, uint8msg); if (msg) { switch (msg.messageType) { - case ServerMessage_MessageType.RESPONSE: + case Data.ServerMessage_MessageType.RESPONSE: this.processServerResponse(msg.response); break; - case ServerMessage_MessageType.ROOM_EVENT: + case Data.ServerMessage_MessageType.ROOM_EVENT: this.processRoomEvent(msg.roomEvent); break; - case ServerMessage_MessageType.SESSION_EVENT: + case Data.ServerMessage_MessageType.SESSION_EVENT: this.processSessionEvent(msg.sessionEvent); break; - case ServerMessage_MessageType.GAME_EVENT_CONTAINER: + case Data.ServerMessage_MessageType.GAME_EVENT_CONTAINER: this.processGameEvent(msg.gameEventContainer); break; default: @@ -156,7 +145,7 @@ export class ProtobufService { } } - private processServerResponse(response: Response | undefined) { + private processServerResponse(response: Data.Response | undefined) { if (!response) { return; } @@ -168,7 +157,7 @@ export class ProtobufService { } } - private processRoomEvent(event: RoomEvent | undefined) { + private processRoomEvent(event: Data.RoomEvent | undefined) { if (!event) { return; } @@ -180,7 +169,7 @@ export class ProtobufService { } } - private processSessionEvent(event: SessionEvent | undefined) { + private processSessionEvent(event: Data.SessionEvent | undefined) { if (!event) { return; } @@ -192,7 +181,7 @@ export class ProtobufService { } } - private processGameEvent(container: GameEventContainer | undefined): void { + private processGameEvent(container: Data.GameEventContainer | undefined): void { if (!container?.eventList?.length) { return; } @@ -200,7 +189,7 @@ export class ProtobufService { const { gameId, context, secondsElapsed, forcedByJudge } = container; for (const event of container.eventList) { - const meta: GameEventMeta = { + const meta: Enriched.GameEventMeta = { gameId: gameId ?? -1, playerId: event.playerId ?? -1, context, diff --git a/webclient/src/websocket/services/WebSocketService.spec.ts b/webclient/src/websocket/services/WebSocketService.spec.ts index f01c2b2c1..a806b512f 100644 --- a/webclient/src/websocket/services/WebSocketService.spec.ts +++ b/webclient/src/websocket/services/WebSocketService.spec.ts @@ -22,18 +22,24 @@ vi.mock('../persistence', () => ({ })); import { WebSocketService } from './WebSocketService'; +import type { WebSocketServiceConfig } from './WebSocketService'; +import { KeepAliveService } from './KeepAliveService'; import { SessionPersistence } from '../persistence'; import { updateStatus } from '../commands/session'; -import { StatusEnum } from 'types'; +import { App } from '@app/types'; + +type WebSocketInternal = WebSocketService & { + keepAliveService: KeepAliveService; + testSocket: WebSocket | null; +}; let MockWS: Mock; let mockInstance: ReturnType['mockInstance']; let restoreWebSocket: ReturnType['restore']; -let mockConfig: any; +let mockConfig: WebSocketServiceConfig; beforeEach(() => { vi.useFakeTimers(); - vi.clearAllMocks(); const installed = installMockWebSocket(); MockWS = installed.MockWS; @@ -53,13 +59,13 @@ afterEach(() => { describe('WebSocketService', () => { function createConnectedService() { const service = new WebSocketService(mockConfig); - service.connect({ host: 'h', port: 1 } as any, 'ws'); + service.connect({ host: 'h', port: '1' }, 'ws'); return service; } function createTestConnectedService() { const service = new WebSocketService(mockConfig); - service.testConnect({ host: 'h', port: 1 } as any, 'ws'); + service.testConnect({ host: 'h', port: '1' }, 'ws'); return service; } @@ -71,11 +77,11 @@ describe('WebSocketService', () => { it('calls disconnect and updateStatus when keepAlive disconnected$ fires', () => { const service = new WebSocketService(mockConfig); - service.connect({ host: 'localhost', port: 8080 } as any, 'ws'); + service.connect({ host: 'localhost', port: '8080' }, 'ws'); // trigger keepAliveService.disconnected$ - (service as any).keepAliveService.disconnected$.next(); + (service as WebSocketInternal).keepAliveService.disconnected$.next(); expect(mockInstance.close).toHaveBeenCalled(); - expect(updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Connection timeout'); + expect(updateStatus).toHaveBeenCalledWith(App.StatusEnum.DISCONNECTED, 'Connection timeout'); }); }); @@ -87,7 +93,7 @@ describe('WebSocketService', () => { writable: true, configurable: true, }); - service.connect({ host: 'example.com', port: 8080 } as any); + service.connect({ host: 'example.com', port: '8080' }); expect(MockWS).toHaveBeenCalledWith('wss://example.com:8080'); }); @@ -98,7 +104,7 @@ describe('WebSocketService', () => { writable: true, configurable: true, }); - service.connect({ host: 'somehost', port: 1234 } as any); + service.connect({ host: 'somehost', port: '1234' }); expect(MockWS).toHaveBeenCalledWith('ws://somehost:1234'); }); @@ -125,21 +131,21 @@ describe('WebSocketService', () => { it('calls updateStatus CONNECTED on open', () => { createConnectedService(); mockInstance.onopen(); - expect(updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTED, 'Connected'); + expect(updateStatus).toHaveBeenCalledWith(App.StatusEnum.CONNECTED, 'Connected'); }); it('starts the ping loop with the keepalive interval', () => { const service = new WebSocketService(mockConfig); - const startSpy = vi.spyOn((service as any).keepAliveService, 'startPingLoop'); - service.connect({ host: 'h', port: 1 } as any, 'ws'); + const startSpy = vi.spyOn((service as WebSocketInternal).keepAliveService, 'startPingLoop'); + service.connect({ host: 'h', port: '1' }, 'ws'); mockInstance.onopen(); expect(startSpy).toHaveBeenCalledWith(1000, expect.any(Function)); }); it('ping loop callback calls keepAliveFn', () => { const service = new WebSocketService(mockConfig); - const startSpy = vi.spyOn((service as any).keepAliveService, 'startPingLoop'); - service.connect({ host: 'h', port: 1 } as any, 'ws'); + const startSpy = vi.spyOn((service as WebSocketInternal).keepAliveService, 'startPingLoop'); + service.connect({ host: 'h', port: '1' }, 'ws'); mockInstance.onopen(); const pingCb = startSpy.mock.calls[0][1] as (done: Function) => void; const done = vi.fn(); @@ -152,20 +158,20 @@ describe('WebSocketService', () => { it('calls updateStatus DISCONNECTED on close when not already DISCONNECTED', () => { createConnectedService(); mockInstance.onclose(); - expect(updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Connection Closed'); + expect(updateStatus).toHaveBeenCalledWith(App.StatusEnum.DISCONNECTED, 'Connection Closed'); }); it('does not overwrite status if already DISCONNECTED', () => { createConnectedService(); mockInstance.onerror(); mockInstance.onclose(); - expect(updateStatus).not.toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Connection Closed'); + expect(updateStatus).not.toHaveBeenCalledWith(App.StatusEnum.DISCONNECTED, 'Connection Closed'); }); it('ends the ping loop on close', () => { const service = new WebSocketService(mockConfig); - const endSpy = vi.spyOn((service as any).keepAliveService, 'endPingLoop'); - service.connect({ host: 'h', port: 1 } as any, 'ws'); + const endSpy = vi.spyOn((service as WebSocketInternal).keepAliveService, 'endPingLoop'); + service.connect({ host: 'h', port: '1' }, 'ws'); mockInstance.onclose(); expect(endSpy).toHaveBeenCalled(); }); @@ -175,7 +181,7 @@ describe('WebSocketService', () => { it('calls updateStatus DISCONNECTED on error', () => { createConnectedService(); mockInstance.onerror(); - expect(updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, 'Connection Failed'); + expect(updateStatus).toHaveBeenCalledWith(App.StatusEnum.DISCONNECTED, 'Connection Failed'); }); it('calls SessionPersistence.connectionFailed on error', () => { @@ -242,7 +248,7 @@ describe('WebSocketService', () => { writable: true, configurable: true, }); - service.testConnect({ host: 'example.com', port: 9000 } as any); + service.testConnect({ host: 'example.com', port: '9000' }); expect(MockWS).toHaveBeenCalledWith('wss://example.com:9000'); }); @@ -253,17 +259,17 @@ describe('WebSocketService', () => { writable: true, configurable: true, }); - service.testConnect({ host: 'h', port: 1 } as any); + service.testConnect({ host: 'h', port: '1' }); expect(MockWS).toHaveBeenCalledWith('ws://h:1'); }); it('closes previous testSocket when connecting again', () => { const service = new WebSocketService(mockConfig); - service.testConnect({ host: 'h', port: 1 } as any, 'ws'); + service.testConnect({ host: 'h', port: '1' }, 'ws'); const firstInstance = mockInstance; // install second mock instance and restore after test const installed2 = installMockWebSocket(); - service.testConnect({ host: 'h', port: 2 } as any, 'ws'); + service.testConnect({ host: 'h', port: '2' }, 'ws'); expect(firstInstance.close).toHaveBeenCalled(); // restore original mock so subsequent tests see a clean global mockInstance = installed2.mockInstance; @@ -293,7 +299,7 @@ describe('WebSocketService', () => { it('nulls out testSocket on close', () => { const service = createTestConnectedService(); mockInstance.onclose(); - expect((service as any).testSocket).toBeNull(); + expect((service as WebSocketInternal).testSocket).toBeNull(); }); }); }); diff --git a/webclient/src/websocket/services/WebSocketService.ts b/webclient/src/websocket/services/WebSocketService.ts index d0b09eab5..0ca13b7e6 100644 --- a/webclient/src/websocket/services/WebSocketService.ts +++ b/webclient/src/websocket/services/WebSocketService.ts @@ -1,6 +1,6 @@ import { Subject } from 'rxjs'; -import { StatusEnum, WebSocketConnectOptions } from 'types'; +import { App, Enriched } from '@app/types'; import { KeepAliveService } from './KeepAliveService'; import { CLIENT_OPTIONS } from '../config'; @@ -29,11 +29,11 @@ export class WebSocketService { this.keepAliveService = new KeepAliveService(this); this.keepAliveService.disconnected$.subscribe(() => { this.disconnect(); - updateStatus(StatusEnum.DISCONNECTED, 'Connection timeout'); + updateStatus(App.StatusEnum.DISCONNECTED, 'Connection timeout'); }); } - public connect(options: WebSocketConnectOptions, protocol: string = 'wss'): void { + public connect(options: Enriched.WebSocketConnectOptions, protocol: string = 'wss'): void { if (window.location.hostname === 'localhost') { protocol = 'ws'; } @@ -44,7 +44,7 @@ export class WebSocketService { this.socket = this.createWebSocket(`${protocol}://${host}:${port}`); } - public testConnect(options: WebSocketConnectOptions, protocol: string = 'wss'): void { + public testConnect(options: Enriched.WebSocketConnectOptions, protocol: string = 'wss'): void { if (window.location.hostname === 'localhost') { protocol = 'ws'; } @@ -77,7 +77,7 @@ export class WebSocketService { socket.onopen = () => { clearTimeout(connectionTimer); this.errorFired = false; - updateStatus(StatusEnum.CONNECTED, 'Connected'); + updateStatus(App.StatusEnum.CONNECTED, 'Connected'); this.keepAliveService.startPingLoop(this.keepalive, (pingReceived: () => void) => { this.config.keepAliveFn(pingReceived); @@ -87,7 +87,7 @@ export class WebSocketService { socket.onclose = () => { // dont overwrite failure messages if (!this.errorFired) { - updateStatus(StatusEnum.DISCONNECTED, 'Connection Closed'); + updateStatus(App.StatusEnum.DISCONNECTED, 'Connection Closed'); } this.errorFired = false; this.keepAliveService.endPingLoop(); @@ -95,7 +95,7 @@ export class WebSocketService { socket.onerror = () => { this.errorFired = true; - updateStatus(StatusEnum.DISCONNECTED, 'Connection Failed'); + updateStatus(App.StatusEnum.DISCONNECTED, 'Connection Failed'); SessionPersistence.connectionFailed(); }; diff --git a/webclient/src/websocket/services/command-options.spec.ts b/webclient/src/websocket/services/command-options.spec.ts index 8c520781c..8548c6c06 100644 --- a/webclient/src/websocket/services/command-options.spec.ts +++ b/webclient/src/websocket/services/command-options.spec.ts @@ -1,12 +1,12 @@ -vi.mock('@bufbuild/protobuf', () => ({ - getExtension: vi.fn(), -})); +import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; +import { Data } from '@app/types'; +vi.mock('@bufbuild/protobuf', async () => { + const actual = await vi.importActual('@bufbuild/protobuf'); + return { ...actual, getExtension: vi.fn() }; +}); -vi.mock('generated/proto/response_pb', () => ({ - Response_ResponseCode: { RespOk: 1 }, -})); +import { create, getExtension } from '@bufbuild/protobuf'; -import { getExtension } from '@bufbuild/protobuf'; import { handleResponse } from './command-options'; beforeEach(() => { @@ -17,42 +17,43 @@ describe('handleResponse', () => { it('calls onResponse and returns early when provided', () => { const onResponse = vi.fn(); const onSuccess = vi.fn(); - handleResponse('test', { responseCode: 99 } as any, { onResponse, onSuccess }); + handleResponse('test', create(Data.ResponseSchema, { responseCode: 99 }), { onResponse, onSuccess }); expect(onResponse).toHaveBeenCalled(); expect(onSuccess).not.toHaveBeenCalled(); }); it('calls onSuccess when responseCode is RespOk and no responseExt', () => { const onSuccess = vi.fn(); - const raw = { responseCode: 1 } as any; + const raw = create(Data.ResponseSchema, { responseCode: Data.Response_ResponseCode.RespOk }); handleResponse('test', raw, { onSuccess }); expect(onSuccess).toHaveBeenCalledWith(); }); it('calls onSuccess with nested response when responseExt is set', () => { - vi.mocked(getExtension).mockReturnValue({ nested: true } as any); + vi.mocked(getExtension).mockReturnValue({ nested: true }); const onSuccess = vi.fn(); - const fakeExt = {} as any; - const raw = { responseCode: 1 } as any; + const fakeExt = {} as unknown as GenExtension; + const raw = create(Data.ResponseSchema, { responseCode: Data.Response_ResponseCode.RespOk }); handleResponse('test', raw, { onSuccess, responseExt: fakeExt }); expect(onSuccess).toHaveBeenCalledWith({ nested: true }, raw); }); it('calls onResponseCode handler when code matches', () => { const specificHandler = vi.fn(); - handleResponse('test', { responseCode: 5 } as any, { onResponseCode: { 5: specificHandler } }); + handleResponse('test', create(Data.ResponseSchema, { responseCode: 5 }), { onResponseCode: { 5: specificHandler } }); expect(specificHandler).toHaveBeenCalled(); }); it('calls onError when responseCode is not RespOk and no specific handler', () => { const onError = vi.fn(); - handleResponse('test', { responseCode: 99 } as any, { onError }); - expect(onError).toHaveBeenCalledWith(99, { responseCode: 99 }); + const raw = create(Data.ResponseSchema, { responseCode: 99 }); + handleResponse('test', raw, { onError }); + expect(onError).toHaveBeenCalledWith(99, raw); }); it('logs error to console when no callbacks for non-RespOk response', () => { const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - handleResponse('test.Type', { responseCode: 42 } as any, {}); + handleResponse('test.Type', create(Data.ResponseSchema, { responseCode: 42 }), {}); expect(consoleSpy).toHaveBeenCalled(); consoleSpy.mockRestore(); }); diff --git a/webclient/src/websocket/services/command-options.ts b/webclient/src/websocket/services/command-options.ts index d9d3b0be8..671d0a02b 100644 --- a/webclient/src/websocket/services/command-options.ts +++ b/webclient/src/websocket/services/command-options.ts @@ -1,16 +1,16 @@ import { getExtension } from '@bufbuild/protobuf'; import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; -import { Response_ResponseCode, type Response } from 'generated/proto/response_pb'; +import { Data } from '@app/types'; interface CommandOptionsBase { - onError?: (responseCode: number, raw: Response) => void; - onResponseCode?: { [code: number]: (raw: Response) => void }; - onResponse?: (raw: Response) => void; + onError?: (responseCode: number, raw: Data.Response) => void; + onResponseCode?: { [code: number]: (raw: Data.Response) => void }; + onResponse?: (raw: Data.Response) => void; } export interface CommandOptionsWithResponse extends CommandOptionsBase { - responseExt: GenExtension; - onSuccess?: (response: R, raw: Response) => void; + responseExt: GenExtension; + onSuccess?: (response: R, raw: Data.Response) => void; } export interface CommandOptionsWithoutResponse extends CommandOptionsBase { @@ -24,7 +24,7 @@ export function hasResponseExt(options: CommandOptions): options is Comman return options.responseExt !== undefined; } -export function handleResponse(typeName: string, raw: Response, options: CommandOptions): void { +export function handleResponse(typeName: string, raw: Data.Response, options: CommandOptions): void { if (options.onResponse) { options.onResponse(raw); return; @@ -32,7 +32,7 @@ export function handleResponse(typeName: string, raw: Response, options: Comm const { responseCode } = raw; - if (responseCode === Response_ResponseCode.RespOk) { + if (responseCode === Data.Response_ResponseCode.RespOk) { if (hasResponseExt(options)) { options.onSuccess?.(getExtension(raw, options.responseExt), raw); } else { diff --git a/webclient/src/websocket/services/protobuf-types.ts b/webclient/src/websocket/services/protobuf-types.ts deleted file mode 100644 index 395442de7..000000000 --- a/webclient/src/websocket/services/protobuf-types.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { GenExtension } from '@bufbuild/protobuf/codegenv2'; -import type { RoomEvent } from 'generated/proto/room_event_pb'; -import type { SessionEvent } from 'generated/proto/session_event_pb'; -import type { GameEvent } from 'generated/proto/game_event_pb'; -import type { GameEventMeta } from 'types'; - -type SessionRegistryEntry = [ - GenExtension, - (value: V) => void -]; -export type SessionExtensionRegistry = SessionRegistryEntry[]; - -type RoomRegistryEntry = [ - GenExtension, - (value: V, roomEvent: RoomEvent) => void -]; -export type RoomExtensionRegistry = RoomRegistryEntry[]; - -type GameRegistryEntry = [ - GenExtension, - (value: V, meta: GameEventMeta) => void -]; -export type GameExtensionRegistry = GameRegistryEntry[]; - -export function makeSessionEntry( - ext: GenExtension, - handler: (value: V) => void -): SessionRegistryEntry { - return [ext as GenExtension, handler as (value: unknown) => void]; -} - -export function makeRoomEntry( - ext: GenExtension, - handler: (value: V, roomEvent: RoomEvent) => void -): RoomRegistryEntry { - return [ext as GenExtension, handler as RoomRegistryEntry[1]]; -} - -export function makeGameEntry( - ext: GenExtension, - handler: (value: V, meta: GameEventMeta) => void -): GameRegistryEntry { - return [ext as GenExtension, handler as GameRegistryEntry[1]]; -} diff --git a/webclient/src/websocket/utils/passwordHasher.spec.ts b/webclient/src/websocket/utils/passwordHasher.spec.ts index 7ab16d128..388261b36 100644 --- a/webclient/src/websocket/utils/passwordHasher.spec.ts +++ b/webclient/src/websocket/utils/passwordHasher.spec.ts @@ -1,4 +1,4 @@ -vi.mock('generated/proto/event_server_identification_pb', () => ({ +vi.mock('../../generated/proto/event_server_identification_pb', () => ({ Event_ServerIdentification_ServerOptions: { SupportsPasswordHash: 2 }, })); diff --git a/webclient/src/websocket/utils/passwordHasher.ts b/webclient/src/websocket/utils/passwordHasher.ts index 3f726f10f..22951ce49 100644 --- a/webclient/src/websocket/utils/passwordHasher.ts +++ b/webclient/src/websocket/utils/passwordHasher.ts @@ -1,6 +1,6 @@ import sha512 from 'crypto-js/sha512'; import Base64 from 'crypto-js/enc-base64'; -import { Event_ServerIdentification_ServerOptions } from 'generated/proto/event_server_identification_pb'; +import { Data } from '@app/types'; const HASH_ROUNDS = 1_000; const SALT_LENGTH = 16; @@ -28,5 +28,5 @@ export const generateSalt = (): string => { export const passwordSaltSupported = (serverOptions: number): number => { // Intentional use of Bitwise operator b/c of how Servatrice Enums work - return serverOptions & Event_ServerIdentification_ServerOptions.SupportsPasswordHash; + return serverOptions & Data.Event_ServerIdentification_ServerOptions.SupportsPasswordHash; } diff --git a/webclient/tsconfig.json b/webclient/tsconfig.json index 3ce50dc96..1e851244e 100644 --- a/webclient/tsconfig.json +++ b/webclient/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "baseUrl": "src", "target": "es2020", "lib": [ "dom", @@ -10,6 +9,7 @@ "typeRoots": [ "node_modules/@types" ], + "types": ["node"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -22,7 +22,21 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "noFallthroughCasesInSwitch": false + "noFallthroughCasesInSwitch": false, + "paths": { + "@app/api": ["./src/api/index.ts"], + "@app/components": ["./src/components/index.ts"], + "@app/containers": ["./src/containers/index.ts"], + "@app/dialogs": ["./src/dialogs/index.ts"], + "@app/forms": ["./src/forms/index.ts"], + "@app/hooks": ["./src/hooks/index.ts"], + "@app/images": ["./src/images/index.ts"], + "@app/services": ["./src/services/index.ts"], + "@app/store": ["./src/store/index.ts"], + "@app/types": ["./src/types/index.ts"], + "@app/websocket": ["./src/websocket/index.ts"], + "@app/generated": ["./src/generated/index.ts"] + } }, "include": [ "src" From ae1bc3da386065ed69da2ce2f9f737d3a2246f37 Mon Sep 17 00:00:00 2001 From: seavor Date: Wed, 15 Apr 2026 18:06:39 -0500 Subject: [PATCH 15/38] upgrade packages --- webclient/package-lock.json | 2181 ++++++----------- webclient/package.json | 29 +- webclient/src/__test-utils__/globalGuards.ts | 48 + webclient/src/__test-utils__/index.ts | 1 + .../ThreePaneLayout/ThreePaneLayout.tsx | 52 +- webclient/src/components/Toast/Toast.tsx | 4 +- webclient/src/containers/App/AppShell.tsx | 44 +- webclient/src/containers/Decks/Decks.tsx | 19 +- webclient/src/containers/Game/Game.tsx | 19 +- webclient/src/containers/Layout/LeftNav.tsx | 14 +- webclient/src/containers/Player/Player.tsx | 18 +- webclient/src/hooks/useReduxEffect.tsx | 54 +- webclient/src/i18n-backend.ts | 4 +- webclient/src/setupTests.ts | 22 + .../src/store/game/game.dispatch.spec.ts | 75 +- webclient/src/store/game/game.reducer.spec.ts | 23 +- .../store/rooms/__mocks__/rooms-fixtures.ts | 13 +- .../src/store/rooms/rooms.dispatch.spec.ts | 45 +- .../src/store/server/server.dispatch.spec.ts | 151 +- .../src/websocket/commands/session/login.ts | 4 +- .../src/websocket/events/game/joinGame.ts | 2 +- .../events/game/playerPropertiesChanged.ts | 2 +- .../events/session/sessionEvents.spec.ts | 3 + .../services/ProtobufService.spec.ts | 42 +- .../src/websocket/services/ProtobufService.ts | 2 +- .../services/WebSocketService.spec.ts | 31 +- .../websocket/services/WebSocketService.ts | 2 +- .../services/command-options.spec.ts | 8 +- .../websocket/utils/passwordHasher.spec.ts | 3 +- webclient/vite.config.ts | 6 +- 30 files changed, 1138 insertions(+), 1783 deletions(-) create mode 100644 webclient/src/__test-utils__/globalGuards.ts create mode 100644 webclient/src/__test-utils__/index.ts diff --git a/webclient/package-lock.json b/webclient/package-lock.json index e51c495d6..9f48a49b2 100644 --- a/webclient/package-lock.json +++ b/webclient/package-lock.json @@ -11,25 +11,25 @@ "@bufbuild/protobuf": "^2.11.0", "@emotion/react": "^11.8.2", "@emotion/styled": "^11.8.1", - "@mui/icons-material": "^7.3.10", - "@mui/material": "^7.3.10", + "@mui/icons-material": "^9.0.0", + "@mui/material": "^9.0.0", "@reduxjs/toolkit": "^2.11.2", "crypto-js": "^4.2.0", "dexie": "^4.4.2", - "dompurify": "^3.3.3", + "dompurify": "^3.4.0", "final-form": "^5.0.0", "final-form-set-field-touched": "^1.0.1", - "i18next": "^26.0.4", + "i18next": "^26.0.5", "i18next-browser-languagedetector": "^8.2.1", "i18next-icu": "^2.0.3", "intl-messageformat": "^11.2.1", "lodash": "^4.17.21", "prop-types": "^15.8.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", "react-final-form": "^7.0.0", "react-final-form-listeners": "^3.0.0", - "react-i18next": "^17.0.2", + "react-i18next": "^17.0.3", "react-redux": "^9.2.0", "react-router-dom": "^7.14.1", "react-virtualized-auto-sizer": "^2.0.3", @@ -37,10 +37,10 @@ "rxjs": "^7.5.4" }, "devDependencies": { - "@bufbuild/buf": "^1.67.0", + "@bufbuild/buf": "^1.68.1", "@bufbuild/protoc-gen-es": "^2.11.0", "@eslint/js": "^10.0.1", - "@mui/types": "^7.1.3", + "@mui/types": "^9.0.0", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.4.0", "@testing-library/react": "^16.3.2", @@ -48,23 +48,22 @@ "@types/lodash": "^4.14.179", "@types/node": "^22.19.17", "@types/prop-types": "^15.7.4", - "@types/react": "18.0.24", - "@types/react-dom": "18.0.8", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", "@types/react-virtualized-auto-sizer": "^1.0.1", "@types/react-window": "^1.8.5", "@typescript-eslint/eslint-plugin": "^8.58.2", "@typescript-eslint/parser": "^8.58.2", - "@vitejs/plugin-react": "^5.2.0", + "@vitejs/plugin-react": "^6.0.1", "@vitest/coverage-v8": "^4.1.4", "eslint": "^10.2.0", "fs-extra": "^11.3.4", "globals": "^17.5.0", "husky": "^9.1.7", "jsdom": "^29.0.2", - "typescript": "~5.8", + "typescript": "~6.0", "typescript-eslint": "^8.58.2", - "vite": "^6.4.2", - "vite-tsconfig-paths": "^5.1.4", + "vite": "^8.0.8", "vitest": "^4.1.4" } }, @@ -128,64 +127,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/generator": { "version": "7.29.1", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", @@ -202,33 +143,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -251,34 +165,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -297,30 +183,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/parser": { "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", @@ -336,38 +198,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", @@ -446,9 +276,9 @@ } }, "node_modules/@bufbuild/buf": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.67.0.tgz", - "integrity": "sha512-BLfgGmNFiHM79PcaafFNiP/+xxbdyFp1neDDdJd6R0tu7McO+WgJHM6vyNYRm7vXOSgO1uUPE4X3YFdBgcWk2Q==", + "version": "1.68.1", + "resolved": "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.68.1.tgz", + "integrity": "sha512-QDJ3oy4qZ5EVS2JYtmpE1n9FuaoABthxIddXB050huGddatr1sjHJSSAXXpLotOI18pW3KQ4zzU1x5Ms+pEEOw==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -461,19 +291,19 @@ "node": ">=12" }, "optionalDependencies": { - "@bufbuild/buf-darwin-arm64": "1.67.0", - "@bufbuild/buf-darwin-x64": "1.67.0", - "@bufbuild/buf-linux-aarch64": "1.67.0", - "@bufbuild/buf-linux-armv7": "1.67.0", - "@bufbuild/buf-linux-x64": "1.67.0", - "@bufbuild/buf-win32-arm64": "1.67.0", - "@bufbuild/buf-win32-x64": "1.67.0" + "@bufbuild/buf-darwin-arm64": "1.68.1", + "@bufbuild/buf-darwin-x64": "1.68.1", + "@bufbuild/buf-linux-aarch64": "1.68.1", + "@bufbuild/buf-linux-armv7": "1.68.1", + "@bufbuild/buf-linux-x64": "1.68.1", + "@bufbuild/buf-win32-arm64": "1.68.1", + "@bufbuild/buf-win32-x64": "1.68.1" } }, "node_modules/@bufbuild/buf-darwin-arm64": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.67.0.tgz", - "integrity": "sha512-9h/1E2FNCSIt9m4wriGiXt8gHrg8VBOOpmUPVr68axZxb17krPQrIZBPsx05yNpbyvSrPj26/jO2aoqpZsG1vw==", + "version": "1.68.1", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.68.1.tgz", + "integrity": "sha512-+Cu/2Kr6Add3s+Zk/edcF9QdpnrsukQkdR/z4fk4+qr6YZqfWfiV8f+s14I3h7qPrPnGeCeynvmZ9NmJ1BMYuA==", "cpu": [ "arm64" ], @@ -488,9 +318,9 @@ } }, "node_modules/@bufbuild/buf-darwin-x64": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.67.0.tgz", - "integrity": "sha512-9kNu0JBR+TQvxCD6NBooy03g8sLNZGEd0umkWHzdO/05HuV/J6GecMGx1kJ2MYlZQHM4/MljfIuYQUblP1nP4A==", + "version": "1.68.1", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.68.1.tgz", + "integrity": "sha512-hvAs452aJ6io9hZKSfr3TvC+//16zW5y5u3ucsIXVkl5mkmKWSCkPbZwGpjNCfRGGUsyRJGL6rixxTgLKw2k5w==", "cpu": [ "x64" ], @@ -505,9 +335,9 @@ } }, "node_modules/@bufbuild/buf-linux-aarch64": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.67.0.tgz", - "integrity": "sha512-hlA20Oot20nW/9CzPBMPPPMfUarKvzqni+Njgrw8T43IFoQWQv8iIRoWWOgOQTGCm4PmjYwiojzEHOEaaKrzTg==", + "version": "1.68.1", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.68.1.tgz", + "integrity": "sha512-GLCakHzZVKUPlAiJEPGMBLW+yBk8tuz6NNcoeQU5lB5AO7ks8V8x9cy4CQjne4YSl3niF1JtvAQckLKhEWPueQ==", "cpu": [ "arm64" ], @@ -522,9 +352,9 @@ } }, "node_modules/@bufbuild/buf-linux-armv7": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-armv7/-/buf-linux-armv7-1.67.0.tgz", - "integrity": "sha512-hO9FEEtloITNaxW89rzKUjAsgnX1+rth7IZbK0Z+ohatXdanYg7Kv66yWffytaYf2iHltTbY6W/H4C3x0Uimbg==", + "version": "1.68.1", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-armv7/-/buf-linux-armv7-1.68.1.tgz", + "integrity": "sha512-lUMCULl3MOYQe0oAPWnqNVYy8pL+F3Jeq6C4sSY+0E9udaACMc2mZ32gYingaMop9O1qS58HjJbezFxxL+CJqg==", "cpu": [ "arm" ], @@ -539,9 +369,9 @@ } }, "node_modules/@bufbuild/buf-linux-x64": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.67.0.tgz", - "integrity": "sha512-KBOWZ0NbhJSfXLM3JEX2AEs32jyHvTKD7wkIYudqOTxPUqwM1MXUg7m2Xw5nP1pcKH4RKS5HFijPMeOW/XUQ8Q==", + "version": "1.68.1", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.68.1.tgz", + "integrity": "sha512-eRU3UWiZQthAgx+qFTG3EeJ/VeOcZzAkKYGt5ansOnOIJHBm+3RG2KqA+Jm8q3EFqB1XpVcGxPXnIu/qmFJXaQ==", "cpu": [ "x64" ], @@ -556,9 +386,9 @@ } }, "node_modules/@bufbuild/buf-win32-arm64": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.67.0.tgz", - "integrity": "sha512-ARGPwOv0lkUp3FU7bUMpYzqoJInx2qkk1ECBEC9XZMnRKmhCbyzmBoBKChBBJhEyDFdzPivhjg//zk5AlQ3bFA==", + "version": "1.68.1", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.68.1.tgz", + "integrity": "sha512-v3xlKzs3l2C+mYv+T0sYol05DTmsFKYmM5Vz8+AyrXdjxRwq2QH7m0arVWwxHX2MwyhQxKA+qqjoF8bCUM7xxA==", "cpu": [ "arm64" ], @@ -573,9 +403,9 @@ } }, "node_modules/@bufbuild/buf-win32-x64": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.67.0.tgz", - "integrity": "sha512-x9fkxEbjb2U4petBbESvNx+sfSQJONJxKOQzPfEKALksqRlvh7ktoHrYbygErnRZBSTNgrXzAqFI1GxMGEGSLQ==", + "version": "1.68.1", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.68.1.tgz", + "integrity": "sha512-b62pwu+G7n5tF8n1QIoT85K7xgKJZS8SzdN020weOa7IVvMNHCDqMq7nrkz46fXCkK7MtD1YJ6sUp86sWSZPsw==", "cpu": [ "x64" ], @@ -786,6 +616,40 @@ "node": ">=20.19.0" } }, + "node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -932,448 +796,6 @@ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", "license": "MIT" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -1590,17 +1012,6 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -1627,9 +1038,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.10.tgz", - "integrity": "sha512-vrOpWRmPJSuwLo23J62wggEm/jvGdzqctej+UOCtgDUz6nZJQuj3ByPccVyaa7eQmwAzUwKN56FQPMKkqbj1GA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-9.0.0.tgz", + "integrity": "sha512-uwQNGkhv0lf7ufxw6QXev77BW6pWbW+7uxYjU5+rfp4lBkFtMEgJCsarTM3Tn+i0lGx6+Ol2u88JdGXr0GDskA==", "license": "MIT", "funding": { "type": "opencollective", @@ -1637,12 +1048,12 @@ } }, "node_modules/@mui/icons-material": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.10.tgz", - "integrity": "sha512-Au0ma4NSKGKNiimukj8UT/W1x2Qx6Qwn2RvFGykiSqVLYBNlIOPbjnIMvrwLGLu89EEpTVdu/ys/OduZR+tWqw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-9.0.0.tgz", + "integrity": "sha512-oDwyvI6LgjWRC9MBcSGvLkPud9S9ELgSBQFYxa1rYcZn6Br55dn22SyvsPDMsn0G8OndFk53iMT45W5mNqrogw==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.6" + "@babel/runtime": "^7.29.2" }, "engines": { "node": ">=14.0.0" @@ -1652,7 +1063,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^7.3.10", + "@mui/material": "^9.0.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -1663,22 +1074,22 @@ } }, "node_modules/@mui/material": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.10.tgz", - "integrity": "sha512-cHvGOk2ZEfbQt3LnGe0ZKd/ETs9gsUpkW66DCO+GSjMZhpdKU4XsuIr7zJ/B/2XaN8ihxuzHfYAR4zPtCN4RYg==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-9.0.0.tgz", + "integrity": "sha512-+VP/oQCDhDR87NQQgXnNBG8dwy6GNuQLnenS1pZvkbn2dKFSxRSRMybTpH9xUxXP+316mlYDy5CSbYtusnCWtw==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.6", - "@mui/core-downloads-tracker": "^7.3.10", - "@mui/system": "^7.3.10", - "@mui/types": "^7.4.12", - "@mui/utils": "^7.3.10", + "@babel/runtime": "^7.29.2", + "@mui/core-downloads-tracker": "^9.0.0", + "@mui/system": "^9.0.0", + "@mui/types": "^9.0.0", + "@mui/utils": "^9.0.0", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", "csstype": "^3.2.3", "prop-types": "^15.8.1", - "react-is": "^19.2.3", + "react-is": "^19.2.4", "react-transition-group": "^4.4.5" }, "engines": { @@ -1691,7 +1102,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^7.3.10", + "@mui/material-pigment-css": "^9.0.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1712,13 +1123,13 @@ } }, "node_modules/@mui/private-theming": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.10.tgz", - "integrity": "sha512-j3EZN+zOctxUISvJSmsEPo5o2F8zse4l5vRkBY+ps6UtnL6J7o14kUaI4w7gwo73id9e3cDNMVQK/9BVaMHVBw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-9.0.0.tgz", + "integrity": "sha512-JtuZoaiCqwD6vjgYu6Xp3T7DZkrxJlgtDz5yESzhI34fEX5hHMh2VJUbuL9UOg8xrfIFMrq6dcYoH/7Zi4G0RA==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.6", - "@mui/utils": "^7.3.10", + "@babel/runtime": "^7.29.2", + "@mui/utils": "^9.0.0", "prop-types": "^15.8.1" }, "engines": { @@ -1739,12 +1150,12 @@ } }, "node_modules/@mui/styled-engine": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.10.tgz", - "integrity": "sha512-WxE9SiF8xskAQqGjsp0poXCkCqsoXFEsSr0HBXfApmGHR+DBnXRp+z46Vsltg4gpPM4Z96DeAQRpeAOnhNg7Ng==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-9.0.0.tgz", + "integrity": "sha512-9RLGdX4Jg0aQPRuvqh/OLzYSPlgd5zyEw5/1HIRfdavSiOd03WtUaGZH9/w1RoTYuRKwpgy0hpIFaMHIqPVIWg==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.6", + "@babel/runtime": "^7.29.2", "@emotion/cache": "^11.14.0", "@emotion/serialize": "^1.3.3", "@emotion/sheet": "^1.4.0", @@ -1773,16 +1184,16 @@ } }, "node_modules/@mui/system": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.10.tgz", - "integrity": "sha512-/sfPpdpJaQn7BSF+avjIdHSYmxHp0UOBYNxSG9QGKfMOD6sLANCpRPCnanq1Pe0lFf0NHkO2iUk0TNzdWC1USQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-9.0.0.tgz", + "integrity": "sha512-YnC5Zg6j04IxiLc/boAKs0464jfZlLFVa7mf5E8lF0XOtZVUvG6R6gJK50lgUYdaaLdyLfxF6xR7LaPuEpeT/g==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.6", - "@mui/private-theming": "^7.3.10", - "@mui/styled-engine": "^7.3.10", - "@mui/types": "^7.4.12", - "@mui/utils": "^7.3.10", + "@babel/runtime": "^7.29.2", + "@mui/private-theming": "^9.0.0", + "@mui/styled-engine": "^9.0.0", + "@mui/types": "^9.0.0", + "@mui/utils": "^9.0.0", "clsx": "^2.1.1", "csstype": "^3.2.3", "prop-types": "^15.8.1" @@ -1813,12 +1224,12 @@ } }, "node_modules/@mui/types": { - "version": "7.4.12", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.12.tgz", - "integrity": "sha512-iKNAF2u9PzSIj40CjvKJWxFXJo122jXVdrmdh0hMYd+FR+NuJMkr/L88XwWLCRiJ5P1j+uyac25+Kp6YC4hu6w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-9.0.0.tgz", + "integrity": "sha512-i1cuFCAWN44b3AJWO7mh7tuh1sqbQSeVr/94oG0TX5uXivac8XalgE4/6fQZcmGZigzbQ35IXxj/4jLpRIBYZg==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.6" + "@babel/runtime": "^7.29.2" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1830,17 +1241,17 @@ } }, "node_modules/@mui/utils": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.10.tgz", - "integrity": "sha512-7y2eIfy0h7JPz+Yy4pS+wgV68d46PuuxDqKBN4Q8VlPQSsCAGwroMCV6xWyc7g9dvEp8ZNFsknc59GHWO+r6Ow==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-9.0.0.tgz", + "integrity": "sha512-bQcqyg/gjULUqTuyUjSAFr6LQGLvtkNtDbJerAtoUn9kGZ0hg5QJiN1PLHMLbeFpe3te1831uq7GFl2ITokGdg==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.6", - "@mui/types": "^7.4.12", + "@babel/runtime": "^7.29.2", + "@mui/types": "^9.0.0", "@types/prop-types": "^15.7.15", "clsx": "^2.1.1", "prop-types": "^15.8.1", - "react-is": "^19.2.3" + "react-is": "^19.2.4" }, "engines": { "node": ">=14.0.0" @@ -1859,6 +1270,35 @@ } } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", + "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -1895,60 +1335,27 @@ } } }, - "node_modules/@reduxjs/toolkit/node_modules/redux": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" - }, - "node_modules/@reduxjs/toolkit/node_modules/redux-thunk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", - "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "peerDependencies": { - "redux": "^5.0.0" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.3", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", - "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", - "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", - "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", - "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", "cpu": [ "arm64" ], @@ -1957,12 +1364,15 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", - "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", "cpu": [ "x64" ], @@ -1971,26 +1381,15 @@ "optional": true, "os": [ "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", - "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", - "cpu": [ - "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", - "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", "cpu": [ "x64" ], @@ -1999,12 +1398,15 @@ "optional": true, "os": [ "freebsd" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", - "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", + "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", "cpu": [ "arm" ], @@ -2013,26 +1415,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", - "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", - "cpu": [ - "arm" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", - "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", "cpu": [ "arm64" ], @@ -2041,12 +1432,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", - "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", "cpu": [ "arm64" ], @@ -2055,40 +1449,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", - "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", - "cpu": [ - "loong64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", - "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", - "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", "cpu": [ "ppc64" ], @@ -2097,54 +1466,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", - "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", - "cpu": [ - "ppc64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", - "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", - "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", - "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", "cpu": [ "s390x" ], @@ -2153,12 +1483,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", - "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", "cpu": [ "x64" ], @@ -2167,12 +1500,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", - "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", "cpu": [ "x64" ], @@ -2181,26 +1517,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", - "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", - "cpu": [ - "x64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", - "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", "cpu": [ "arm64" ], @@ -2209,12 +1534,34 @@ "optional": true, "os": [ "openharmony" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", - "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", + "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", "cpu": [ "arm64" ], @@ -2223,26 +1570,15 @@ "optional": true, "os": [ "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", - "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", - "cpu": [ - "ia32" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", - "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", "cpu": [ "x64" ], @@ -2251,21 +1587,17 @@ "optional": true, "os": [ "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", - "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", - "cpu": [ - "x64" ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.7", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", + "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "license": "MIT" }, "node_modules/@standard-schema/spec": { "version": "1.1.0", @@ -2299,23 +1631,6 @@ "node": ">=18" } }, - "node_modules/@testing-library/dom/node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/@testing-library/dom/node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "license": "MIT" - }, "node_modules/@testing-library/jest-dom": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", @@ -2336,6 +1651,13 @@ "yarn": ">=1" } }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, "node_modules/@testing-library/react": { "version": "16.3.2", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", @@ -2364,6 +1686,17 @@ } } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -2371,51 +1704,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -2495,25 +1783,23 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.0.24", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.24.tgz", - "integrity": "sha512-wRJWT6ouziGUy+9uX0aW4YOJxAY0bG6/AOk5AW5QSvZqI7dk6VBIbXvcVgIw/W5Jrl24f77df98GEKTJGOLx7Q==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "license": "MIT", "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "18.0.8", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.8.tgz", - "integrity": "sha512-C3GYO0HLaOkk9dDAz3Dl4sbe4AKUGTCfFIZsz3n/82dPNN8Du533HzKatDxeUYWu24wJgMP1xICqkWk1YOLOIw==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", - "dependencies": { - "@types/react": "*" + "peerDependencies": { + "@types/react": "^19.2.0" } }, "node_modules/@types/react-transition-group": { @@ -2545,13 +1831,6 @@ "@types/react": "*" } }, - "node_modules/@types/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -2812,24 +2091,29 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", - "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", + "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.29.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-rc.3", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.18.0" + "@rolldown/pluginutils": "1.0.0-rc.7" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } } }, "node_modules/@vitest/coverage-v8": { @@ -3047,13 +2331,13 @@ } }, "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" + "dependencies": { + "dequal": "^2.0.3" } }, "node_modules/assertion-error": { @@ -3110,19 +2394,6 @@ "node": "18 || 20 || >=22" } }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.18", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.18.tgz", - "integrity": "sha512-VSnGQAOLtP5mib/DPyg2/t+Tlv65NTBz83BJBJvmLVHHuKJVaDOBvJJykiT5TR++em5nfAySPccDZDa4oSrn8A==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/bidi-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", @@ -3146,40 +2417,6 @@ "node": "18 || 20 || >=22" } }, - "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3189,27 +2426,6 @@ "node": ">=6" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001787", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz", - "integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -3367,6 +2583,16 @@ "node": ">=6" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/dexie": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.4.2.tgz", @@ -3374,9 +2600,9 @@ "license": "Apache-2.0" }, "node_modules/dom-accessibility-api": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", - "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, "license": "MIT" }, @@ -3391,21 +2617,14 @@ } }, "node_modules/dompurify": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", - "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.0.tgz", + "integrity": "sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.335", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.335.tgz", - "integrity": "sha512-q9n5T4BR4Xwa2cwbrwcsDJtHD/enpQ5S1xF1IAtdqf5AAgqDFmR/aakqH3ChFdqd/QXJhS3rnnXFtexU7rax6Q==", - "dev": true, - "license": "ISC" - }, "node_modules/entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", @@ -3444,58 +2663,6 @@ "dev": true, "license": "MIT" }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3876,16 +3043,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3912,13 +3069,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true, - "license": "MIT" - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -4009,9 +3159,9 @@ } }, "node_modules/i18next": { - "version": "26.0.4", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.0.4.tgz", - "integrity": "sha512-gXF7U9bfioXPLv7mw8Qt2nfO7vij5MyINvPgVv99pX3fL1Y01pw2mKBFrlYpRxRCl2wz3ISenj6VsMJT2isfuA==", + "version": "26.0.5", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.0.5.tgz", + "integrity": "sha512-9uHb4T27TdV36phJXcbpnRPt5yzAfqHXVrdASvmHZyPuZJtrLythd+GyXhiaHV5LlpuuskbAqhwPjmfTbKbi8w==", "funding": [ { "type": "individual", @@ -4267,16 +3417,6 @@ } } }, - "node_modules/jsdom/node_modules/lru-cache": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", - "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -4316,19 +3456,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/jsonfile": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", @@ -4366,6 +3493,267 @@ "node": ">= 0.8.0" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -4407,13 +3795,13 @@ } }, "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" } }, "node_modules/lz-string": { @@ -4529,13 +3917,6 @@ "dev": true, "license": "MIT" }, - "node_modules/node-releases": { - "version": "2.0.37", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", - "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", - "dev": true, - "license": "MIT" - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4697,10 +4078,23 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/postcss": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", - "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", "dev": true, "funding": [ { @@ -4786,28 +4180,24 @@ } }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.2.5" } }, "node_modules/react-final-form": { @@ -4842,9 +4232,9 @@ } }, "node_modules/react-i18next": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.2.tgz", - "integrity": "sha512-shBftH2vaTWK2Bsp7FiL+cevx3xFJlvFxmsDFQSrJc+6twHkP0tv/bGa01VVWzpreUVVwU+3Hev5iFqRg65RwA==", + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.3.tgz", + "integrity": "sha512-x4xjvUNZ56T+zfXWNedNnCET9Xq1IBYWX7IsWo5cCQ/RT+Rm7GWqt0h9PShFi4IhyMnsdiu1C6Jc4DE+/S3PFQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.2", @@ -4897,16 +4287,6 @@ } } }, - "node_modules/react-refresh": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", - "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react-router": { "version": "7.14.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.1.tgz", @@ -4995,6 +4375,21 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -5041,51 +4436,47 @@ "node": ">=4" } }, - "node_modules/rollup": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", - "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "node_modules/rolldown": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", + "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@oxc-project/types": "=0.124.0", + "@rolldown/pluginutils": "1.0.0-rc.15" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.1", - "@rollup/rollup-android-arm64": "4.60.1", - "@rollup/rollup-darwin-arm64": "4.60.1", - "@rollup/rollup-darwin-x64": "4.60.1", - "@rollup/rollup-freebsd-arm64": "4.60.1", - "@rollup/rollup-freebsd-x64": "4.60.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", - "@rollup/rollup-linux-arm-musleabihf": "4.60.1", - "@rollup/rollup-linux-arm64-gnu": "4.60.1", - "@rollup/rollup-linux-arm64-musl": "4.60.1", - "@rollup/rollup-linux-loong64-gnu": "4.60.1", - "@rollup/rollup-linux-loong64-musl": "4.60.1", - "@rollup/rollup-linux-ppc64-gnu": "4.60.1", - "@rollup/rollup-linux-ppc64-musl": "4.60.1", - "@rollup/rollup-linux-riscv64-gnu": "4.60.1", - "@rollup/rollup-linux-riscv64-musl": "4.60.1", - "@rollup/rollup-linux-s390x-gnu": "4.60.1", - "@rollup/rollup-linux-x64-gnu": "4.60.1", - "@rollup/rollup-linux-x64-musl": "4.60.1", - "@rollup/rollup-openbsd-x64": "4.60.1", - "@rollup/rollup-openharmony-arm64": "4.60.1", - "@rollup/rollup-win32-arm64-msvc": "4.60.1", - "@rollup/rollup-win32-ia32-msvc": "4.60.1", - "@rollup/rollup-win32-x64-gnu": "4.60.1", - "@rollup/rollup-win32-x64-msvc": "4.60.1", - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" } }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", + "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", + "dev": true, + "license": "MIT" + }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -5109,13 +4500,10 @@ } }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" }, "node_modules/semver": { "version": "7.7.4", @@ -5193,9 +4581,9 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", - "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", "dev": true, "license": "MIT" }, @@ -5284,19 +4672,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tinyrainbow": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", @@ -5366,27 +4741,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/tsconfck": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", - "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", - "dev": true, - "license": "MIT", - "bin": { - "tsconfck": "bin/tsconfck.js" - }, - "engines": { - "node": "^18 || >=20" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -5407,9 +4761,9 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5471,37 +4825,6 @@ "node": ">= 10.0.0" } }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5522,24 +4845,23 @@ } }, "node_modules/vite": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", - "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", + "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.15", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -5548,14 +4870,15 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -5564,15 +4887,18 @@ "@types/node": { "optional": true }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, "jiti": { "optional": true }, "less": { "optional": true }, - "lightningcss": { - "optional": true - }, "sass": { "optional": true }, @@ -5596,39 +4922,6 @@ } } }, - "node_modules/vite-tsconfig-paths": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", - "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "globrex": "^0.1.2", - "tsconfck": "^3.0.3" - }, - "peerDependencies": { - "vite": "*" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/vitest": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz", @@ -5719,19 +5012,6 @@ } } }, - "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", @@ -5849,13 +5129,6 @@ "dev": true, "license": "MIT" }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, "node_modules/yaml": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", diff --git a/webclient/package.json b/webclient/package.json index 1259a3aa7..b2927025f 100644 --- a/webclient/package.json +++ b/webclient/package.json @@ -21,25 +21,25 @@ "@bufbuild/protobuf": "^2.11.0", "@emotion/react": "^11.8.2", "@emotion/styled": "^11.8.1", - "@mui/icons-material": "^7.3.10", - "@mui/material": "^7.3.10", + "@mui/icons-material": "^9.0.0", + "@mui/material": "^9.0.0", "@reduxjs/toolkit": "^2.11.2", "crypto-js": "^4.2.0", "dexie": "^4.4.2", - "dompurify": "^3.3.3", + "dompurify": "^3.4.0", "final-form": "^5.0.0", "final-form-set-field-touched": "^1.0.1", - "i18next": "^26.0.4", + "i18next": "^26.0.5", "i18next-browser-languagedetector": "^8.2.1", "i18next-icu": "^2.0.3", "intl-messageformat": "^11.2.1", "lodash": "^4.17.21", "prop-types": "^15.8.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", "react-final-form": "^7.0.0", "react-final-form-listeners": "^3.0.0", - "react-i18next": "^17.0.2", + "react-i18next": "^17.0.3", "react-redux": "^9.2.0", "react-router-dom": "^7.14.1", "react-virtualized-auto-sizer": "^2.0.3", @@ -47,10 +47,10 @@ "rxjs": "^7.5.4" }, "devDependencies": { - "@bufbuild/buf": "^1.67.0", + "@bufbuild/buf": "^1.68.1", "@bufbuild/protoc-gen-es": "^2.11.0", "@eslint/js": "^10.0.1", - "@mui/types": "^7.1.3", + "@mui/types": "^9.0.0", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.4.0", "@testing-library/react": "^16.3.2", @@ -58,23 +58,22 @@ "@types/lodash": "^4.14.179", "@types/node": "^22.19.17", "@types/prop-types": "^15.7.4", - "@types/react": "18.0.24", - "@types/react-dom": "18.0.8", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", "@types/react-virtualized-auto-sizer": "^1.0.1", "@types/react-window": "^1.8.5", "@typescript-eslint/eslint-plugin": "^8.58.2", "@typescript-eslint/parser": "^8.58.2", - "@vitejs/plugin-react": "^5.2.0", + "@vitejs/plugin-react": "^6.0.1", "@vitest/coverage-v8": "^4.1.4", "eslint": "^10.2.0", "fs-extra": "^11.3.4", "globals": "^17.5.0", "husky": "^9.1.7", "jsdom": "^29.0.2", - "typescript": "~5.8", + "typescript": "~6.0", "typescript-eslint": "^8.58.2", - "vite": "^6.4.2", - "vite-tsconfig-paths": "^5.1.4", + "vite": "^8.0.8", "vitest": "^4.1.4" }, "browserslist": { diff --git a/webclient/src/__test-utils__/globalGuards.ts b/webclient/src/__test-utils__/globalGuards.ts new file mode 100644 index 000000000..b350d440f --- /dev/null +++ b/webclient/src/__test-utils__/globalGuards.ts @@ -0,0 +1,48 @@ +// Shared lifecycle helpers for test files that need to mutate global state. +// +// The root `setupTests.ts` guards catch leaks even when callers forget to +// clean up, but opt-in helpers make intent explicit at the call site and +// avoid piling cleanup logic onto the shared safety net. + +/** + * Temporarily override fields on `window.location` and return a restore fn. + * + * `Object.defineProperty(window, 'location', ...)` is not a `vi.spyOn` target, + * so `vi.restoreAllMocks()` will NOT undo it. Always pair with the returned + * `restore` callback (ideally in `afterEach`). + */ +export function withMockLocation(overrides: Partial): () => void { + const originalDescriptor = Object.getOwnPropertyDescriptor(window, 'location'); + + Object.defineProperty(window, 'location', { + value: { ...window.location, ...overrides }, + writable: true, + configurable: true, + }); + + return () => { + if (originalDescriptor) { + Object.defineProperty(window, 'location', originalDescriptor); + } + }; +} + +/** + * Push an entry onto a shared event-handler registry array and return a + * teardown function that removes exactly that entry. + * + * Used by ProtobufService specs which install temporary handlers into the + * (mocked) `GameEvents` / `RoomEvents` / `SessionEvents` arrays. Manual + * `.push()`/`.pop()` inside a test body corrupts the array if an assertion + * throws between them — this helper makes the teardown safe to run in + * `afterEach`. + */ +export function withEventRegistry(registry: T[], entry: T): () => void { + registry.push(entry); + return () => { + const index = registry.lastIndexOf(entry); + if (index !== -1) { + registry.splice(index, 1); + } + }; +} diff --git a/webclient/src/__test-utils__/index.ts b/webclient/src/__test-utils__/index.ts new file mode 100644 index 000000000..6d606210a --- /dev/null +++ b/webclient/src/__test-utils__/index.ts @@ -0,0 +1 @@ +export { withMockLocation, withEventRegistry } from './globalGuards'; diff --git a/webclient/src/components/ThreePaneLayout/ThreePaneLayout.tsx b/webclient/src/components/ThreePaneLayout/ThreePaneLayout.tsx index 2b202238c..287e58382 100644 --- a/webclient/src/components/ThreePaneLayout/ThreePaneLayout.tsx +++ b/webclient/src/components/ThreePaneLayout/ThreePaneLayout.tsx @@ -1,42 +1,40 @@ -import { Component, CElement } from 'react'; +import { ReactElement } from 'react'; import Grid from '@mui/material/Grid'; import './ThreePaneLayout.css'; // @DEPRECATED // This component sucks balls, dont use it. It will be removed sooner than later. -class ThreePaneLayout extends Component { - render() { - return ( -
- - - - {this.props.top} - - - {this.props.bottom} - +function ThreePaneLayout(props: ThreePaneLayoutProps) { + return ( +
+ + + + {props.top} - - {this.props.side} + + {props.bottom} -
- ); - } + + {props.side} + +
+
+ ); } interface ThreePaneLayoutProps { - top: CElement, - bottom: CElement, - side?: CElement, + top: ReactElement, + bottom: ReactElement, + side?: ReactElement, fixedHeight?: boolean, } diff --git a/webclient/src/components/Toast/Toast.tsx b/webclient/src/components/Toast/Toast.tsx index ac7c029e7..faf4af586 100644 --- a/webclient/src/components/Toast/Toast.tsx +++ b/webclient/src/components/Toast/Toast.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import ReactDOM from 'react-dom' +import { createPortal } from 'react-dom' import Alert from '@mui/material/Alert'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; @@ -46,7 +46,7 @@ function Toast(props) { return null } - return ReactDOM.createPortal( + return createPortal( node, rootElemRef.current ); diff --git a/webclient/src/containers/App/AppShell.tsx b/webclient/src/containers/App/AppShell.tsx index 9476d76cd..66618d865 100644 --- a/webclient/src/containers/App/AppShell.tsx +++ b/webclient/src/containers/App/AppShell.tsx @@ -1,4 +1,4 @@ -import { Component, Suspense } from 'react'; +import { Suspense, useEffect } from 'react'; import { Provider } from 'react-redux'; import { MemoryRouter as Router } from 'react-router-dom'; import CssBaseline from '@mui/material/CssBaseline'; @@ -10,33 +10,31 @@ import './AppShell.css'; import { ToastProvider } from '@app/components' -class AppShell extends Component { - componentDidMount() { +function AppShell() { + useEffect(() => { // @TODO (1) window.onbeforeunload = () => true; - } + }, []); - handleContextMenu(event) { + const handleContextMenu = (event) => { event.preventDefault(); - } + }; - render() { - return ( - - - - -
- - - - -
-
-
-
- ); - } + return ( + + + + +
+ + + + +
+
+
+
+ ); } export default AppShell; diff --git a/webclient/src/containers/Decks/Decks.tsx b/webclient/src/containers/Decks/Decks.tsx index f37d67261..e5856eaeb 100644 --- a/webclient/src/containers/Decks/Decks.tsx +++ b/webclient/src/containers/Decks/Decks.tsx @@ -1,20 +1,15 @@ -// eslint-disable-next-line -import React, { Component } from "react"; - import { AuthGuard } from '@app/components'; import Layout from '../Layout/Layout'; import './Decks.css'; -class Decks extends Component { - render() { - return ( - - - "Decks" - - ) - } +function Decks() { + return ( + + + "Decks" + + ); } export default Decks; diff --git a/webclient/src/containers/Game/Game.tsx b/webclient/src/containers/Game/Game.tsx index 6be3ab1ca..139147ab1 100644 --- a/webclient/src/containers/Game/Game.tsx +++ b/webclient/src/containers/Game/Game.tsx @@ -1,20 +1,15 @@ -// eslint-disable-next-line -import React, { Component } from "react"; - import { AuthGuard } from '@app/components'; import Layout from '../Layout/Layout'; import './Game.css'; -class Game extends Component { - render() { - return ( - - - "Game" - - ) - } +function Game() { + return ( + + + "Game" + + ); } export default Game; diff --git a/webclient/src/containers/Layout/LeftNav.tsx b/webclient/src/containers/Layout/LeftNav.tsx index db4227b36..0730011d5 100644 --- a/webclient/src/containers/Layout/LeftNav.tsx +++ b/webclient/src/containers/Layout/LeftNav.tsx @@ -5,7 +5,7 @@ import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import CloseIcon from '@mui/icons-material/Close'; -import MailOutlineRoundedIcon from '@mui/icons-material/MailOutline'; +import MailOutlineRoundedIcon from '@mui/icons-material/MailOutlineRounded'; import MenuRoundedIcon from '@mui/icons-material/MenuRounded'; import { AuthenticationService, RoomsService } from '@app/api'; @@ -56,7 +56,7 @@ const LeftNav = () => { } const handleMenuItemClick = (option: string) => { - const route = RouteEnum[option.toUpperCase()]; + const route = App.RouteEnum[option.toUpperCase()]; navigate(generatePath(route)); } @@ -149,10 +149,12 @@ const LeftNav = () => { keepMounted open={!!state.anchorEl} onClose={() => handleMenuClose()} - PaperProps={{ - style: { - marginTop: '32px', - width: '20ch', + slotProps={{ + paper: { + style: { + marginTop: '32px', + width: '20ch', + }, }, }} > diff --git a/webclient/src/containers/Player/Player.tsx b/webclient/src/containers/Player/Player.tsx index 58f5403cf..899285054 100644 --- a/webclient/src/containers/Player/Player.tsx +++ b/webclient/src/containers/Player/Player.tsx @@ -1,18 +1,14 @@ -// eslint-disable-next-line -import React, { Component } from "react"; import Layout from '../Layout/Layout'; import { AuthGuard } from '@app/components'; -class Player extends Component { - render() { - return ( - - - "Player" - - ) - } +function Player() { + return ( + + + "Player" + + ); } export default Player; diff --git a/webclient/src/hooks/useReduxEffect.tsx b/webclient/src/hooks/useReduxEffect.tsx index a84d5d728..3f9ec853d 100644 --- a/webclient/src/hooks/useReduxEffect.tsx +++ b/webclient/src/hooks/useReduxEffect.tsx @@ -4,7 +4,7 @@ File is adapted from https://github.com/Qeepsake/use-redux-effect under MIT Lice * @description */ -import { useRef, useEffect, DependencyList } from 'react' +import { useEffect, useRef, DependencyList } from 'react' import { useStore } from 'react-redux' import { castArray } from 'lodash' @@ -14,36 +14,44 @@ import { castArray } from 'lodash' export type ReduxEffect = (action: any) => void /** - * Subscribes to redux store events - * - * @param effect - * @param type - * @param deps - */ + * Subscribes to redux store events. + * + * On mount, synchronously inspects the current `state.action` so an action + * dispatched between render and effect-commit is still observed — this is + * what lets `` catch a `JOIN_ROOM` that auto-join fired while the + * route was transitioning. + */ export function useReduxEffect( effect: ReduxEffect, type: string | string[], deps: DependencyList = [], ): void { - const currentValue = useRef(null); const store = useStore(); + const effectRef = useRef(effect); + const typeRef = useRef(type); + // Persists across StrictMode's mount → unmount → remount cycle so we + // don't re-fire for an action we already handled on the first mount. + const lastHandledCountRef = useRef(-1); - const handleChange = (): void => { - const state: any = store.getState(); - const action = state.action; - const previousValue = currentValue.current; - currentValue.current = action.count; - - if ( - previousValue !== action.count && - castArray(type).includes(action.type) - ) { - effect(action); - } - } + effectRef.current = effect; + typeRef.current = type; useEffect(() => { - const unsubscribe = store.subscribe(handleChange); + const check = (): void => { + const action = (store.getState() as any).action; + if (!action || action.count === lastHandledCountRef.current) { + return; + } + lastHandledCountRef.current = action.count; + if (castArray(typeRef.current).includes(action.type)) { + effectRef.current(action); + } + }; + + check(); + + const unsubscribe = store.subscribe(check); return (): void => unsubscribe(); - }, deps) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, deps); } diff --git a/webclient/src/i18n-backend.ts b/webclient/src/i18n-backend.ts index 9c481f5eb..86642c5e1 100644 --- a/webclient/src/i18n-backend.ts +++ b/webclient/src/i18n-backend.ts @@ -7,12 +7,12 @@ class I18nBackend { static BASE_URL = `${import.meta.env.BASE_URL}locales`; read(language, namespace, callback) { - if (!language[App.Language]) { + if (!language[App.Language as unknown as string]) { callback(true, null); return; } - fetch(`${I18nBackend.BASE_URL}/${language[App.Language]}/${namespace}.json`) + fetch(`${I18nBackend.BASE_URL}/${language[App.Language as unknown as string]}/${namespace}.json`) .then(resp => resp.json().then(json => callback(null, json))) .catch(error => callback(error, null)); } diff --git a/webclient/src/setupTests.ts b/webclient/src/setupTests.ts index d58601667..5c422f120 100644 --- a/webclient/src/setupTests.ts +++ b/webclient/src/setupTests.ts @@ -33,8 +33,30 @@ import '@testing-library/jest-dom/vitest'; // `mockImplementation`, it should set it in that test's body and rely on // the next test overwriting or the global `clearAllMocks` clearing calls — // it should NOT assume the mock is reset to its factory default automatically. +// +// Global snapshot/restore guards for non-`vi.spyOn` globals that tests mutate +// directly. `vi.restoreAllMocks()` only restores `vi.spyOn` targets, so bare +// `Object.defineProperty` writes on `window.location` and `globalThis.WebSocket` +// reassignments leak between tests unless we explicitly capture and restore them. +let _locationDescriptor: PropertyDescriptor | undefined; +let _originalWebSocket: typeof globalThis.WebSocket | undefined; + +beforeEach(() => { + _locationDescriptor = Object.getOwnPropertyDescriptor(window, 'location'); + _originalWebSocket = globalThis.WebSocket; +}); + afterEach(() => { vi.clearAllMocks(); vi.restoreAllMocks(); vi.useRealTimers(); + + const currentLocationDescriptor = Object.getOwnPropertyDescriptor(window, 'location'); + if (currentLocationDescriptor !== _locationDescriptor && _locationDescriptor) { + Object.defineProperty(window, 'location', _locationDescriptor); + } + + if (globalThis.WebSocket !== _originalWebSocket) { + globalThis.WebSocket = _originalWebSocket as typeof globalThis.WebSocket; + } }); diff --git a/webclient/src/store/game/game.dispatch.spec.ts b/webclient/src/store/game/game.dispatch.spec.ts index a06d940e4..0f8102955 100644 --- a/webclient/src/store/game/game.dispatch.spec.ts +++ b/webclient/src/store/game/game.dispatch.spec.ts @@ -1,8 +1,11 @@ -vi.mock('../store', () => ({ store: { dispatch: vi.fn() } })); +// Use `vi.hoisted` so the mocked `store.dispatch` reference stays stable across +// re-runs of the factory under `isolate: false`. See rooms.dispatch.spec.ts for +// the same pattern and rationale. +const { mockDispatch } = vi.hoisted(() => ({ mockDispatch: vi.fn() })); +vi.mock('../store', () => ({ store: { dispatch: mockDispatch } })); import { create } from '@bufbuild/protobuf'; import { Data } from '@app/types'; -import { store } from '..'; import { Actions } from './game.actions'; import { Dispatch } from './game.dispatch'; import { @@ -12,31 +15,35 @@ import { makePlayerProperties, } from './__mocks__/fixtures'; +beforeEach(() => { + mockDispatch.mockClear(); +}); + describe('Dispatch', () => { it('clearStore dispatches Actions.clearStore()', () => { Dispatch.clearStore(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.clearStore()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.clearStore()); }); it('gameJoined dispatches Actions.gameJoined()', () => { const data = create(Data.Event_GameJoinedSchema, { hostId: 1, playerId: 2 }); Dispatch.gameJoined(data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.gameJoined(data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.gameJoined(data)); }); it('gameLeft dispatches Actions.gameLeft()', () => { Dispatch.gameLeft(2); - expect(store.dispatch).toHaveBeenCalledWith(Actions.gameLeft(2)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.gameLeft(2)); }); it('gameClosed dispatches Actions.gameClosed()', () => { Dispatch.gameClosed(3); - expect(store.dispatch).toHaveBeenCalledWith(Actions.gameClosed(3)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.gameClosed(3)); }); it('gameHostChanged dispatches Actions.gameHostChanged()', () => { Dispatch.gameHostChanged(1, 7); - expect(store.dispatch).toHaveBeenCalledWith(Actions.gameHostChanged(1, 7)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.gameHostChanged(1, 7)); }); it('gameStateChanged dispatches Actions.gameStateChanged()', () => { @@ -44,156 +51,156 @@ describe('Dispatch', () => { playerList: [], gameStarted: false, activePlayerId: 0, activePhase: 0, secondsElapsed: 0 }); Dispatch.gameStateChanged(1, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.gameStateChanged(1, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.gameStateChanged(1, data)); }); it('playerJoined dispatches Actions.playerJoined()', () => { const props = makePlayerProperties(); Dispatch.playerJoined(1, props); - expect(store.dispatch).toHaveBeenCalledWith(Actions.playerJoined(1, props)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.playerJoined(1, props)); }); it('playerLeft dispatches Actions.playerLeft()', () => { Dispatch.playerLeft(1, 2, 3); - expect(store.dispatch).toHaveBeenCalledWith(Actions.playerLeft(1, 2, 3)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.playerLeft(1, 2, 3)); }); it('playerPropertiesChanged dispatches Actions.playerPropertiesChanged()', () => { const props = makePlayerProperties(); Dispatch.playerPropertiesChanged(1, 2, props); - expect(store.dispatch).toHaveBeenCalledWith(Actions.playerPropertiesChanged(1, 2, props)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.playerPropertiesChanged(1, 2, props)); }); it('kicked dispatches Actions.kicked()', () => { Dispatch.kicked(1); - expect(store.dispatch).toHaveBeenCalledWith(Actions.kicked(1)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.kicked(1)); }); it('cardMoved dispatches Actions.cardMoved()', () => { const data = create(Data.Event_MoveCardSchema, { cardId: 1 }); Dispatch.cardMoved(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.cardMoved(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.cardMoved(1, 2, data)); }); it('cardFlipped dispatches Actions.cardFlipped()', () => { const data = create(Data.Event_FlipCardSchema, { cardId: 1 }); Dispatch.cardFlipped(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.cardFlipped(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.cardFlipped(1, 2, data)); }); it('cardDestroyed dispatches Actions.cardDestroyed()', () => { const data = create(Data.Event_DestroyCardSchema, { cardId: 1 }); Dispatch.cardDestroyed(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.cardDestroyed(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.cardDestroyed(1, 2, data)); }); it('cardAttached dispatches Actions.cardAttached()', () => { const data = create(Data.Event_AttachCardSchema, { cardId: 1 }); Dispatch.cardAttached(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.cardAttached(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.cardAttached(1, 2, data)); }); it('tokenCreated dispatches Actions.tokenCreated()', () => { const data = create(Data.Event_CreateTokenSchema, { cardId: 1 }); Dispatch.tokenCreated(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.tokenCreated(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.tokenCreated(1, 2, data)); }); it('cardAttrChanged dispatches Actions.cardAttrChanged()', () => { const data = create(Data.Event_SetCardAttrSchema, { cardId: 1 }); Dispatch.cardAttrChanged(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.cardAttrChanged(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.cardAttrChanged(1, 2, data)); }); it('cardCounterChanged dispatches Actions.cardCounterChanged()', () => { const data = create(Data.Event_SetCardCounterSchema, { cardId: 1 }); Dispatch.cardCounterChanged(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.cardCounterChanged(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.cardCounterChanged(1, 2, data)); }); it('arrowCreated dispatches Actions.arrowCreated()', () => { const data = create(Data.Event_CreateArrowSchema, { arrowInfo: makeArrow() }); Dispatch.arrowCreated(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.arrowCreated(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.arrowCreated(1, 2, data)); }); it('arrowDeleted dispatches Actions.arrowDeleted()', () => { const data = create(Data.Event_DeleteArrowSchema, { arrowId: 3 }); Dispatch.arrowDeleted(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.arrowDeleted(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.arrowDeleted(1, 2, data)); }); it('counterCreated dispatches Actions.counterCreated()', () => { const data = create(Data.Event_CreateCounterSchema, { counterInfo: makeCounter() }); Dispatch.counterCreated(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.counterCreated(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.counterCreated(1, 2, data)); }); it('counterSet dispatches Actions.counterSet()', () => { const data = create(Data.Event_SetCounterSchema, { counterId: 1, value: 10 }); Dispatch.counterSet(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.counterSet(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.counterSet(1, 2, data)); }); it('counterDeleted dispatches Actions.counterDeleted()', () => { const data = create(Data.Event_DelCounterSchema, { counterId: 1 }); Dispatch.counterDeleted(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.counterDeleted(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.counterDeleted(1, 2, data)); }); it('cardsDrawn dispatches Actions.cardsDrawn()', () => { const data = create(Data.Event_DrawCardsSchema, { number: 2, cards: [makeCard()] }); Dispatch.cardsDrawn(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.cardsDrawn(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.cardsDrawn(1, 2, data)); }); it('cardsRevealed dispatches Actions.cardsRevealed()', () => { const data = create(Data.Event_RevealCardsSchema, { zoneName: 'hand', cards: [] }); Dispatch.cardsRevealed(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.cardsRevealed(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.cardsRevealed(1, 2, data)); }); it('zoneShuffled dispatches Actions.zoneShuffled()', () => { const data = create(Data.Event_ShuffleSchema, { zoneName: 'deck', start: 0, end: 39 }); Dispatch.zoneShuffled(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.zoneShuffled(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.zoneShuffled(1, 2, data)); }); it('dieRolled dispatches Actions.dieRolled()', () => { const data = create(Data.Event_RollDieSchema, { sides: 6, value: 4, values: [4] }); Dispatch.dieRolled(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.dieRolled(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.dieRolled(1, 2, data)); }); it('activePlayerSet dispatches Actions.activePlayerSet()', () => { Dispatch.activePlayerSet(1, 3); - expect(store.dispatch).toHaveBeenCalledWith(Actions.activePlayerSet(1, 3)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.activePlayerSet(1, 3)); }); it('activePhaseSet dispatches Actions.activePhaseSet()', () => { Dispatch.activePhaseSet(1, 2); - expect(store.dispatch).toHaveBeenCalledWith(Actions.activePhaseSet(1, 2)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.activePhaseSet(1, 2)); }); it('turnReversed dispatches Actions.turnReversed()', () => { Dispatch.turnReversed(1, true); - expect(store.dispatch).toHaveBeenCalledWith(Actions.turnReversed(1, true)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.turnReversed(1, true)); }); it('zoneDumped dispatches Actions.zoneDumped()', () => { const data = create(Data.Event_DumpZoneSchema, { zoneOwnerId: 1, zoneName: 'hand', numberCards: 3, isReversed: false }); Dispatch.zoneDumped(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.zoneDumped(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.zoneDumped(1, 2, data)); }); it('zonePropertiesChanged dispatches Actions.zonePropertiesChanged()', () => { const data = create(Data.Event_ChangeZonePropertiesSchema, { zoneName: 'deck', alwaysRevealTopCard: true, alwaysLookAtTopCard: false }); Dispatch.zonePropertiesChanged(1, 2, data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.zonePropertiesChanged(1, 2, data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.zonePropertiesChanged(1, 2, data)); }); it('gameSay dispatches Actions.gameSay()', () => { Dispatch.gameSay(1, 2, 'gg wp'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.gameSay(1, 2, 'gg wp')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.gameSay(1, 2, 'gg wp')); }); }); diff --git a/webclient/src/store/game/game.reducer.spec.ts b/webclient/src/store/game/game.reducer.spec.ts index 048dbbd93..d92a1132c 100644 --- a/webclient/src/store/game/game.reducer.spec.ts +++ b/webclient/src/store/game/game.reducer.spec.ts @@ -898,17 +898,20 @@ describe('2J: Turn, phase, and chat', () => { it('GAME_SAY → appends message with mocked Date.now() as timeReceived', () => { const state = makeState(); - vi.spyOn(Date, 'now').mockReturnValue(123456789); - const result = gamesReducer(state, { - type: Types.GAME_SAY, - gameId: 1, - playerId: 2, - message: 'gg', - }); - vi.restoreAllMocks(); + const dateNowSpy = vi.spyOn(Date, 'now').mockReturnValue(123456789); + try { + const result = gamesReducer(state, { + type: Types.GAME_SAY, + gameId: 1, + playerId: 2, + message: 'gg', + }); - expect(result.games[1].messages).toHaveLength(1); - expect(result.games[1].messages[0]).toEqual({ playerId: 2, message: 'gg', timeReceived: 123456789 }); + expect(result.games[1].messages).toHaveLength(1); + expect(result.games[1].messages[0]).toEqual({ playerId: 2, message: 'gg', timeReceived: 123456789 }); + } finally { + dateNowSpy.mockRestore(); + } }); }); diff --git a/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts b/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts index 940ec76ea..2a4454f2a 100644 --- a/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts +++ b/webclient/src/store/rooms/__mocks__/rooms-fixtures.ts @@ -55,12 +55,15 @@ export function makeGame( }; } -export function makeMessage(overrides: Partial = {}): Enriched.Message { +export function makeMessage(overrides: Partial> = {}): Enriched.Message { + const { timeReceived = 0, ...protoOverrides } = overrides; return { - message: 'hello', - messageType: 0, - timeReceived: 0, - ...overrides, + ...create(Data.Event_RoomSaySchema, { + message: 'hello', + messageType: 0, + ...protoOverrides, + }), + timeReceived, }; } diff --git a/webclient/src/store/rooms/rooms.dispatch.spec.ts b/webclient/src/store/rooms/rooms.dispatch.spec.ts index 9b305c894..aa12c0034 100644 --- a/webclient/src/store/rooms/rooms.dispatch.spec.ts +++ b/webclient/src/store/rooms/rooms.dispatch.spec.ts @@ -1,84 +1,95 @@ -vi.mock('..', () => ({ store: { dispatch: vi.fn() } })); +// Use `vi.hoisted` so the mocked `store.dispatch` reference stays stable across +// re-runs of the factory under `isolate: false`. Other dispatch specs mock the +// same `..` path with their own factories; under the shared module graph, the +// cache entry for `..` can flip between competing `vi.fn()` instances. Asserting +// against the hoisted `mockDispatch` directly (rather than reaching through +// `store.dispatch`) decouples the assertions from whatever the module cache +// currently resolves `store` to. +const { mockDispatch } = vi.hoisted(() => ({ mockDispatch: vi.fn() })); +vi.mock('..', () => ({ store: { dispatch: mockDispatch } })); -import { store } from '..'; import { Actions } from './rooms.actions'; import { Dispatch } from './rooms.dispatch'; import { makeGame, makeMessage, makeRoom, makeUser } from './__mocks__/rooms-fixtures'; import { App } from '@app/types'; +beforeEach(() => { + mockDispatch.mockClear(); +}); + describe('Dispatch', () => { it('clearStore dispatches Actions.clearStore()', () => { Dispatch.clearStore(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.clearStore()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.clearStore()); }); it('updateRooms dispatches Actions.updateRooms()', () => { const rooms = [makeRoom()]; Dispatch.updateRooms(rooms); - expect(store.dispatch).toHaveBeenCalledWith(Actions.updateRooms(rooms)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.updateRooms(rooms)); }); it('joinRoom dispatches Actions.joinRoom()', () => { const roomInfo = makeRoom({ roomId: 2 }); Dispatch.joinRoom(roomInfo); - expect(store.dispatch).toHaveBeenCalledWith(Actions.joinRoom(roomInfo)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.joinRoom(roomInfo)); }); it('leaveRoom dispatches Actions.leaveRoom()', () => { Dispatch.leaveRoom(3); - expect(store.dispatch).toHaveBeenCalledWith(Actions.leaveRoom(3)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.leaveRoom(3)); }); it('addMessage with message.name falsy → dispatches only Actions.addMessage()', () => { const message = { ...makeMessage(), name: undefined }; Dispatch.addMessage(1, message); - expect(store.dispatch).toHaveBeenCalledTimes(1); - expect(store.dispatch).toHaveBeenCalledWith(Actions.addMessage(1, message)); + expect(mockDispatch).toHaveBeenCalledTimes(1); + expect(mockDispatch).toHaveBeenCalledWith(Actions.addMessage(1, message)); }); it('addMessage with message.name truthy → dispatches Actions.addMessage()', () => { const message = { ...makeMessage(), name: 'Alice' }; Dispatch.addMessage(1, message); - expect(store.dispatch).toHaveBeenCalledTimes(1); - expect(store.dispatch).toHaveBeenCalledWith(Actions.addMessage(1, message)); + expect(mockDispatch).toHaveBeenCalledTimes(1); + expect(mockDispatch).toHaveBeenCalledWith(Actions.addMessage(1, message)); }); it('updateGames dispatches Actions.updateGames()', () => { const games = [makeGame()]; Dispatch.updateGames(1, games); - expect(store.dispatch).toHaveBeenCalledWith(Actions.updateGames(1, games)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.updateGames(1, games)); }); it('userJoined dispatches Actions.userJoined()', () => { const user = makeUser(); Dispatch.userJoined(1, user); - expect(store.dispatch).toHaveBeenCalledWith(Actions.userJoined(1, user)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.userJoined(1, user)); }); it('userLeft dispatches Actions.userLeft()', () => { Dispatch.userLeft(1, 'Alice'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.userLeft(1, 'Alice')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.userLeft(1, 'Alice')); }); it('sortGames dispatches Actions.sortGames()', () => { Dispatch.sortGames(1, App.GameSortField.START_TIME, App.SortDirection.ASC); - expect(store.dispatch).toHaveBeenCalledWith( + expect(mockDispatch).toHaveBeenCalledWith( Actions.sortGames(1, App.GameSortField.START_TIME, App.SortDirection.ASC) ); }); it('removeMessages dispatches Actions.removeMessages()', () => { Dispatch.removeMessages(1, 'Alice', 5); - expect(store.dispatch).toHaveBeenCalledWith(Actions.removeMessages(1, 'Alice', 5)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.removeMessages(1, 'Alice', 5)); }); it('gameCreated dispatches Actions.gameCreated()', () => { Dispatch.gameCreated(2); - expect(store.dispatch).toHaveBeenCalledWith(Actions.gameCreated(2)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.gameCreated(2)); }); it('joinedGame dispatches Actions.joinedGame()', () => { Dispatch.joinedGame(1, 5); - expect(store.dispatch).toHaveBeenCalledWith(Actions.joinedGame(1, 5)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.joinedGame(1, 5)); }); }); diff --git a/webclient/src/store/server/server.dispatch.spec.ts b/webclient/src/store/server/server.dispatch.spec.ts index d58d3fcb1..5523e3e2a 100644 --- a/webclient/src/store/server/server.dispatch.spec.ts +++ b/webclient/src/store/server/server.dispatch.spec.ts @@ -1,6 +1,9 @@ -vi.mock('..', () => ({ store: { dispatch: vi.fn() } })); +// Use `vi.hoisted` so the mocked `store.dispatch` reference stays stable across +// re-runs of the factory under `isolate: false`. See rooms.dispatch.spec.ts for +// the same pattern and rationale. +const { mockDispatch } = vi.hoisted(() => ({ mockDispatch: vi.fn() })); +vi.mock('..', () => ({ store: { dispatch: mockDispatch } })); -import { store } from '..'; import { Actions } from './server.actions'; import { Dispatch } from './server.dispatch'; import { App, Data } from '@app/types'; @@ -17,378 +20,382 @@ import { makeWarnListItem, } from './__mocks__/server-fixtures'; +beforeEach(() => { + mockDispatch.mockClear(); +}); + describe('Dispatch', () => { it('initialized dispatches Actions.initialized()', () => { Dispatch.initialized(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.initialized()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.initialized()); }); it('clearStore dispatches Actions.clearStore()', () => { Dispatch.clearStore(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.clearStore()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.clearStore()); }); it('connectionAttempted dispatches Actions.connectionAttempted()', () => { Dispatch.connectionAttempted(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.connectionAttempted()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.connectionAttempted()); }); it('loginSuccessful dispatches Actions.loginSuccessful()', () => { const options = makeLoginSuccessContext(); Dispatch.loginSuccessful(options); - expect(store.dispatch).toHaveBeenCalledWith(Actions.loginSuccessful(options)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.loginSuccessful(options)); }); it('loginFailed dispatches Actions.loginFailed()', () => { Dispatch.loginFailed(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.loginFailed()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.loginFailed()); }); it('connectionFailed dispatches Actions.connectionFailed()', () => { Dispatch.connectionFailed(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.connectionFailed()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.connectionFailed()); }); it('testConnectionSuccessful dispatches Actions.testConnectionSuccessful()', () => { Dispatch.testConnectionSuccessful(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.testConnectionSuccessful()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.testConnectionSuccessful()); }); it('testConnectionFailed dispatches Actions.testConnectionFailed()', () => { Dispatch.testConnectionFailed(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.testConnectionFailed()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.testConnectionFailed()); }); it('updateBuddyList dispatches Actions.updateBuddyList()', () => { const list = [makeUser()]; Dispatch.updateBuddyList(list); - expect(store.dispatch).toHaveBeenCalledWith(Actions.updateBuddyList(list)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.updateBuddyList(list)); }); it('addToBuddyList dispatches Actions.addToBuddyList()', () => { const user = makeUser(); Dispatch.addToBuddyList(user); - expect(store.dispatch).toHaveBeenCalledWith(Actions.addToBuddyList(user)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.addToBuddyList(user)); }); it('removeFromBuddyList dispatches Actions.removeFromBuddyList()', () => { Dispatch.removeFromBuddyList('Alice'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.removeFromBuddyList('Alice')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.removeFromBuddyList('Alice')); }); it('updateIgnoreList dispatches Actions.updateIgnoreList()', () => { const list = [makeUser()]; Dispatch.updateIgnoreList(list); - expect(store.dispatch).toHaveBeenCalledWith(Actions.updateIgnoreList(list)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.updateIgnoreList(list)); }); it('addToIgnoreList dispatches Actions.addToIgnoreList()', () => { const user = makeUser(); Dispatch.addToIgnoreList(user); - expect(store.dispatch).toHaveBeenCalledWith(Actions.addToIgnoreList(user)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.addToIgnoreList(user)); }); it('removeFromIgnoreList dispatches Actions.removeFromIgnoreList()', () => { Dispatch.removeFromIgnoreList('Bob'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.removeFromIgnoreList('Bob')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.removeFromIgnoreList('Bob')); }); it('updateInfo dispatches Actions.updateInfo({ name, version })', () => { Dispatch.updateInfo('Servatrice', '2.9'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.updateInfo({ name: 'Servatrice', version: '2.9' })); + expect(mockDispatch).toHaveBeenCalledWith(Actions.updateInfo({ name: 'Servatrice', version: '2.9' })); }); it('updateStatus dispatches Actions.updateStatus({ state, description })', () => { Dispatch.updateStatus(App.StatusEnum.CONNECTED, 'ok'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.updateStatus({ state: App.StatusEnum.CONNECTED, description: 'ok' })); + expect(mockDispatch).toHaveBeenCalledWith(Actions.updateStatus({ state: App.StatusEnum.CONNECTED, description: 'ok' })); }); it('updateUser dispatches Actions.updateUser()', () => { const user = makeUser(); Dispatch.updateUser(user); - expect(store.dispatch).toHaveBeenCalledWith(Actions.updateUser(user)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.updateUser(user)); }); it('updateUsers dispatches Actions.updateUsers()', () => { const users = [makeUser()]; Dispatch.updateUsers(users); - expect(store.dispatch).toHaveBeenCalledWith(Actions.updateUsers(users)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.updateUsers(users)); }); it('userJoined dispatches Actions.userJoined()', () => { const user = makeUser(); Dispatch.userJoined(user); - expect(store.dispatch).toHaveBeenCalledWith(Actions.userJoined(user)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.userJoined(user)); }); it('userLeft dispatches Actions.userLeft()', () => { Dispatch.userLeft('Carol'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.userLeft('Carol')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.userLeft('Carol')); }); it('viewLogs dispatches Actions.viewLogs()', () => { const logs = [create(Data.ServerInfo_ChatMessageSchema, { targetType: 'room' })]; Dispatch.viewLogs(logs); - expect(store.dispatch).toHaveBeenCalledWith(Actions.viewLogs(logs)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.viewLogs(logs)); }); it('clearLogs dispatches Actions.clearLogs()', () => { Dispatch.clearLogs(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.clearLogs()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.clearLogs()); }); it('serverMessage dispatches Actions.serverMessage()', () => { Dispatch.serverMessage('Welcome!'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.serverMessage('Welcome!')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.serverMessage('Welcome!')); }); it('registrationRequiresEmail dispatches correctly', () => { Dispatch.registrationRequiresEmail(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.registrationRequiresEmail()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.registrationRequiresEmail()); }); it('registrationSuccess dispatches correctly', () => { Dispatch.registrationSuccess(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.registrationSuccess()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.registrationSuccess()); }); it('registrationFailed passes reason and endTime to action', () => { Dispatch.registrationFailed('reason', 999); - expect(store.dispatch).toHaveBeenCalledWith(Actions.registrationFailed('reason', 999)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.registrationFailed('reason', 999)); }); it('registrationFailed passes reason only when no endTime', () => { Dispatch.registrationFailed('plain reason'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.registrationFailed('plain reason', undefined)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.registrationFailed('plain reason', undefined)); }); it('registrationEmailError dispatches correctly', () => { Dispatch.registrationEmailError('bad'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.registrationEmailError('bad')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.registrationEmailError('bad')); }); it('registrationPasswordError dispatches correctly', () => { Dispatch.registrationPasswordError('weak'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.registrationPasswordError('weak')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.registrationPasswordError('weak')); }); it('registrationUserNameError dispatches correctly', () => { Dispatch.registrationUserNameError('taken'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.registrationUserNameError('taken')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.registrationUserNameError('taken')); }); it('accountAwaitingActivation dispatches correctly', () => { const options = makePendingActivationContext(); Dispatch.accountAwaitingActivation(options); - expect(store.dispatch).toHaveBeenCalledWith(Actions.accountAwaitingActivation(options)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.accountAwaitingActivation(options)); }); it('accountActivationSuccess dispatches correctly', () => { Dispatch.accountActivationSuccess(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.accountActivationSuccess()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.accountActivationSuccess()); }); it('accountActivationFailed dispatches correctly', () => { Dispatch.accountActivationFailed(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.accountActivationFailed()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.accountActivationFailed()); }); it('resetPassword dispatches correctly', () => { Dispatch.resetPassword(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.resetPassword()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.resetPassword()); }); it('resetPasswordFailed dispatches correctly', () => { Dispatch.resetPasswordFailed(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.resetPasswordFailed()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.resetPasswordFailed()); }); it('resetPasswordChallenge dispatches correctly', () => { Dispatch.resetPasswordChallenge(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.resetPasswordChallenge()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.resetPasswordChallenge()); }); it('resetPasswordSuccess dispatches correctly', () => { Dispatch.resetPasswordSuccess(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.resetPasswordSuccess()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.resetPasswordSuccess()); }); it('adjustMod dispatches Actions.adjustMod()', () => { Dispatch.adjustMod('Dan', true, false); - expect(store.dispatch).toHaveBeenCalledWith(Actions.adjustMod('Dan', true, false)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.adjustMod('Dan', true, false)); }); it('reloadConfig dispatches correctly', () => { Dispatch.reloadConfig(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.reloadConfig()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.reloadConfig()); }); it('shutdownServer dispatches correctly', () => { Dispatch.shutdownServer(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.shutdownServer()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.shutdownServer()); }); it('updateServerMessage dispatches correctly', () => { Dispatch.updateServerMessage(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.updateServerMessage()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.updateServerMessage()); }); it('accountPasswordChange dispatches correctly', () => { Dispatch.accountPasswordChange(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.accountPasswordChange()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.accountPasswordChange()); }); it('accountEditChanged dispatches correctly', () => { const user = makeUser(); Dispatch.accountEditChanged(user); - expect(store.dispatch).toHaveBeenCalledWith(Actions.accountEditChanged(user)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.accountEditChanged(user)); }); it('accountImageChanged dispatches correctly', () => { const user = makeUser(); Dispatch.accountImageChanged(user); - expect(store.dispatch).toHaveBeenCalledWith(Actions.accountImageChanged(user)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.accountImageChanged(user)); }); it('getUserInfo dispatches correctly', () => { const userInfo = makeUser({ name: 'Frank' }); Dispatch.getUserInfo(userInfo); - expect(store.dispatch).toHaveBeenCalledWith(Actions.getUserInfo(userInfo)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.getUserInfo(userInfo)); }); it('notifyUser dispatches correctly', () => { const notification = create(Data.Event_NotifyUserSchema, { type: 1, warningReason: '', customTitle: '', customContent: '' }); Dispatch.notifyUser(notification); - expect(store.dispatch).toHaveBeenCalledWith(Actions.notifyUser(notification)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.notifyUser(notification)); }); it('serverShutdown dispatches correctly', () => { const data = create(Data.Event_ServerShutdownSchema, { reason: 'maintenance', minutes: 5 }); Dispatch.serverShutdown(data); - expect(store.dispatch).toHaveBeenCalledWith(Actions.serverShutdown(data)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.serverShutdown(data)); }); it('userMessage dispatches correctly', () => { const messageData = create(Data.Event_UserMessageSchema, { senderName: 'Alice', receiverName: 'Bob', message: 'hey' }); Dispatch.userMessage(messageData); - expect(store.dispatch).toHaveBeenCalledWith(Actions.userMessage(messageData)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.userMessage(messageData)); }); it('addToList dispatches correctly', () => { Dispatch.addToList('buddyList', 'Grace'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.addToList('buddyList', 'Grace')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.addToList('buddyList', 'Grace')); }); it('removeFromList dispatches correctly', () => { Dispatch.removeFromList('buddyList', 'Hank'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.removeFromList('buddyList', 'Hank')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.removeFromList('buddyList', 'Hank')); }); it('banFromServer dispatches correctly', () => { Dispatch.banFromServer('Ira'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.banFromServer('Ira')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.banFromServer('Ira')); }); it('banHistory dispatches correctly', () => { const history = [makeBanHistoryItem()]; Dispatch.banHistory('Ira', history); - expect(store.dispatch).toHaveBeenCalledWith(Actions.banHistory('Ira', history)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.banHistory('Ira', history)); }); it('warnHistory dispatches correctly', () => { const history = [makeWarnHistoryItem()]; Dispatch.warnHistory('Jack', history); - expect(store.dispatch).toHaveBeenCalledWith(Actions.warnHistory('Jack', history)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.warnHistory('Jack', history)); }); it('warnListOptions dispatches correctly', () => { const list = [makeWarnListItem()]; Dispatch.warnListOptions(list); - expect(store.dispatch).toHaveBeenCalledWith(Actions.warnListOptions(list)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.warnListOptions(list)); }); it('warnUser dispatches correctly', () => { Dispatch.warnUser('Kelly'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.warnUser('Kelly')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.warnUser('Kelly')); }); it('grantReplayAccess dispatches correctly', () => { Dispatch.grantReplayAccess(7, 'Moe'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.grantReplayAccess(7, 'Moe')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.grantReplayAccess(7, 'Moe')); }); it('forceActivateUser dispatches correctly', () => { Dispatch.forceActivateUser('Ned', 'Moe'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.forceActivateUser('Ned', 'Moe')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.forceActivateUser('Ned', 'Moe')); }); it('getAdminNotes dispatches correctly', () => { Dispatch.getAdminNotes('Ned', 'notes'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.getAdminNotes('Ned', 'notes')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.getAdminNotes('Ned', 'notes')); }); it('updateAdminNotes dispatches correctly', () => { Dispatch.updateAdminNotes('Ned', 'updated'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.updateAdminNotes('Ned', 'updated')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.updateAdminNotes('Ned', 'updated')); }); it('replayList dispatches correctly', () => { const list = [makeReplayMatch()]; Dispatch.replayList(list); - expect(store.dispatch).toHaveBeenCalledWith(Actions.replayList(list)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.replayList(list)); }); it('replayAdded dispatches correctly', () => { const match = makeReplayMatch(); Dispatch.replayAdded(match); - expect(store.dispatch).toHaveBeenCalledWith(Actions.replayAdded(match)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.replayAdded(match)); }); it('replayModifyMatch dispatches correctly', () => { Dispatch.replayModifyMatch(5, true); - expect(store.dispatch).toHaveBeenCalledWith(Actions.replayModifyMatch(5, true)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.replayModifyMatch(5, true)); }); it('replayDeleteMatch dispatches correctly', () => { Dispatch.replayDeleteMatch(5); - expect(store.dispatch).toHaveBeenCalledWith(Actions.replayDeleteMatch(5)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.replayDeleteMatch(5)); }); it('backendDecks dispatches correctly', () => { const deckList = makeDeckList(); Dispatch.backendDecks(deckList); - expect(store.dispatch).toHaveBeenCalledWith(Actions.backendDecks(deckList)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.backendDecks(deckList)); }); it('deckNewDir dispatches correctly', () => { Dispatch.deckNewDir('a/b', 'newFolder'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.deckNewDir('a/b', 'newFolder')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.deckNewDir('a/b', 'newFolder')); }); it('deckDelDir dispatches correctly', () => { Dispatch.deckDelDir('a/b'); - expect(store.dispatch).toHaveBeenCalledWith(Actions.deckDelDir('a/b')); + expect(mockDispatch).toHaveBeenCalledWith(Actions.deckDelDir('a/b')); }); it('deckUpload dispatches correctly', () => { const treeItem = makeDeckTreeItem(); Dispatch.deckUpload('a/b', treeItem); - expect(store.dispatch).toHaveBeenCalledWith(Actions.deckUpload('a/b', treeItem)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.deckUpload('a/b', treeItem)); }); it('deckDelete dispatches correctly', () => { Dispatch.deckDelete(42); - expect(store.dispatch).toHaveBeenCalledWith(Actions.deckDelete(42)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.deckDelete(42)); }); it('gamesOfUser dispatches correctly', () => { const response = create(Data.Response_GetGamesOfUserSchema, { roomList: [], gameList: [] }); Dispatch.gamesOfUser('alice', response); - expect(store.dispatch).toHaveBeenCalledWith(Actions.gamesOfUser('alice', response)); + expect(mockDispatch).toHaveBeenCalledWith(Actions.gamesOfUser('alice', response)); }); it('clearRegistrationErrors dispatches correctly', () => { Dispatch.clearRegistrationErrors(); - expect(store.dispatch).toHaveBeenCalledWith(Actions.clearRegistrationErrors()); + expect(mockDispatch).toHaveBeenCalledWith(Actions.clearRegistrationErrors()); }); }); diff --git a/webclient/src/websocket/commands/session/login.ts b/webclient/src/websocket/commands/session/login.ts index 57332a5a6..4291ddb17 100644 --- a/webclient/src/websocket/commands/session/login.ts +++ b/webclient/src/websocket/commands/session/login.ts @@ -17,14 +17,14 @@ import { export function login(options: Omit, password?: string, passwordSalt?: string): void { const { userName, hashedPassword } = options; - const loginConfig: MessageInitShape = { + const loginConfig = { ...CLIENT_CONFIG, clientid: 'webatrice', userName, ...(passwordSalt ? { hashedPassword: hashedPassword || hashPassword(passwordSalt, password) } : { password }), - }; + } satisfies MessageInitShape; const onLoginError = (message: string, extra?: () => void) => { updateStatus(App.StatusEnum.DISCONNECTED, message); diff --git a/webclient/src/websocket/events/game/joinGame.ts b/webclient/src/websocket/events/game/joinGame.ts index 376801f73..bd59dbb6e 100644 --- a/webclient/src/websocket/events/game/joinGame.ts +++ b/webclient/src/websocket/events/game/joinGame.ts @@ -1,6 +1,6 @@ import { GamePersistence } from '../../persistence'; import type { Data, Enriched } from '@app/types'; -export function joinGame(data: { playerProperties: Data.ServerInfo_PlayerProperties }, meta: Enriched.GameEventMeta): void { +export function joinGame(data: Data.Event_Join, meta: Enriched.GameEventMeta): void { GamePersistence.playerJoined(meta.gameId, data.playerProperties); } diff --git a/webclient/src/websocket/events/game/playerPropertiesChanged.ts b/webclient/src/websocket/events/game/playerPropertiesChanged.ts index dcc0acef6..9f2fab867 100644 --- a/webclient/src/websocket/events/game/playerPropertiesChanged.ts +++ b/webclient/src/websocket/events/game/playerPropertiesChanged.ts @@ -1,6 +1,6 @@ import type { Data, Enriched } from '@app/types'; import { GamePersistence } from '../../persistence'; -export function playerPropertiesChanged(data: { playerProperties: Data.ServerInfo_PlayerProperties }, meta: Enriched.GameEventMeta): void { +export function playerPropertiesChanged(data: Data.Event_PlayerPropertiesChanged, meta: Enriched.GameEventMeta): void { GamePersistence.playerPropertiesChanged(meta.gameId, meta.playerId, data.playerProperties); } diff --git a/webclient/src/websocket/events/session/sessionEvents.spec.ts b/webclient/src/websocket/events/session/sessionEvents.spec.ts index 37948043d..b8993313c 100644 --- a/webclient/src/websocket/events/session/sessionEvents.spec.ts +++ b/webclient/src/websocket/events/session/sessionEvents.spec.ts @@ -204,6 +204,9 @@ describe('addToList', () => { beforeEach(() => { logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); }); + afterEach(() => { + logSpy.mockRestore(); + }); it('buddy list → addToBuddyList', () => { const data = create(Data.Event_AddToListSchema, { diff --git a/webclient/src/websocket/services/ProtobufService.spec.ts b/webclient/src/websocket/services/ProtobufService.spec.ts index 0c578de77..7430bd435 100644 --- a/webclient/src/websocket/services/ProtobufService.spec.ts +++ b/webclient/src/websocket/services/ProtobufService.spec.ts @@ -1,5 +1,5 @@ -vi.mock('@bufbuild/protobuf', () => ({ - create: vi.fn((_schema: unknown, fields?: Record) => ({ ...(fields ?? {}) })), +vi.mock('@bufbuild/protobuf', async (importOriginal) => ({ + ...(await importOriginal()), fromBinary: vi.fn(), toBinary: vi.fn().mockReturnValue(new Uint8Array()), hasExtension: vi.fn().mockReturnValue(false), @@ -7,20 +7,6 @@ vi.mock('@bufbuild/protobuf', () => ({ setExtension: vi.fn(), })); -vi.mock('../../generated/proto/commands_pb', () => ({ - CommandContainerSchema: {}, -})); - -vi.mock('../../generated/proto/server_message_pb', () => ({ - ServerMessageSchema: {}, - ServerMessage_MessageType: { - RESPONSE: 1, - ROOM_EVENT: 2, - SESSION_EVENT: 3, - GAME_EVENT_CONTAINER: 4, - }, -})); - vi.mock('../events', () => ({ GameEvents: [], RoomEvents: [], @@ -40,6 +26,7 @@ import { GameEvents, RoomEvents, SessionEvents } from '../events'; import type { GameExtensionRegistry } from '../events/game'; import type { RoomExtensionRegistry } from '../events/room'; import type { SessionExtensionRegistry } from '../events/session'; +import { withEventRegistry } from '../../__test-utils__'; import { Data } from '@app/types'; @@ -53,12 +40,20 @@ type ProtobufInternal = ProtobufService & { }; let mockSocket: { isOpen: ReturnType; send: ReturnType }; +let registryTeardowns: Array<() => void>; beforeEach(() => { mockSocket = { isOpen: vi.fn().mockReturnValue(true), send: vi.fn(), }; + registryTeardowns = []; +}); + +afterEach(() => { + while (registryTeardowns.length > 0) { + registryTeardowns.pop()!(); + } }); describe('ProtobufService', () => { @@ -348,8 +343,7 @@ describe('ProtobufService', () => { const mockExt = {} as GenExtension; const payload = { someData: 1 }; - // Temporarily override GameEvents for this test - (GameEvents as GameExtensionRegistry).push([mockExt, handler]); + registryTeardowns.push(withEventRegistry(GameEvents as GameExtensionRegistry, [mockExt, handler])); vi.mocked(hasExtension).mockReturnValue(true); vi.mocked(getExtension).mockReturnValue(payload); @@ -359,7 +353,6 @@ describe('ProtobufService', () => { }, {}); expect(handler).toHaveBeenCalledWith(payload, expect.objectContaining({ gameId: 42, playerId: 5 })); - (GameEvents as GameExtensionRegistry).pop(); }); it('defaults gameId and playerId to -1 when undefined', () => { @@ -368,7 +361,7 @@ describe('ProtobufService', () => { const mockExt = {} as GenExtension; const payload = { someData: 1 }; - (GameEvents as GameExtensionRegistry).push([mockExt, handler]); + registryTeardowns.push(withEventRegistry(GameEvents as GameExtensionRegistry, [mockExt, handler])); vi.mocked(hasExtension).mockReturnValue(true); vi.mocked(getExtension).mockReturnValue(payload); @@ -378,7 +371,6 @@ describe('ProtobufService', () => { }); expect(handler).toHaveBeenCalledWith(payload, expect.objectContaining({ gameId: -1, playerId: -1 })); - (GameEvents as GameExtensionRegistry).pop(); }); }); @@ -405,7 +397,7 @@ describe('ProtobufService', () => { const mockExt = {} as GenExtension; const payload = { roomData: 1 }; - (RoomEvents as RoomExtensionRegistry).push([mockExt, handler]); + registryTeardowns.push(withEventRegistry(RoomEvents as RoomExtensionRegistry, [mockExt, handler])); vi.mocked(hasExtension).mockReturnValue(true); vi.mocked(getExtension).mockReturnValue(payload); @@ -413,7 +405,6 @@ describe('ProtobufService', () => { (service as ProtobufInternal).processRoomEvent(event); expect(handler).toHaveBeenCalledWith(payload, event); - (RoomEvents as RoomExtensionRegistry).pop(); }); }); @@ -431,14 +422,13 @@ describe('ProtobufService', () => { const mockExt = {} as GenExtension; const payload = { sessionData: 1 }; - (SessionEvents as SessionExtensionRegistry).push([mockExt, handler]); + registryTeardowns.push(withEventRegistry(SessionEvents as SessionExtensionRegistry, [mockExt, handler])); vi.mocked(hasExtension).mockReturnValue(true); vi.mocked(getExtension).mockReturnValue(payload); (service as ProtobufInternal).processSessionEvent({ sessionId: 7 }); - expect(handler).toHaveBeenCalledWith(payload); - (SessionEvents as SessionExtensionRegistry).pop(); + expect(handler).toHaveBeenCalledWith(payload, undefined); }); }); diff --git a/webclient/src/websocket/services/ProtobufService.ts b/webclient/src/websocket/services/ProtobufService.ts index 4bc148ece..9c04f10d6 100644 --- a/webclient/src/websocket/services/ProtobufService.ts +++ b/webclient/src/websocket/services/ProtobufService.ts @@ -175,7 +175,7 @@ export class ProtobufService { } for (const [ext, handler] of SessionEvents) { if (hasExtension(event, ext)) { - handler(getExtension(event, ext)); + handler(getExtension(event, ext), undefined); return; } } diff --git a/webclient/src/websocket/services/WebSocketService.spec.ts b/webclient/src/websocket/services/WebSocketService.spec.ts index a806b512f..58e468dbf 100644 --- a/webclient/src/websocket/services/WebSocketService.spec.ts +++ b/webclient/src/websocket/services/WebSocketService.spec.ts @@ -1,4 +1,5 @@ import { installMockWebSocket } from '../__mocks__/helpers'; +import { withMockLocation } from '../../__test-utils__'; import { Mock } from 'vitest'; vi.mock('../WebClient', () => ({ @@ -37,6 +38,7 @@ let MockWS: Mock; let mockInstance: ReturnType['mockInstance']; let restoreWebSocket: ReturnType['restore']; let mockConfig: WebSocketServiceConfig; +let locationRestores: Array<() => void>; beforeEach(() => { vi.useFakeTimers(); @@ -49,9 +51,14 @@ beforeEach(() => { mockConfig = { keepAliveFn: vi.fn(), }; + + locationRestores = []; }); afterEach(() => { + while (locationRestores.length > 0) { + locationRestores.pop()!(); + } restoreWebSocket(); vi.useRealTimers(); }); @@ -88,22 +95,14 @@ describe('WebSocketService', () => { describe('connect', () => { it('creates a WebSocket with wss protocol by default', () => { const service = new WebSocketService(mockConfig); - Object.defineProperty(window, 'location', { - value: { hostname: 'example.com' }, - writable: true, - configurable: true, - }); + locationRestores.push(withMockLocation({ hostname: 'example.com' })); service.connect({ host: 'example.com', port: '8080' }); expect(MockWS).toHaveBeenCalledWith('wss://example.com:8080'); }); it('switches to ws protocol when hostname is localhost', () => { const service = new WebSocketService(mockConfig); - Object.defineProperty(window, 'location', { - value: { hostname: 'localhost' }, - writable: true, - configurable: true, - }); + locationRestores.push(withMockLocation({ hostname: 'localhost' })); service.connect({ host: 'somehost', port: '1234' }); expect(MockWS).toHaveBeenCalledWith('ws://somehost:1234'); }); @@ -243,22 +242,14 @@ describe('WebSocketService', () => { describe('testConnect', () => { it('creates a test WebSocket with correct URL', () => { const service = new WebSocketService(mockConfig); - Object.defineProperty(window, 'location', { - value: { hostname: 'example.com' }, - writable: true, - configurable: true, - }); + locationRestores.push(withMockLocation({ hostname: 'example.com' })); service.testConnect({ host: 'example.com', port: '9000' }); expect(MockWS).toHaveBeenCalledWith('wss://example.com:9000'); }); it('uses ws protocol on localhost', () => { const service = new WebSocketService(mockConfig); - Object.defineProperty(window, 'location', { - value: { hostname: 'localhost' }, - writable: true, - configurable: true, - }); + locationRestores.push(withMockLocation({ hostname: 'localhost' })); service.testConnect({ host: 'h', port: '1' }); expect(MockWS).toHaveBeenCalledWith('ws://h:1'); }); diff --git a/webclient/src/websocket/services/WebSocketService.ts b/webclient/src/websocket/services/WebSocketService.ts index 0ca13b7e6..f4e8fed4e 100644 --- a/webclient/src/websocket/services/WebSocketService.ts +++ b/webclient/src/websocket/services/WebSocketService.ts @@ -65,7 +65,7 @@ export class WebSocketService { } public send(message: Uint8Array): void { - this.socket.send(message); + this.socket.send(message as unknown as ArrayBufferView); } private createWebSocket(url: string): WebSocket { diff --git a/webclient/src/websocket/services/command-options.spec.ts b/webclient/src/websocket/services/command-options.spec.ts index 8548c6c06..23065d8e6 100644 --- a/webclient/src/websocket/services/command-options.spec.ts +++ b/webclient/src/websocket/services/command-options.spec.ts @@ -9,9 +9,11 @@ import { create, getExtension } from '@bufbuild/protobuf'; import { handleResponse } from './command-options'; -beforeEach(() => { - vi.resetAllMocks(); -}); +// NOTE: do NOT call `vi.resetAllMocks()` here — under `isolate: false` it +// resets `vi.fn()` implementations set inside other files' `vi.mock(...)` +// factories, which breaks any spec that relied on those factory defaults +// (e.g. ProtobufService.spec.ts expects `hasExtension` to return `false`). +// The root `setupTests.ts` afterEach already calls `vi.clearAllMocks()`. describe('handleResponse', () => { it('calls onResponse and returns early when provided', () => { diff --git a/webclient/src/websocket/utils/passwordHasher.spec.ts b/webclient/src/websocket/utils/passwordHasher.spec.ts index 388261b36..0d2a22142 100644 --- a/webclient/src/websocket/utils/passwordHasher.spec.ts +++ b/webclient/src/websocket/utils/passwordHasher.spec.ts @@ -1,4 +1,5 @@ -vi.mock('../../generated/proto/event_server_identification_pb', () => ({ +vi.mock('../../generated/proto/event_server_identification_pb', async (importOriginal) => ({ + ...(await importOriginal()), Event_ServerIdentification_ServerOptions: { SupportsPasswordHash: 2 }, })); diff --git a/webclient/vite.config.ts b/webclient/vite.config.ts index cff39dd64..8e96ac328 100644 --- a/webclient/vite.config.ts +++ b/webclient/vite.config.ts @@ -1,9 +1,11 @@ import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; -import tsconfigPaths from 'vite-tsconfig-paths'; export default defineConfig({ - plugins: [react(), tsconfigPaths()], + plugins: [react()], + resolve: { + tsconfigPaths: true, + }, publicDir: 'public', build: { outDir: 'build', From 0ff391491d5528daa5fe181d0483504be189fcc4 Mon Sep 17 00:00:00 2001 From: seavor Date: Wed, 15 Apr 2026 21:48:03 -0500 Subject: [PATCH 16/38] refactor redux data model --- webclient/eslint.boundaries.mjs | 55 ++ webclient/eslint.config.mjs | 4 + webclient/package-lock.json | 740 +++++++++++++++- webclient/package.json | 4 +- webclient/src/api/AdminService.spec.ts | 46 - webclient/src/api/AdminService.tsx | 19 - .../src/api/AuthenticationService.spec.ts | 166 ---- webclient/src/api/AuthenticationService.tsx | 50 -- webclient/src/api/ModeratorService.spec.ts | 73 -- webclient/src/api/ModeratorService.tsx | 29 - webclient/src/api/RoomsService.spec.ts | 35 - webclient/src/api/RoomsService.tsx | 15 - webclient/src/api/SessionService.spec.ts | 100 --- webclient/src/api/SessionService.tsx | 43 - webclient/src/api/index.ts | 37 +- webclient/src/api/request/AdminRequestImpl.ts | 20 + .../api/request/AuthenticationRequestImpl.ts | 37 + .../src/api/request/ModeratorRequestImpl.ts | 37 + webclient/src/api/request/RoomsRequestImpl.ts | 16 + .../src/api/request/SessionRequestImpl.ts | 44 + webclient/src/api/request/index.ts | 23 + .../src/api/response/AdminResponseImpl.ts | 20 + .../src/api/response/GameResponseImpl.ts | 125 +++ .../src/api/response/ModeratorResponseImpl.ts | 45 + .../src/api/response/RoomResponseImpl.ts | 49 ++ .../src/api/response/SessionResponseImpl.ts | 232 +++++ webclient/src/api/response/index.ts | 23 + webclient/src/components/Guard/AuthGuard.tsx | 8 +- webclient/src/components/Guard/ModGuard.tsx | 8 +- .../src/components/KnownHosts/KnownHosts.tsx | 4 +- .../components/UserDisplay/UserDisplay.tsx | 14 +- webclient/src/containers/Account/Account.tsx | 18 +- webclient/src/containers/Layout/LeftNav.tsx | 28 +- webclient/src/containers/Login/Login.tsx | 22 +- webclient/src/containers/Logs/LogResults.tsx | 5 +- webclient/src/containers/Logs/Logs.tsx | 33 +- webclient/src/containers/Room/Games.tsx | 64 +- webclient/src/containers/Room/OpenGames.tsx | 64 +- webclient/src/containers/Room/Room.tsx | 8 +- webclient/src/containers/Server/Rooms.tsx | 38 +- webclient/src/containers/Server/Server.tsx | 4 +- .../src/forms/RegisterForm/RegisterForm.tsx | 6 +- webclient/src/hooks/useDebounce.ts | 25 +- webclient/src/hooks/useReduxEffect.tsx | 5 +- webclient/src/index.tsx | 37 +- webclient/src/store/common/SortUtil.ts | 39 +- .../src/store/common/normalizers.spec.ts | 30 +- webclient/src/store/common/normalizers.ts | 36 +- .../src/store/game/__mocks__/fixtures.ts | 49 +- webclient/src/store/game/game.actions.spec.ts | 107 ++- webclient/src/store/game/game.actions.ts | 212 +---- .../src/store/game/game.dispatch.spec.ts | 65 +- webclient/src/store/game/game.dispatch.ts | 64 +- webclient/src/store/game/game.interfaces.ts | 59 +- webclient/src/store/game/game.reducer.spec.ts | 515 ++++++----- webclient/src/store/game/game.reducer.ts | 824 ++++++------------ .../src/store/game/game.selectors.spec.ts | 4 +- webclient/src/store/game/game.selectors.ts | 42 +- webclient/src/store/game/game.types.ts | 70 +- webclient/src/store/index.ts | 2 - .../store/rooms/__mocks__/rooms-fixtures.ts | 69 +- .../src/store/rooms/rooms.actions.spec.ts | 39 +- webclient/src/store/rooms/rooms.actions.tsx | 74 +- .../src/store/rooms/rooms.dispatch.spec.ts | 24 +- webclient/src/store/rooms/rooms.dispatch.tsx | 23 +- .../src/store/rooms/rooms.interfaces.tsx | 7 - .../src/store/rooms/rooms.reducer.spec.ts | 181 ++-- webclient/src/store/rooms/rooms.reducer.tsx | 411 +++------ .../src/store/rooms/rooms.selectors.spec.ts | 51 +- webclient/src/store/rooms/rooms.selectors.tsx | 73 +- webclient/src/store/rooms/rooms.types.tsx | 30 +- .../store/server/__mocks__/server-fixtures.ts | 20 +- .../src/store/server/server.actions.spec.ts | 181 ++-- webclient/src/store/server/server.actions.ts | 244 +----- .../src/store/server/server.dispatch.spec.ts | 102 +-- webclient/src/store/server/server.dispatch.ts | 106 ++- .../src/store/server/server.interfaces.ts | 12 +- .../src/store/server/server.reducer.spec.ts | 386 ++++---- webclient/src/store/server/server.reducer.ts | 670 ++++++-------- .../src/store/server/server.selectors.spec.ts | 54 +- .../src/store/server/server.selectors.ts | 80 +- webclient/src/store/server/server.types.ts | 142 +-- webclient/src/types/enriched.ts | 102 ++- webclient/src/types/sort.ts | 3 +- webclient/src/websocket/WebClient.spec.ts | 95 +- webclient/src/websocket/WebClient.ts | 48 +- .../__mocks__/sessionCommandMocks.ts | 22 +- .../src/websocket/commands/admin/adjustMod.ts | 8 +- .../commands/admin/adminCommands.spec.ts | 51 +- .../websocket/commands/admin/reloadConfig.ts | 8 +- .../commands/admin/shutdownServer.ts | 18 +- .../commands/admin/updateServerMessage.ts | 8 +- .../src/websocket/commands/game/attachCard.ts | 4 +- .../commands/game/changeZoneProperties.ts | 4 +- .../src/websocket/commands/game/concede.ts | 4 +- .../websocket/commands/game/createArrow.ts | 4 +- .../websocket/commands/game/createCounter.ts | 4 +- .../websocket/commands/game/createToken.ts | 4 +- .../src/websocket/commands/game/deckSelect.ts | 4 +- .../src/websocket/commands/game/delCounter.ts | 4 +- .../websocket/commands/game/deleteArrow.ts | 4 +- .../src/websocket/commands/game/drawCards.ts | 4 +- .../src/websocket/commands/game/dumpZone.ts | 4 +- .../src/websocket/commands/game/flipCard.ts | 4 +- .../commands/game/gameCommands.spec.ts | 84 +- .../src/websocket/commands/game/gameSay.ts | 4 +- .../websocket/commands/game/incCardCounter.ts | 4 +- .../src/websocket/commands/game/incCounter.ts | 4 +- .../src/websocket/commands/game/judge.ts | 4 +- .../websocket/commands/game/kickFromGame.ts | 4 +- .../src/websocket/commands/game/leaveGame.ts | 4 +- .../src/websocket/commands/game/moveCard.ts | 4 +- .../src/websocket/commands/game/mulligan.ts | 4 +- .../src/websocket/commands/game/nextTurn.ts | 4 +- .../src/websocket/commands/game/readyStart.ts | 4 +- .../websocket/commands/game/revealCards.ts | 4 +- .../websocket/commands/game/reverseTurn.ts | 4 +- .../websocket/commands/game/setActivePhase.ts | 4 +- .../websocket/commands/game/setCardAttr.ts | 4 +- .../websocket/commands/game/setCardCounter.ts | 4 +- .../src/websocket/commands/game/setCounter.ts | 4 +- .../commands/game/setSideboardLock.ts | 8 +- .../commands/game/setSideboardPlan.ts | 8 +- .../src/websocket/commands/game/shuffle.ts | 4 +- .../src/websocket/commands/game/unconcede.ts | 4 +- .../src/websocket/commands/game/undoDraw.ts | 4 +- .../commands/moderator/banFromServer.ts | 7 +- .../commands/moderator/forceActivateUser.ts | 7 +- .../commands/moderator/getAdminNotes.ts | 7 +- .../commands/moderator/getBanHistory.ts | 7 +- .../commands/moderator/getWarnHistory.ts | 19 +- .../commands/moderator/getWarnList.ts | 7 +- .../commands/moderator/grantReplayAccess.ts | 7 +- .../moderator/moderatorCommands.spec.ts | 101 +-- .../commands/moderator/updateAdminNotes.ts | 7 +- .../commands/moderator/viewLogHistory.ts | 8 +- .../websocket/commands/moderator/warnUser.ts | 7 +- .../src/websocket/commands/room/createGame.ts | 7 +- .../src/websocket/commands/room/joinGame.ts | 7 +- .../src/websocket/commands/room/leaveRoom.ts | 7 +- .../commands/room/roomCommands.spec.ts | 51 +- .../src/websocket/commands/room/roomSay.ts | 4 +- .../websocket/commands/session/accountEdit.ts | 7 +- .../commands/session/accountImage.ts | 7 +- .../commands/session/accountPassword.ts | 7 +- .../websocket/commands/session/activate.ts | 10 +- .../websocket/commands/session/addToList.ts | 7 +- .../src/websocket/commands/session/connect.ts | 6 +- .../src/websocket/commands/session/deckDel.ts | 7 +- .../websocket/commands/session/deckDelDir.ts | 7 +- .../websocket/commands/session/deckList.ts | 7 +- .../websocket/commands/session/deckNewDir.ts | 7 +- .../websocket/commands/session/deckUpload.ts | 23 +- .../websocket/commands/session/disconnect.ts | 4 +- .../session/forgotPasswordChallenge.ts | 39 +- .../commands/session/forgotPasswordRequest.ts | 12 +- .../commands/session/forgotPasswordReset.ts | 31 +- .../commands/session/getGamesOfUser.ts | 7 +- .../websocket/commands/session/getUserInfo.ts | 7 +- .../websocket/commands/session/joinRoom.ts | 7 +- .../websocket/commands/session/listRooms.ts | 4 +- .../websocket/commands/session/listUsers.ts | 7 +- .../src/websocket/commands/session/login.ts | 18 +- .../src/websocket/commands/session/message.ts | 4 +- .../src/websocket/commands/session/ping.ts | 4 +- .../websocket/commands/session/register.ts | 27 +- .../commands/session/removeFromList.ts | 17 +- .../commands/session/replayDeleteMatch.ts | 17 +- .../commands/session/replayGetCode.ts | 4 +- .../websocket/commands/session/replayList.ts | 7 +- .../commands/session/replayModifyMatch.ts | 7 +- .../commands/session/replaySubmitCode.ts | 14 +- .../commands/session/requestPasswordSalt.ts | 12 +- .../session/sessionCommands-complex.spec.ts | 134 ++- .../session/sessionCommands-simple.spec.ts | 119 ++- .../commands/session/updateStatus.ts | 8 +- .../src/websocket/events/game/attachCard.ts | 4 +- .../events/game/changeZoneProperties.ts | 4 +- .../src/websocket/events/game/createArrow.ts | 4 +- .../websocket/events/game/createCounter.ts | 4 +- .../src/websocket/events/game/createToken.ts | 4 +- .../src/websocket/events/game/delCounter.ts | 4 +- .../src/websocket/events/game/deleteArrow.ts | 4 +- .../src/websocket/events/game/destroyCard.ts | 4 +- .../src/websocket/events/game/drawCards.ts | 4 +- .../src/websocket/events/game/dumpZone.ts | 4 +- .../src/websocket/events/game/flipCard.ts | 4 +- .../src/websocket/events/game/gameClosed.ts | 4 +- .../websocket/events/game/gameEvents.spec.ts | 186 ++-- .../websocket/events/game/gameHostChanged.ts | 4 +- .../src/websocket/events/game/gameSay.ts | 4 +- .../websocket/events/game/gameStateChanged.ts | 4 +- .../src/websocket/events/game/joinGame.ts | 5 +- webclient/src/websocket/events/game/kicked.ts | 4 +- .../src/websocket/events/game/leaveGame.ts | 4 +- .../src/websocket/events/game/moveCard.ts | 4 +- .../events/game/playerPropertiesChanged.ts | 4 +- .../src/websocket/events/game/revealCards.ts | 4 +- .../src/websocket/events/game/reverseTurn.ts | 4 +- .../src/websocket/events/game/rollDie.ts | 4 +- .../websocket/events/game/setActivePhase.ts | 4 +- .../websocket/events/game/setActivePlayer.ts | 4 +- .../src/websocket/events/game/setCardAttr.ts | 4 +- .../websocket/events/game/setCardCounter.ts | 4 +- .../src/websocket/events/game/setCounter.ts | 4 +- .../src/websocket/events/game/shuffle.ts | 4 +- .../src/websocket/events/room/joinRoom.ts | 4 +- .../src/websocket/events/room/leaveRoom.ts | 4 +- .../src/websocket/events/room/listGames.ts | 4 +- .../websocket/events/room/removeMessages.ts | 4 +- .../websocket/events/room/roomEvents.spec.ts | 42 +- .../src/websocket/events/room/roomSay.ts | 4 +- .../src/websocket/events/session/addToList.ts | 6 +- .../websocket/events/session/gameJoined.ts | 4 +- .../src/websocket/events/session/listRooms.ts | 4 +- .../websocket/events/session/notifyUser.ts | 4 +- .../events/session/removeFromList.ts | 6 +- .../websocket/events/session/replayAdded.ts | 4 +- .../events/session/serverCompleteList.ts | 6 +- .../events/session/serverIdentification.ts | 10 +- .../websocket/events/session/serverMessage.ts | 5 +- .../events/session/serverShutdown.ts | 4 +- .../events/session/sessionEvents.spec.ts | 141 +-- .../websocket/events/session/userJoined.ts | 4 +- .../src/websocket/events/session/userLeft.ts | 4 +- .../websocket/events/session/userMessage.ts | 4 +- webclient/src/websocket/index.ts | 3 +- .../websocket/interfaces/WebClientRequest.ts | 63 ++ .../websocket/interfaces/WebClientResponse.ts | 134 +++ webclient/src/websocket/interfaces/index.ts | 17 + .../persistence/AdminPersistence.spec.ts | 33 - .../websocket/persistence/AdminPersistence.ts | 19 - .../persistence/GamePersistence.spec.ts | 208 ----- .../websocket/persistence/GamePersistence.ts | 121 --- .../persistence/ModeratorPersistence.spec.ts | 72 -- .../persistence/ModeratorPersistence.ts | 44 - .../persistence/RoomPersistence.spec.ts | 94 -- .../websocket/persistence/RoomPersistence.ts | 48 - .../persistence/SessionPersistence.spec.ts | 401 --------- .../persistence/SessionPersistence.ts | 229 ----- webclient/src/websocket/persistence/index.ts | 5 - .../services/WebSocketService.spec.ts | 65 +- .../websocket/services/WebSocketService.ts | 21 +- 243 files changed, 5212 insertions(+), 5963 deletions(-) create mode 100644 webclient/eslint.boundaries.mjs delete mode 100644 webclient/src/api/AdminService.spec.ts delete mode 100644 webclient/src/api/AdminService.tsx delete mode 100644 webclient/src/api/AuthenticationService.spec.ts delete mode 100644 webclient/src/api/AuthenticationService.tsx delete mode 100644 webclient/src/api/ModeratorService.spec.ts delete mode 100644 webclient/src/api/ModeratorService.tsx delete mode 100644 webclient/src/api/RoomsService.spec.ts delete mode 100644 webclient/src/api/RoomsService.tsx delete mode 100644 webclient/src/api/SessionService.spec.ts delete mode 100644 webclient/src/api/SessionService.tsx create mode 100644 webclient/src/api/request/AdminRequestImpl.ts create mode 100644 webclient/src/api/request/AuthenticationRequestImpl.ts create mode 100644 webclient/src/api/request/ModeratorRequestImpl.ts create mode 100644 webclient/src/api/request/RoomsRequestImpl.ts create mode 100644 webclient/src/api/request/SessionRequestImpl.ts create mode 100644 webclient/src/api/request/index.ts create mode 100644 webclient/src/api/response/AdminResponseImpl.ts create mode 100644 webclient/src/api/response/GameResponseImpl.ts create mode 100644 webclient/src/api/response/ModeratorResponseImpl.ts create mode 100644 webclient/src/api/response/RoomResponseImpl.ts create mode 100644 webclient/src/api/response/SessionResponseImpl.ts create mode 100644 webclient/src/api/response/index.ts create mode 100644 webclient/src/websocket/interfaces/WebClientRequest.ts create mode 100644 webclient/src/websocket/interfaces/WebClientResponse.ts create mode 100644 webclient/src/websocket/interfaces/index.ts delete mode 100644 webclient/src/websocket/persistence/AdminPersistence.spec.ts delete mode 100644 webclient/src/websocket/persistence/AdminPersistence.ts delete mode 100644 webclient/src/websocket/persistence/GamePersistence.spec.ts delete mode 100644 webclient/src/websocket/persistence/GamePersistence.ts delete mode 100644 webclient/src/websocket/persistence/ModeratorPersistence.spec.ts delete mode 100644 webclient/src/websocket/persistence/ModeratorPersistence.ts delete mode 100644 webclient/src/websocket/persistence/RoomPersistence.spec.ts delete mode 100644 webclient/src/websocket/persistence/RoomPersistence.ts delete mode 100644 webclient/src/websocket/persistence/SessionPersistence.spec.ts delete mode 100644 webclient/src/websocket/persistence/SessionPersistence.ts delete mode 100644 webclient/src/websocket/persistence/index.ts diff --git a/webclient/eslint.boundaries.mjs b/webclient/eslint.boundaries.mjs new file mode 100644 index 000000000..0d67c6ae9 --- /dev/null +++ b/webclient/eslint.boundaries.mjs @@ -0,0 +1,55 @@ +import boundaries from 'eslint-plugin-boundaries'; + +const elements = [ + { type: 'api', pattern: ['src/api/**'] }, + { type: 'components', pattern: ['src/components/**'] }, + { type: 'containers', pattern: ['src/containers/**'] }, + { type: 'dialogs', pattern: ['src/dialogs/**'] }, + { type: 'forms', pattern: ['src/forms/**'] }, + { type: 'generated', pattern: ['src/generated/**'] }, + { type: 'hooks', pattern: ['src/hooks/**'] }, + { type: 'images', pattern: ['src/images/**'] }, + { type: 'services', pattern: ['src/services/**'] }, + { type: 'store', pattern: ['src/store/**'] }, + { type: 'types', pattern: ['src/types/**'] }, + { type: 'websocket', pattern: ['src/websocket/**'] }, +]; + +const types = (...types) => types.map((type) => ({ to: { type } })); + +const rules = [ + { from: { type: 'generated' }, allow: [] }, + { from: { type: 'types' }, allow: types('generated') }, + + { from: { type: 'websocket' }, allow: types('types') }, + { from: { type: 'store' }, allow: types('types') }, + { from: { type: 'api' }, allow: types('types', 'store', 'websocket') }, + + { from: { type: 'hooks' }, allow: types('services', 'types') }, + { from: { type: 'images' }, allow: types('types') }, + { from: { type: 'services' }, allow: types('api', 'store', 'types') }, + + { from: { type: 'components' }, allow: types('api', 'dialogs', 'forms', 'hooks', 'images', 'services', 'types', 'store') }, + { from: { type: 'containers' }, allow: types('api', 'components', 'dialogs', 'forms', 'hooks', 'images', 'services', 'types', 'store') }, + { from: { type: 'dialogs' }, allow: types('components', 'forms', 'hooks', 'services', 'types', 'store') }, + { from: { type: 'forms' }, allow: types('components', 'hooks', 'types', 'services', 'store') }, +]; + +export const boundariesConfig = { + plugins: { boundaries }, + settings: { + 'boundaries/elements': elements, + 'import/resolver': { + typescript: { + alwaysTryTypes: true, + project: './tsconfig.json', + }, + }, + }, + rules: { + 'boundaries/dependencies': ['error', { + default: 'disallow', + rules, + }], + }, +}; diff --git a/webclient/eslint.config.mjs b/webclient/eslint.config.mjs index f5a46a5a7..28a00a0ae 100644 --- a/webclient/eslint.config.mjs +++ b/webclient/eslint.config.mjs @@ -1,6 +1,7 @@ import js from '@eslint/js'; import tseslint from 'typescript-eslint'; import globals from 'globals'; +import { boundariesConfig } from './eslint.boundaries.mjs'; export default tseslint.config( // Global ignores @@ -12,6 +13,9 @@ export default tseslint.config( // TypeScript recommended (sets up parser + plugin) ...tseslint.configs.recommended, + // Enforce module boundaries + boundariesConfig, + // Project-specific config { languageOptions: { diff --git a/webclient/package-lock.json b/webclient/package-lock.json index 9f48a49b2..ed7f1854a 100644 --- a/webclient/package-lock.json +++ b/webclient/package-lock.json @@ -23,7 +23,6 @@ "i18next-browser-languagedetector": "^8.2.1", "i18next-icu": "^2.0.3", "intl-messageformat": "^11.2.1", - "lodash": "^4.17.21", "prop-types": "^15.8.1", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -45,7 +44,6 @@ "@testing-library/jest-dom": "^6.4.0", "@testing-library/react": "^16.3.2", "@types/dompurify": "^3.0.5", - "@types/lodash": "^4.14.179", "@types/node": "^22.19.17", "@types/prop-types": "^15.7.4", "@types/react": "^19.0.0", @@ -57,6 +55,8 @@ "@vitejs/plugin-react": "^6.0.1", "@vitest/coverage-v8": "^4.1.4", "eslint": "^10.2.0", + "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-boundaries": "^6.0.2", "fs-extra": "^11.3.4", "globals": "^17.5.0", "husky": "^9.1.7", @@ -262,6 +262,23 @@ "node": ">=18" } }, + "node_modules/@boundaries/elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@boundaries/elements/-/elements-2.0.1.tgz", + "integrity": "sha512-sAWO3D8PFP6pBXdxxW93SQi/KQqqhE2AAHo3AgWfdtJXwO6bfK6/wUN81XnOZk0qRC6vHzUEKhjwVD9dtDWvxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-import-resolver-node": "0.3.9", + "eslint-module-utils": "2.12.1", + "handlebars": "4.7.9", + "is-core-module": "2.16.1", + "micromatch": "4.0.8" + }, + "engines": { + "node": ">=18.18" + } + }, "node_modules/@bramus/specificity": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", @@ -1753,13 +1770,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/lodash": { - "version": "4.17.24", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", - "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "22.19.17", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", @@ -2090,6 +2100,288 @@ "typescript": "*" } }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@vitejs/plugin-react": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", @@ -2417,6 +2709,19 @@ "node": "18 || 20 || >=22" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2436,6 +2741,39 @@ "node": ">=18" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2445,6 +2783,26 @@ "node": ">=6" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -2731,6 +3089,137 @@ } } }, + "node_modules/eslint-import-context": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", + "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-tsconfig": "^4.10.1", + "stable-hash-x": "^0.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-context" + }, + "peerDependencies": { + "unrs-resolver": "^1.0.0" + }, + "peerDependenciesMeta": { + "unrs-resolver": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.4.tgz", + "integrity": "sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==", + "dev": true, + "license": "ISC", + "dependencies": { + "debug": "^4.4.1", + "eslint-import-context": "^0.1.8", + "get-tsconfig": "^4.10.1", + "is-bun-module": "^2.0.0", + "stable-hash-x": "^0.2.0", + "tinyglobby": "^0.2.14", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^16.17.0 || >=18.6.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-boundaries": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-boundaries/-/eslint-plugin-boundaries-6.0.2.tgz", + "integrity": "sha512-wSHgiYeMEbziP91lH0UQ9oslgF2djG1x+LV9z/qO19ggMKZaCB8pKIGePHAY91eLF4EAgpsxQk8MRSFGRPfPzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@boundaries/elements": "2.0.1", + "chalk": "4.1.2", + "eslint-import-resolver-node": "0.3.9", + "eslint-module-utils": "2.12.1", + "handlebars": "4.7.9", + "micromatch": "4.0.8" + }, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, "node_modules/eslint-scope": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", @@ -2935,6 +3424,19 @@ "node": ">=16.0.0" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/final-form": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/final-form/-/final-form-5.0.0.tgz", @@ -3043,6 +3545,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3076,6 +3591,38 @@ "dev": true, "license": "ISC" }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3279,6 +3826,16 @@ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "license": "MIT" }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -3317,6 +3874,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -3776,12 +4343,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "license": "MIT" - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3859,6 +4420,33 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -3885,6 +4473,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3910,6 +4508,22 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -3917,6 +4531,13 @@ "dev": true, "license": "MIT" }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4436,6 +5057,16 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/rolldown": { "version": "1.0.0-rc.15", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", @@ -4573,6 +5204,16 @@ "node": ">=0.10.0" } }, + "node_modules/stable-hash-x": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", + "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -4702,6 +5343,19 @@ "dev": true, "license": "MIT" }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/tough-cookie": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", @@ -4798,6 +5452,20 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/undici": { "version": "7.25.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", @@ -4825,6 +5493,41 @@ "node": ">= 10.0.0" } }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5112,6 +5815,13 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", diff --git a/webclient/package.json b/webclient/package.json index b2927025f..27f84e51d 100644 --- a/webclient/package.json +++ b/webclient/package.json @@ -33,7 +33,6 @@ "i18next-browser-languagedetector": "^8.2.1", "i18next-icu": "^2.0.3", "intl-messageformat": "^11.2.1", - "lodash": "^4.17.21", "prop-types": "^15.8.1", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -55,7 +54,6 @@ "@testing-library/jest-dom": "^6.4.0", "@testing-library/react": "^16.3.2", "@types/dompurify": "^3.0.5", - "@types/lodash": "^4.14.179", "@types/node": "^22.19.17", "@types/prop-types": "^15.7.4", "@types/react": "^19.0.0", @@ -67,6 +65,8 @@ "@vitejs/plugin-react": "^6.0.1", "@vitest/coverage-v8": "^4.1.4", "eslint": "^10.2.0", + "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-boundaries": "^6.0.2", "fs-extra": "^11.3.4", "globals": "^17.5.0", "husky": "^9.1.7", diff --git a/webclient/src/api/AdminService.spec.ts b/webclient/src/api/AdminService.spec.ts deleted file mode 100644 index 1964a8368..000000000 --- a/webclient/src/api/AdminService.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -vi.mock('@app/websocket', () => ({ - AdminCommands: { - adjustMod: vi.fn(), - reloadConfig: vi.fn(), - shutdownServer: vi.fn(), - updateServerMessage: vi.fn(), - }, -})); - -import { AdminService } from './AdminService'; -import { AdminCommands } from '@app/websocket'; - -describe('AdminService', () => { - describe('adjustMod', () => { - it('delegates to AdminCommands.adjustMod with all arguments', () => { - AdminService.adjustMod('alice', true, false); - expect(AdminCommands.adjustMod).toHaveBeenCalledWith('alice', true, false); - }); - - it('delegates with optional arguments omitted', () => { - AdminService.adjustMod('alice'); - expect(AdminCommands.adjustMod).toHaveBeenCalledWith('alice', undefined, undefined); - }); - }); - - describe('reloadConfig', () => { - it('delegates to AdminCommands.reloadConfig', () => { - AdminService.reloadConfig(); - expect(AdminCommands.reloadConfig).toHaveBeenCalled(); - }); - }); - - describe('shutdownServer', () => { - it('delegates to AdminCommands.shutdownServer', () => { - AdminService.shutdownServer('maintenance', 10); - expect(AdminCommands.shutdownServer).toHaveBeenCalledWith('maintenance', 10); - }); - }); - - describe('updateServerMessage', () => { - it('delegates to AdminCommands.updateServerMessage', () => { - AdminService.updateServerMessage(); - expect(AdminCommands.updateServerMessage).toHaveBeenCalled(); - }); - }); -}); diff --git a/webclient/src/api/AdminService.tsx b/webclient/src/api/AdminService.tsx deleted file mode 100644 index c280fca7b..000000000 --- a/webclient/src/api/AdminService.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { AdminCommands } from '@app/websocket'; - -export class AdminService { - static adjustMod(userName: string, shouldBeMod?: boolean, shouldBeJudge?: boolean): void { - AdminCommands.adjustMod(userName, shouldBeMod, shouldBeJudge); - } - - static reloadConfig(): void { - AdminCommands.reloadConfig(); - } - - static shutdownServer(reason: string, minutes: number): void { - AdminCommands.shutdownServer(reason, minutes); - } - - static updateServerMessage(): void { - AdminCommands.updateServerMessage(); - } -} diff --git a/webclient/src/api/AuthenticationService.spec.ts b/webclient/src/api/AuthenticationService.spec.ts deleted file mode 100644 index 0b2dfbc1c..000000000 --- a/webclient/src/api/AuthenticationService.spec.ts +++ /dev/null @@ -1,166 +0,0 @@ -vi.mock('@app/websocket', () => ({ - SessionCommands: { - connect: vi.fn(), - disconnect: vi.fn(), - }, -})); - -vi.mock('../generated/proto/serverinfo_user_pb', async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - ServerInfo_User_UserLevelFlag: { - IsModerator: 4, - }, - }; -}); - -import { AuthenticationService } from './AuthenticationService'; -import { SessionCommands } from '@app/websocket'; -import { App, Data } from '@app/types'; -import { create } from '@bufbuild/protobuf'; - -const baseTransport = { host: 'localhost', port: '4748' }; - -describe('AuthenticationService', () => { - describe('login', () => { - it('calls SessionCommands.connect with LOGIN reason', () => { - AuthenticationService.login({ ...baseTransport, userName: 'user', password: 'pw' }); - expect(SessionCommands.connect).toHaveBeenCalledWith( - expect.objectContaining({ - ...baseTransport, - userName: 'user', - password: 'pw', - reason: App.WebSocketConnectReason.LOGIN, - }) - ); - }); - }); - - describe('testConnection', () => { - it('calls SessionCommands.connect with TEST_CONNECTION reason', () => { - AuthenticationService.testConnection(baseTransport); - expect(SessionCommands.connect).toHaveBeenCalledWith( - expect.objectContaining({ ...baseTransport, reason: App.WebSocketConnectReason.TEST_CONNECTION }) - ); - }); - }); - - describe('register', () => { - it('calls SessionCommands.connect with REGISTER reason', () => { - AuthenticationService.register({ - ...baseTransport, - userName: 'user', - password: 'pw', - email: 'a@b.com', - country: 'US', - realName: 'User', - }); - expect(SessionCommands.connect).toHaveBeenCalledWith( - expect.objectContaining({ userName: 'user', reason: App.WebSocketConnectReason.REGISTER }) - ); - }); - }); - - describe('activateAccount', () => { - it('calls SessionCommands.connect with ACTIVATE_ACCOUNT reason', () => { - AuthenticationService.activateAccount({ - ...baseTransport, - userName: 'user', - token: 'tok', - }); - expect(SessionCommands.connect).toHaveBeenCalledWith( - expect.objectContaining({ token: 'tok', reason: App.WebSocketConnectReason.ACTIVATE_ACCOUNT }) - ); - }); - }); - - describe('resetPasswordRequest', () => { - it('calls SessionCommands.connect with PASSWORD_RESET_REQUEST reason', () => { - AuthenticationService.resetPasswordRequest({ ...baseTransport, userName: 'user' }); - expect(SessionCommands.connect).toHaveBeenCalledWith( - expect.objectContaining({ userName: 'user', reason: App.WebSocketConnectReason.PASSWORD_RESET_REQUEST }) - ); - }); - }); - - describe('resetPasswordChallenge', () => { - it('calls SessionCommands.connect with PASSWORD_RESET_CHALLENGE reason', () => { - AuthenticationService.resetPasswordChallenge({ - ...baseTransport, - userName: 'user', - email: 'a@b.com', - }); - expect(SessionCommands.connect).toHaveBeenCalledWith( - expect.objectContaining({ email: 'a@b.com', reason: App.WebSocketConnectReason.PASSWORD_RESET_CHALLENGE }) - ); - }); - }); - - describe('resetPassword', () => { - it('calls SessionCommands.connect with PASSWORD_RESET reason', () => { - AuthenticationService.resetPassword({ - ...baseTransport, - userName: 'user', - token: 'tok', - newPassword: 'newpw', - }); - expect(SessionCommands.connect).toHaveBeenCalledWith( - expect.objectContaining({ newPassword: 'newpw', reason: App.WebSocketConnectReason.PASSWORD_RESET }) - ); - }); - }); - - describe('disconnect', () => { - it('delegates to SessionCommands.disconnect', () => { - AuthenticationService.disconnect(); - expect(SessionCommands.disconnect).toHaveBeenCalled(); - }); - }); - - describe('isConnected', () => { - it('returns true when state is LOGGED_IN', () => { - expect(AuthenticationService.isConnected(App.StatusEnum.LOGGED_IN)).toBe(true); - }); - - it('returns false when state is DISCONNECTED', () => { - expect(AuthenticationService.isConnected(App.StatusEnum.DISCONNECTED)).toBe(false); - }); - - it('returns false when state is CONNECTING', () => { - expect(AuthenticationService.isConnected(App.StatusEnum.CONNECTING)).toBe(false); - }); - - it('returns false when state is CONNECTED', () => { - expect(AuthenticationService.isConnected(App.StatusEnum.CONNECTED)).toBe(false); - }); - - it('returns false when state is LOGGING_IN', () => { - expect(AuthenticationService.isConnected(App.StatusEnum.LOGGING_IN)).toBe(false); - }); - }); - - describe('isModerator', () => { - it('returns true when userLevel has the IsModerator bit set', () => { - expect(AuthenticationService.isModerator(create(Data.ServerInfo_UserSchema, { userLevel: 4 }))).toBe(true); - }); - - it('returns true when userLevel has IsModerator and other bits set', () => { - expect(AuthenticationService.isModerator(create(Data.ServerInfo_UserSchema, { userLevel: 7 }))).toBe(true); - }); - - it('returns false when userLevel does not have the IsModerator bit', () => { - expect(AuthenticationService.isModerator(create(Data.ServerInfo_UserSchema, { userLevel: 1 }))).toBe(false); - }); - - it('returns false for admin-only userLevel without moderator bit', () => { - expect(AuthenticationService.isModerator(create(Data.ServerInfo_UserSchema, { userLevel: 8 }))).toBe(false); - }); - }); - - describe('isAdmin', () => { - it('returns undefined (not yet implemented)', () => { - expect(AuthenticationService.isAdmin()).toBeUndefined(); - }); - }); -}); diff --git a/webclient/src/api/AuthenticationService.tsx b/webclient/src/api/AuthenticationService.tsx deleted file mode 100644 index bacc8e350..000000000 --- a/webclient/src/api/AuthenticationService.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { App, Data, Enriched } from '@app/types'; -import { SessionCommands } from '@app/websocket'; - -export class AuthenticationService { - static login(options: Omit): void { - SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.LOGIN }); - } - - static testConnection(options: Omit): void { - SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.TEST_CONNECTION }); - } - - static register(options: Omit): void { - SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.REGISTER }); - } - - static activateAccount(options: Omit): void { - SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.ACTIVATE_ACCOUNT }); - } - - static resetPasswordRequest(options: Omit): void { - SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.PASSWORD_RESET_REQUEST }); - } - - static resetPasswordChallenge(options: Omit): void { - SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.PASSWORD_RESET_CHALLENGE }); - } - - static resetPassword(options: Omit): void { - SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.PASSWORD_RESET }); - } - - static disconnect(): void { - SessionCommands.disconnect(); - } - - static isConnected(state: number): boolean { - return state === App.StatusEnum.LOGGED_IN; - } - - static isModerator(user: Data.ServerInfo_User): boolean { - const moderatorLevel = Data.ServerInfo_User_UserLevelFlag.IsModerator; - // @TODO tell cockatrice not to do this so shittily - return (user.userLevel & moderatorLevel) === moderatorLevel; - } - - static isAdmin() { - - } -} diff --git a/webclient/src/api/ModeratorService.spec.ts b/webclient/src/api/ModeratorService.spec.ts deleted file mode 100644 index f32a58d09..000000000 --- a/webclient/src/api/ModeratorService.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -vi.mock('@app/websocket', () => ({ - ModeratorCommands: { - banFromServer: vi.fn(), - getBanHistory: vi.fn(), - getWarnHistory: vi.fn(), - getWarnList: vi.fn(), - viewLogHistory: vi.fn(), - warnUser: vi.fn(), - }, -})); - -import { ModeratorService } from './ModeratorService'; -import { ModeratorCommands } from '@app/websocket'; -import { Data } from '@app/types'; - -describe('ModeratorService', () => { - describe('banFromServer', () => { - it('delegates to ModeratorCommands.banFromServer with all arguments', () => { - ModeratorService.banFromServer(30, 'alice', '1.2.3.4', 'reason', 'visible reason', 'cid', 1); - expect(ModeratorCommands.banFromServer).toHaveBeenCalledWith( - 30, 'alice', '1.2.3.4', 'reason', 'visible reason', 'cid', 1 - ); - }); - - it('delegates with only required argument', () => { - ModeratorService.banFromServer(60); - expect(ModeratorCommands.banFromServer).toHaveBeenCalledWith( - 60, undefined, undefined, undefined, undefined, undefined, undefined - ); - }); - }); - - describe('getBanHistory', () => { - it('delegates to ModeratorCommands.getBanHistory', () => { - ModeratorService.getBanHistory('alice'); - expect(ModeratorCommands.getBanHistory).toHaveBeenCalledWith('alice'); - }); - }); - - describe('getWarnHistory', () => { - it('delegates to ModeratorCommands.getWarnHistory', () => { - ModeratorService.getWarnHistory('alice'); - expect(ModeratorCommands.getWarnHistory).toHaveBeenCalledWith('alice'); - }); - }); - - describe('getWarnList', () => { - it('delegates to ModeratorCommands.getWarnList', () => { - ModeratorService.getWarnList('mod1', 'alice', 'cid123'); - expect(ModeratorCommands.getWarnList).toHaveBeenCalledWith('mod1', 'alice', 'cid123'); - }); - }); - - describe('viewLogHistory', () => { - it('delegates to ModeratorCommands.viewLogHistory', () => { - const filters: Data.ViewLogHistoryParams = { dateRange: 7, userName: 'alice' }; - ModeratorService.viewLogHistory(filters); - expect(ModeratorCommands.viewLogHistory).toHaveBeenCalledWith(filters); - }); - }); - - describe('warnUser', () => { - it('delegates to ModeratorCommands.warnUser with all arguments', () => { - ModeratorService.warnUser('alice', 'spamming', 'cid', 5); - expect(ModeratorCommands.warnUser).toHaveBeenCalledWith('alice', 'spamming', 'cid', 5); - }); - - it('delegates with only required arguments', () => { - ModeratorService.warnUser('alice', 'spamming'); - expect(ModeratorCommands.warnUser).toHaveBeenCalledWith('alice', 'spamming', undefined, undefined); - }); - }); -}); diff --git a/webclient/src/api/ModeratorService.tsx b/webclient/src/api/ModeratorService.tsx deleted file mode 100644 index 2fa9e9019..000000000 --- a/webclient/src/api/ModeratorService.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { ModeratorCommands } from '@app/websocket'; -import { Data } from '@app/types'; - -export class ModeratorService { - static banFromServer(minutes: number, userName?: string, address?: string, reason?: string, - visibleReason?: string, clientid?: string, removeMessages?: number): void { - ModeratorCommands.banFromServer(minutes, userName, address, reason, visibleReason, clientid, removeMessages); - } - - static getBanHistory(userName: string): void { - ModeratorCommands.getBanHistory(userName); - } - - static getWarnHistory(userName: string): void { - ModeratorCommands.getWarnHistory(userName); - } - - static getWarnList(modName: string, userName: string, userClientid: string): void { - ModeratorCommands.getWarnList(modName, userName, userClientid); - } - - static viewLogHistory(filters: Data.ViewLogHistoryParams): void { - ModeratorCommands.viewLogHistory(filters); - } - - static warnUser(userName: string, reason: string, clientid?: string, removeMessages?: number): void { - ModeratorCommands.warnUser(userName, reason, clientid, removeMessages); - } -} diff --git a/webclient/src/api/RoomsService.spec.ts b/webclient/src/api/RoomsService.spec.ts deleted file mode 100644 index 80ff8d4cd..000000000 --- a/webclient/src/api/RoomsService.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -vi.mock('@app/websocket', () => ({ - SessionCommands: { - joinRoom: vi.fn(), - }, - RoomCommands: { - leaveRoom: vi.fn(), - roomSay: vi.fn(), - }, -})); - -import { RoomsService } from './RoomsService'; -import { RoomCommands, SessionCommands } from '@app/websocket'; - -describe('RoomsService', () => { - describe('joinRoom', () => { - it('delegates to SessionCommands.joinRoom', () => { - RoomsService.joinRoom(42); - expect(SessionCommands.joinRoom).toHaveBeenCalledWith(42); - }); - }); - - describe('leaveRoom', () => { - it('delegates to RoomCommands.leaveRoom', () => { - RoomsService.leaveRoom(42); - expect(RoomCommands.leaveRoom).toHaveBeenCalledWith(42); - }); - }); - - describe('roomSay', () => { - it('delegates to RoomCommands.roomSay', () => { - RoomsService.roomSay(42, 'hello room'); - expect(RoomCommands.roomSay).toHaveBeenCalledWith(42, 'hello room'); - }); - }); -}); diff --git a/webclient/src/api/RoomsService.tsx b/webclient/src/api/RoomsService.tsx deleted file mode 100644 index b2f9ddc4e..000000000 --- a/webclient/src/api/RoomsService.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { RoomCommands, SessionCommands } from '@app/websocket'; - -export class RoomsService { - static joinRoom(roomId: number): void { - SessionCommands.joinRoom(roomId); - } - - static leaveRoom(roomId: number): void { - RoomCommands.leaveRoom(roomId); - } - - static roomSay(roomId: number, message: string): void { - RoomCommands.roomSay(roomId, message); - } -} diff --git a/webclient/src/api/SessionService.spec.ts b/webclient/src/api/SessionService.spec.ts deleted file mode 100644 index 879d4c3ef..000000000 --- a/webclient/src/api/SessionService.spec.ts +++ /dev/null @@ -1,100 +0,0 @@ -vi.mock('@app/websocket', () => ({ - SessionCommands: { - addToBuddyList: vi.fn(), - removeFromBuddyList: vi.fn(), - addToIgnoreList: vi.fn(), - removeFromIgnoreList: vi.fn(), - accountPassword: vi.fn(), - accountEdit: vi.fn(), - accountImage: vi.fn(), - message: vi.fn(), - getUserInfo: vi.fn(), - getGamesOfUser: vi.fn(), - }, -})); - -import { SessionService } from './SessionService'; -import { SessionCommands } from '@app/websocket'; - -describe('SessionService', () => { - describe('addToBuddyList', () => { - it('delegates to SessionCommands.addToBuddyList', () => { - SessionService.addToBuddyList('alice'); - expect(SessionCommands.addToBuddyList).toHaveBeenCalledWith('alice'); - }); - }); - - describe('removeFromBuddyList', () => { - it('delegates to SessionCommands.removeFromBuddyList', () => { - SessionService.removeFromBuddyList('alice'); - expect(SessionCommands.removeFromBuddyList).toHaveBeenCalledWith('alice'); - }); - }); - - describe('addToIgnoreList', () => { - it('delegates to SessionCommands.addToIgnoreList', () => { - SessionService.addToIgnoreList('bob'); - expect(SessionCommands.addToIgnoreList).toHaveBeenCalledWith('bob'); - }); - }); - - describe('removeFromIgnoreList', () => { - it('delegates to SessionCommands.removeFromIgnoreList', () => { - SessionService.removeFromIgnoreList('bob'); - expect(SessionCommands.removeFromIgnoreList).toHaveBeenCalledWith('bob'); - }); - }); - - describe('changeAccountPassword', () => { - it('delegates to SessionCommands.accountPassword with all arguments', () => { - SessionService.changeAccountPassword('oldPw', 'newPw', 'hashedPw'); - expect(SessionCommands.accountPassword).toHaveBeenCalledWith('oldPw', 'newPw', 'hashedPw'); - }); - - it('delegates without hashedNewPassword when omitted', () => { - SessionService.changeAccountPassword('oldPw', 'newPw'); - expect(SessionCommands.accountPassword).toHaveBeenCalledWith('oldPw', 'newPw', undefined); - }); - }); - - describe('changeAccountDetails', () => { - it('delegates to SessionCommands.accountEdit with all arguments', () => { - SessionService.changeAccountDetails('pw', 'Alice', 'alice@example.com', 'US'); - expect(SessionCommands.accountEdit).toHaveBeenCalledWith('pw', 'Alice', 'alice@example.com', 'US'); - }); - - it('delegates with only required argument', () => { - SessionService.changeAccountDetails('pw'); - expect(SessionCommands.accountEdit).toHaveBeenCalledWith('pw', undefined, undefined, undefined); - }); - }); - - describe('changeAccountImage', () => { - it('delegates to SessionCommands.accountImage', () => { - const image = new Uint8Array([1, 2, 3]); - SessionService.changeAccountImage(image); - expect(SessionCommands.accountImage).toHaveBeenCalledWith(image); - }); - }); - - describe('sendDirectMessage', () => { - it('delegates to SessionCommands.message', () => { - SessionService.sendDirectMessage('alice', 'hello'); - expect(SessionCommands.message).toHaveBeenCalledWith('alice', 'hello'); - }); - }); - - describe('getUserInfo', () => { - it('delegates to SessionCommands.getUserInfo', () => { - SessionService.getUserInfo('alice'); - expect(SessionCommands.getUserInfo).toHaveBeenCalledWith('alice'); - }); - }); - - describe('getUserGames', () => { - it('delegates to SessionCommands.getGamesOfUser', () => { - SessionService.getUserGames('alice'); - expect(SessionCommands.getGamesOfUser).toHaveBeenCalledWith('alice'); - }); - }); -}); diff --git a/webclient/src/api/SessionService.tsx b/webclient/src/api/SessionService.tsx deleted file mode 100644 index 129cdfc58..000000000 --- a/webclient/src/api/SessionService.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { SessionCommands } from '@app/websocket'; - -export class SessionService { - static addToBuddyList(userName: string) { - SessionCommands.addToBuddyList(userName); - } - - static removeFromBuddyList(userName: string) { - SessionCommands.removeFromBuddyList(userName); - } - - static addToIgnoreList(userName: string) { - SessionCommands.addToIgnoreList(userName); - } - - static removeFromIgnoreList(userName: string) { - SessionCommands.removeFromIgnoreList(userName); - } - - static changeAccountPassword(oldPassword: string, newPassword: string, hashedNewPassword?: string): void { - SessionCommands.accountPassword(oldPassword, newPassword, hashedNewPassword); - } - - static changeAccountDetails(passwordCheck: string, realName?: string, email?: string, country?: string): void { - SessionCommands.accountEdit(passwordCheck, realName, email, country); - } - - static changeAccountImage(image: Uint8Array): void { - SessionCommands.accountImage(image); - } - - static sendDirectMessage(userName: string, message: string): void { - SessionCommands.message(userName, message); - } - - static getUserInfo(userName: string): void { - SessionCommands.getUserInfo(userName); - } - - static getUserGames(userName: string): void { - SessionCommands.getGamesOfUser(userName); - } -} diff --git a/webclient/src/api/index.ts b/webclient/src/api/index.ts index c4f67092e..651bc3162 100644 --- a/webclient/src/api/index.ts +++ b/webclient/src/api/index.ts @@ -1,5 +1,32 @@ -export { AdminService } from './AdminService'; -export { AuthenticationService } from './AuthenticationService'; -export { ModeratorService } from './ModeratorService'; -export { RoomsService } from './RoomsService'; -export { SessionService } from './SessionService'; +import { WebClient } from '@app/websocket'; +import type { IWebClientRequest } from '@app/websocket'; + +export { createWebClientResponse } from './response'; +export { createWebClientRequest } from './request'; + +/** + * UI-facing request surface. Each property is a lazy getter that resolves + * `WebClient.instance` at call time, so consumers can import this before the + * singleton is bootstrapped — it only needs to exist by the first actual call. + * + * Prefer this over importing `WebClient` directly: it keeps UI code free of + * transport-layer names and makes `@app/websocket` an internal detail of the + * `api` layer. + */ +export const request: IWebClientRequest = { + get authentication() { + return WebClient.instance.request.authentication; + }, + get session() { + return WebClient.instance.request.session; + }, + get rooms() { + return WebClient.instance.request.rooms; + }, + get admin() { + return WebClient.instance.request.admin; + }, + get moderator() { + return WebClient.instance.request.moderator; + }, +}; diff --git a/webclient/src/api/request/AdminRequestImpl.ts b/webclient/src/api/request/AdminRequestImpl.ts new file mode 100644 index 000000000..5cc21695b --- /dev/null +++ b/webclient/src/api/request/AdminRequestImpl.ts @@ -0,0 +1,20 @@ +import type { IAdminRequest } from '@app/websocket'; +import { AdminCommands } from '@app/websocket'; + +export class AdminRequestImpl implements IAdminRequest { + adjustMod(userName: string, shouldBeMod?: boolean, shouldBeJudge?: boolean): void { + AdminCommands.adjustMod(userName, shouldBeMod, shouldBeJudge); + } + + reloadConfig(): void { + AdminCommands.reloadConfig(); + } + + shutdownServer(reason: string, minutes: number): void { + AdminCommands.shutdownServer(reason, minutes); + } + + updateServerMessage(): void { + AdminCommands.updateServerMessage(); + } +} diff --git a/webclient/src/api/request/AuthenticationRequestImpl.ts b/webclient/src/api/request/AuthenticationRequestImpl.ts new file mode 100644 index 000000000..9157d736f --- /dev/null +++ b/webclient/src/api/request/AuthenticationRequestImpl.ts @@ -0,0 +1,37 @@ +import { App, Enriched } from '@app/types'; +import type { IAuthenticationRequest } from '@app/websocket'; +import { SessionCommands } from '@app/websocket'; + +export class AuthenticationRequestImpl implements IAuthenticationRequest { + login(options: Omit): void { + SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.LOGIN }); + } + + testConnection(options: Omit): void { + SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.TEST_CONNECTION }); + } + + register(options: Omit): void { + SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.REGISTER }); + } + + activateAccount(options: Omit): void { + SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.ACTIVATE_ACCOUNT }); + } + + resetPasswordRequest(options: Omit): void { + SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.PASSWORD_RESET_REQUEST }); + } + + resetPasswordChallenge(options: Omit): void { + SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.PASSWORD_RESET_CHALLENGE }); + } + + resetPassword(options: Omit): void { + SessionCommands.connect({ ...options, reason: App.WebSocketConnectReason.PASSWORD_RESET }); + } + + disconnect(): void { + SessionCommands.disconnect(); + } +} diff --git a/webclient/src/api/request/ModeratorRequestImpl.ts b/webclient/src/api/request/ModeratorRequestImpl.ts new file mode 100644 index 000000000..9f5c063da --- /dev/null +++ b/webclient/src/api/request/ModeratorRequestImpl.ts @@ -0,0 +1,37 @@ +import { Data } from '@app/types'; +import type { IModeratorRequest } from '@app/websocket'; +import { ModeratorCommands } from '@app/websocket'; + +export class ModeratorRequestImpl implements IModeratorRequest { + banFromServer( + minutes: number, + userName?: string, + address?: string, + reason?: string, + visibleReason?: string, + clientid?: string, + removeMessages?: number + ): void { + ModeratorCommands.banFromServer(minutes, userName, address, reason, visibleReason, clientid, removeMessages); + } + + getBanHistory(userName: string): void { + ModeratorCommands.getBanHistory(userName); + } + + getWarnHistory(userName: string): void { + ModeratorCommands.getWarnHistory(userName); + } + + getWarnList(modName: string, userName: string, userClientid: string): void { + ModeratorCommands.getWarnList(modName, userName, userClientid); + } + + viewLogHistory(filters: Data.ViewLogHistoryParams): void { + ModeratorCommands.viewLogHistory(filters); + } + + warnUser(userName: string, reason: string, clientid?: string, removeMessages?: number): void { + ModeratorCommands.warnUser(userName, reason, clientid, removeMessages); + } +} diff --git a/webclient/src/api/request/RoomsRequestImpl.ts b/webclient/src/api/request/RoomsRequestImpl.ts new file mode 100644 index 000000000..62b1f4e9b --- /dev/null +++ b/webclient/src/api/request/RoomsRequestImpl.ts @@ -0,0 +1,16 @@ +import type { IRoomsRequest } from '@app/websocket'; +import { RoomCommands, SessionCommands } from '@app/websocket'; + +export class RoomsRequestImpl implements IRoomsRequest { + joinRoom(roomId: number): void { + SessionCommands.joinRoom(roomId); + } + + leaveRoom(roomId: number): void { + RoomCommands.leaveRoom(roomId); + } + + roomSay(roomId: number, message: string): void { + RoomCommands.roomSay(roomId, message); + } +} diff --git a/webclient/src/api/request/SessionRequestImpl.ts b/webclient/src/api/request/SessionRequestImpl.ts new file mode 100644 index 000000000..0075b9418 --- /dev/null +++ b/webclient/src/api/request/SessionRequestImpl.ts @@ -0,0 +1,44 @@ +import type { ISessionRequest } from '@app/websocket'; +import { SessionCommands } from '@app/websocket'; + +export class SessionRequestImpl implements ISessionRequest { + addToBuddyList(userName: string): void { + SessionCommands.addToBuddyList(userName); + } + + removeFromBuddyList(userName: string): void { + SessionCommands.removeFromBuddyList(userName); + } + + addToIgnoreList(userName: string): void { + SessionCommands.addToIgnoreList(userName); + } + + removeFromIgnoreList(userName: string): void { + SessionCommands.removeFromIgnoreList(userName); + } + + changeAccountPassword(oldPassword: string, newPassword: string, hashedNewPassword?: string): void { + SessionCommands.accountPassword(oldPassword, newPassword, hashedNewPassword); + } + + changeAccountDetails(passwordCheck: string, realName?: string, email?: string, country?: string): void { + SessionCommands.accountEdit(passwordCheck, realName, email, country); + } + + changeAccountImage(image: Uint8Array): void { + SessionCommands.accountImage(image); + } + + sendDirectMessage(userName: string, message: string): void { + SessionCommands.message(userName, message); + } + + getUserInfo(userName: string): void { + SessionCommands.getUserInfo(userName); + } + + getUserGames(userName: string): void { + SessionCommands.getGamesOfUser(userName); + } +} diff --git a/webclient/src/api/request/index.ts b/webclient/src/api/request/index.ts new file mode 100644 index 000000000..85e55a739 --- /dev/null +++ b/webclient/src/api/request/index.ts @@ -0,0 +1,23 @@ +import type { IWebClientRequest } from '@app/websocket'; + +import { AuthenticationRequestImpl } from './AuthenticationRequestImpl'; +import { SessionRequestImpl } from './SessionRequestImpl'; +import { RoomsRequestImpl } from './RoomsRequestImpl'; +import { AdminRequestImpl } from './AdminRequestImpl'; +import { ModeratorRequestImpl } from './ModeratorRequestImpl'; + +export { AuthenticationRequestImpl } from './AuthenticationRequestImpl'; +export { SessionRequestImpl } from './SessionRequestImpl'; +export { RoomsRequestImpl } from './RoomsRequestImpl'; +export { AdminRequestImpl } from './AdminRequestImpl'; +export { ModeratorRequestImpl } from './ModeratorRequestImpl'; + +export function createWebClientRequest(): IWebClientRequest { + return { + authentication: new AuthenticationRequestImpl(), + session: new SessionRequestImpl(), + rooms: new RoomsRequestImpl(), + admin: new AdminRequestImpl(), + moderator: new ModeratorRequestImpl(), + }; +} diff --git a/webclient/src/api/response/AdminResponseImpl.ts b/webclient/src/api/response/AdminResponseImpl.ts new file mode 100644 index 000000000..a47b0f40b --- /dev/null +++ b/webclient/src/api/response/AdminResponseImpl.ts @@ -0,0 +1,20 @@ +import type { IAdminResponse } from '@app/websocket'; +import { ServerDispatch } from '@app/store'; + +export class AdminResponseImpl implements IAdminResponse { + adjustMod(userName: string, shouldBeMod: boolean, shouldBeJudge: boolean): void { + ServerDispatch.adjustMod(userName, shouldBeMod, shouldBeJudge); + } + + reloadConfig(): void { + ServerDispatch.reloadConfig(); + } + + shutdownServer(): void { + ServerDispatch.shutdownServer(); + } + + updateServerMessage(): void { + ServerDispatch.updateServerMessage(); + } +} diff --git a/webclient/src/api/response/GameResponseImpl.ts b/webclient/src/api/response/GameResponseImpl.ts new file mode 100644 index 000000000..cb8b9fef7 --- /dev/null +++ b/webclient/src/api/response/GameResponseImpl.ts @@ -0,0 +1,125 @@ +import { Data } from '@app/types'; +import type { IGameResponse } from '@app/websocket'; +import { GameDispatch } from '@app/store'; + +export class GameResponseImpl implements IGameResponse { + clearStore(): void { + GameDispatch.clearStore(); + } + + gameStateChanged(gameId: number, data: Data.Event_GameStateChanged): void { + GameDispatch.gameStateChanged(gameId, data); + } + + playerJoined(gameId: number, playerProperties: Data.ServerInfo_PlayerProperties): void { + GameDispatch.playerJoined(gameId, playerProperties); + } + + playerLeft(gameId: number, playerId: number, reason: number): void { + GameDispatch.playerLeft(gameId, playerId, reason); + } + + playerPropertiesChanged(gameId: number, playerId: number, properties: Data.ServerInfo_PlayerProperties): void { + GameDispatch.playerPropertiesChanged(gameId, playerId, properties); + } + + gameClosed(gameId: number): void { + GameDispatch.gameClosed(gameId); + } + + gameHostChanged(gameId: number, hostId: number): void { + GameDispatch.gameHostChanged(gameId, hostId); + } + + kicked(gameId: number): void { + GameDispatch.kicked(gameId); + } + + gameSay(gameId: number, playerId: number, message: string): void { + GameDispatch.gameSay(gameId, playerId, message); + } + + cardMoved(gameId: number, playerId: number, data: Data.Event_MoveCard): void { + GameDispatch.cardMoved(gameId, playerId, data); + } + + cardFlipped(gameId: number, playerId: number, data: Data.Event_FlipCard): void { + GameDispatch.cardFlipped(gameId, playerId, data); + } + + cardDestroyed(gameId: number, playerId: number, data: Data.Event_DestroyCard): void { + GameDispatch.cardDestroyed(gameId, playerId, data); + } + + cardAttached(gameId: number, playerId: number, data: Data.Event_AttachCard): void { + GameDispatch.cardAttached(gameId, playerId, data); + } + + tokenCreated(gameId: number, playerId: number, data: Data.Event_CreateToken): void { + GameDispatch.tokenCreated(gameId, playerId, data); + } + + cardAttrChanged(gameId: number, playerId: number, data: Data.Event_SetCardAttr): void { + GameDispatch.cardAttrChanged(gameId, playerId, data); + } + + cardCounterChanged(gameId: number, playerId: number, data: Data.Event_SetCardCounter): void { + GameDispatch.cardCounterChanged(gameId, playerId, data); + } + + arrowCreated(gameId: number, playerId: number, data: Data.Event_CreateArrow): void { + GameDispatch.arrowCreated(gameId, playerId, data); + } + + arrowDeleted(gameId: number, playerId: number, data: Data.Event_DeleteArrow): void { + GameDispatch.arrowDeleted(gameId, playerId, data); + } + + counterCreated(gameId: number, playerId: number, data: Data.Event_CreateCounter): void { + GameDispatch.counterCreated(gameId, playerId, data); + } + + counterSet(gameId: number, playerId: number, data: Data.Event_SetCounter): void { + GameDispatch.counterSet(gameId, playerId, data); + } + + counterDeleted(gameId: number, playerId: number, data: Data.Event_DelCounter): void { + GameDispatch.counterDeleted(gameId, playerId, data); + } + + cardsDrawn(gameId: number, playerId: number, data: Data.Event_DrawCards): void { + GameDispatch.cardsDrawn(gameId, playerId, data); + } + + cardsRevealed(gameId: number, playerId: number, data: Data.Event_RevealCards): void { + GameDispatch.cardsRevealed(gameId, playerId, data); + } + + zoneShuffled(gameId: number, playerId: number, data: Data.Event_Shuffle): void { + GameDispatch.zoneShuffled(gameId, playerId, data); + } + + dieRolled(gameId: number, playerId: number, data: Data.Event_RollDie): void { + GameDispatch.dieRolled(gameId, playerId, data); + } + + activePlayerSet(gameId: number, activePlayerId: number): void { + GameDispatch.activePlayerSet(gameId, activePlayerId); + } + + activePhaseSet(gameId: number, phase: number): void { + GameDispatch.activePhaseSet(gameId, phase); + } + + turnReversed(gameId: number, reversed: boolean): void { + GameDispatch.turnReversed(gameId, reversed); + } + + zoneDumped(gameId: number, playerId: number, data: Data.Event_DumpZone): void { + GameDispatch.zoneDumped(gameId, playerId, data); + } + + zonePropertiesChanged(gameId: number, playerId: number, data: Data.Event_ChangeZoneProperties): void { + GameDispatch.zonePropertiesChanged(gameId, playerId, data); + } +} diff --git a/webclient/src/api/response/ModeratorResponseImpl.ts b/webclient/src/api/response/ModeratorResponseImpl.ts new file mode 100644 index 000000000..c855152af --- /dev/null +++ b/webclient/src/api/response/ModeratorResponseImpl.ts @@ -0,0 +1,45 @@ +import { Data } from '@app/types'; +import type { IModeratorResponse } from '@app/websocket'; +import { ServerDispatch } from '@app/store'; + +export class ModeratorResponseImpl implements IModeratorResponse { + banFromServer(userName: string): void { + ServerDispatch.banFromServer(userName); + } + + banHistory(userName: string, banHistory: Data.ServerInfo_Ban[]): void { + ServerDispatch.banHistory(userName, banHistory); + } + + viewLogs(logs: Data.ServerInfo_ChatMessage[]): void { + ServerDispatch.viewLogs(logs); + } + + warnHistory(userName: string, warnHistory: Data.ServerInfo_Warning[]): void { + ServerDispatch.warnHistory(userName, warnHistory); + } + + warnListOptions(warnList: Data.Response_WarnList[]): void { + ServerDispatch.warnListOptions(warnList); + } + + warnUser(userName: string): void { + ServerDispatch.warnUser(userName); + } + + grantReplayAccess(replayId: number, moderatorName: string): void { + ServerDispatch.grantReplayAccess(replayId, moderatorName); + } + + forceActivateUser(usernameToActivate: string, moderatorName: string): void { + ServerDispatch.forceActivateUser(usernameToActivate, moderatorName); + } + + getAdminNotes(userName: string, notes: string): void { + ServerDispatch.getAdminNotes(userName, notes); + } + + updateAdminNotes(userName: string, notes: string): void { + ServerDispatch.updateAdminNotes(userName, notes); + } +} diff --git a/webclient/src/api/response/RoomResponseImpl.ts b/webclient/src/api/response/RoomResponseImpl.ts new file mode 100644 index 000000000..1a11bc4c5 --- /dev/null +++ b/webclient/src/api/response/RoomResponseImpl.ts @@ -0,0 +1,49 @@ +import { Data, Enriched } from '@app/types'; +import type { IRoomResponse } from '@app/websocket'; +import { RoomsDispatch } from '@app/store'; + +export class RoomResponseImpl implements IRoomResponse { + clearStore(): void { + RoomsDispatch.clearStore(); + } + + joinRoom(roomInfo: Data.ServerInfo_Room): void { + RoomsDispatch.joinRoom(roomInfo); + } + + leaveRoom(roomId: number): void { + RoomsDispatch.leaveRoom(roomId); + } + + updateRooms(rooms: Data.ServerInfo_Room[]): void { + RoomsDispatch.updateRooms(rooms); + } + + updateGames(roomId: number, gameList: Data.ServerInfo_Game[]): void { + RoomsDispatch.updateGames(roomId, gameList); + } + + addMessage(roomId: number, message: Enriched.Message): void { + RoomsDispatch.addMessage(roomId, message); + } + + userJoined(roomId: number, user: Data.ServerInfo_User): void { + RoomsDispatch.userJoined(roomId, user); + } + + userLeft(roomId: number, name: string): void { + RoomsDispatch.userLeft(roomId, name); + } + + removeMessages(roomId: number, name: string, amount: number): void { + RoomsDispatch.removeMessages(roomId, name, amount); + } + + gameCreated(roomId: number): void { + RoomsDispatch.gameCreated(roomId); + } + + joinedGame(roomId: number, gameId: number): void { + RoomsDispatch.joinedGame(roomId, gameId); + } +} diff --git a/webclient/src/api/response/SessionResponseImpl.ts b/webclient/src/api/response/SessionResponseImpl.ts new file mode 100644 index 000000000..98e1fbd70 --- /dev/null +++ b/webclient/src/api/response/SessionResponseImpl.ts @@ -0,0 +1,232 @@ +import { App, Data, Enriched } from '@app/types'; +import type { ISessionResponse } from '@app/websocket'; +import { GameDispatch, RoomsDispatch, ServerDispatch } from '@app/store'; + +export class SessionResponseImpl implements ISessionResponse { + initialized(): void { + ServerDispatch.initialized(); + } + + connectionAttempted(): void { + ServerDispatch.connectionAttempted(); + } + + clearStore(): void { + ServerDispatch.clearStore(); + } + + loginSuccessful(options: Enriched.LoginSuccessContext): void { + ServerDispatch.loginSuccessful(options); + } + + loginFailed(): void { + ServerDispatch.loginFailed(); + } + + connectionFailed(): void { + ServerDispatch.connectionFailed(); + } + + testConnectionSuccessful(): void { + ServerDispatch.testConnectionSuccessful(); + } + + testConnectionFailed(): void { + ServerDispatch.testConnectionFailed(); + } + + updateBuddyList(buddyList: Data.ServerInfo_User[]): void { + ServerDispatch.updateBuddyList(buddyList); + } + + addToBuddyList(user: Data.ServerInfo_User): void { + ServerDispatch.addToBuddyList(user); + } + + removeFromBuddyList(userName: string): void { + ServerDispatch.removeFromBuddyList(userName); + } + + updateIgnoreList(ignoreList: Data.ServerInfo_User[]): void { + ServerDispatch.updateIgnoreList(ignoreList); + } + + addToIgnoreList(user: Data.ServerInfo_User): void { + ServerDispatch.addToIgnoreList(user); + } + + removeFromIgnoreList(userName: string): void { + ServerDispatch.removeFromIgnoreList(userName); + } + + updateInfo(name: string, version: string): void { + ServerDispatch.updateInfo(name, version); + } + + updateStatus(state: App.StatusEnum, description: string): void { + if (state === App.StatusEnum.DISCONNECTED) { + GameDispatch.clearStore(); + RoomsDispatch.clearStore(); + ServerDispatch.clearStore(); + } + ServerDispatch.updateStatus(state, description); + } + + updateUser(user: Data.ServerInfo_User): void { + ServerDispatch.updateUser(user); + } + + updateUsers(users: Data.ServerInfo_User[]): void { + ServerDispatch.updateUsers(users); + } + + userJoined(user: Data.ServerInfo_User): void { + ServerDispatch.userJoined(user); + } + + userLeft(userName: string): void { + ServerDispatch.userLeft(userName); + } + + serverMessage(message: string): void { + ServerDispatch.serverMessage(message); + } + + accountAwaitingActivation(options: Enriched.PendingActivationContext): void { + ServerDispatch.accountAwaitingActivation(options); + } + + accountActivationSuccess(): void { + ServerDispatch.accountActivationSuccess(); + } + + accountActivationFailed(): void { + ServerDispatch.accountActivationFailed(); + } + + registrationRequiresEmail(): void { + ServerDispatch.registrationRequiresEmail(); + } + + registrationSuccess(): void { + ServerDispatch.registrationSuccess(); + } + + registrationFailed(reason: string, endTime?: number): void { + ServerDispatch.registrationFailed(reason, endTime); + } + + registrationEmailError(error: string): void { + ServerDispatch.registrationEmailError(error); + } + + registrationPasswordError(error: string): void { + ServerDispatch.registrationPasswordError(error); + } + + registrationUserNameError(error: string): void { + ServerDispatch.registrationUserNameError(error); + } + + resetPasswordChallenge(): void { + ServerDispatch.resetPasswordChallenge(); + } + + resetPassword(): void { + ServerDispatch.resetPassword(); + } + + resetPasswordSuccess(): void { + ServerDispatch.resetPasswordSuccess(); + } + + resetPasswordFailed(): void { + ServerDispatch.resetPasswordFailed(); + } + + accountPasswordChange(): void { + ServerDispatch.accountPasswordChange(); + } + + accountEditChanged(realName?: string, email?: string, country?: string): void { + ServerDispatch.accountEditChanged({ realName, email, country }); + } + + accountImageChanged(avatarBmp: Uint8Array): void { + ServerDispatch.accountImageChanged({ avatarBmp }); + } + + getUserInfo(userInfo: Data.ServerInfo_User): void { + ServerDispatch.getUserInfo(userInfo); + } + + getGamesOfUser(userName: string, response: Data.Response_GetGamesOfUser): void { + ServerDispatch.gamesOfUser(userName, response); + } + + gameJoined(gameJoinedData: Data.Event_GameJoined): void { + GameDispatch.gameJoined(gameJoinedData); + } + + notifyUser(notification: Data.Event_NotifyUser): void { + ServerDispatch.notifyUser(notification); + } + + playerPropertiesChanged(gameId: number, playerId: number, payload: Data.Event_PlayerPropertiesChanged): void { + if (payload.playerProperties) { + GameDispatch.playerPropertiesChanged(gameId, playerId, payload.playerProperties); + } + } + + serverShutdown(data: Data.Event_ServerShutdown): void { + ServerDispatch.serverShutdown(data); + } + + userMessage(messageData: Data.Event_UserMessage): void { + ServerDispatch.userMessage(messageData); + } + + addToList(list: string, userName: string): void { + ServerDispatch.addToList(list, userName); + } + + removeFromList(list: string, userName: string): void { + ServerDispatch.removeFromList(list, userName); + } + + deleteServerDeck(deckId: number): void { + ServerDispatch.deckDelete(deckId); + } + + updateServerDecks(deckList: Data.Response_DeckList): void { + ServerDispatch.backendDecks(deckList); + } + + uploadServerDeck(path: string, treeItem: Data.ServerInfo_DeckStorage_TreeItem): void { + ServerDispatch.deckUpload(path, treeItem); + } + + createServerDeckDir(path: string, dirName: string): void { + ServerDispatch.deckNewDir(path, dirName); + } + + deleteServerDeckDir(path: string): void { + ServerDispatch.deckDelDir(path); + } + + replayList(matchList: Data.ServerInfo_ReplayMatch[]): void { + ServerDispatch.replayList(matchList); + } + + replayAdded(matchInfo: Data.ServerInfo_ReplayMatch): void { + ServerDispatch.replayAdded(matchInfo); + } + + replayModifyMatch(gameId: number, doNotHide: boolean): void { + ServerDispatch.replayModifyMatch(gameId, doNotHide); + } + + replayDeleteMatch(gameId: number): void { + ServerDispatch.replayDeleteMatch(gameId); + } +} diff --git a/webclient/src/api/response/index.ts b/webclient/src/api/response/index.ts new file mode 100644 index 000000000..21941a1b0 --- /dev/null +++ b/webclient/src/api/response/index.ts @@ -0,0 +1,23 @@ +import type { IWebClientResponse } from '@app/websocket'; + +import { SessionResponseImpl } from './SessionResponseImpl'; +import { RoomResponseImpl } from './RoomResponseImpl'; +import { GameResponseImpl } from './GameResponseImpl'; +import { AdminResponseImpl } from './AdminResponseImpl'; +import { ModeratorResponseImpl } from './ModeratorResponseImpl'; + +export { SessionResponseImpl } from './SessionResponseImpl'; +export { RoomResponseImpl } from './RoomResponseImpl'; +export { GameResponseImpl } from './GameResponseImpl'; +export { AdminResponseImpl } from './AdminResponseImpl'; +export { ModeratorResponseImpl } from './ModeratorResponseImpl'; + +export function createWebClientResponse(): IWebClientResponse { + return { + session: new SessionResponseImpl(), + room: new RoomResponseImpl(), + game: new GameResponseImpl(), + admin: new AdminResponseImpl(), + moderator: new ModeratorResponseImpl(), + }; +} diff --git a/webclient/src/components/Guard/AuthGuard.tsx b/webclient/src/components/Guard/AuthGuard.tsx index bf1ff1876..4bbb8e1d5 100644 --- a/webclient/src/components/Guard/AuthGuard.tsx +++ b/webclient/src/components/Guard/AuthGuard.tsx @@ -1,14 +1,12 @@ import React from 'react'; import { Navigate } from 'react-router-dom'; -import { ServerSelectors } from '@app/store'; +import { ServerSelectors, useAppSelector } from '@app/store'; import { App } from '@app/types'; -import { useAppSelector } from '@app/store'; -import { AuthenticationService } from '@app/api'; const AuthGuard = () => { - const state = useAppSelector(s => ServerSelectors.getState(s)); - return !AuthenticationService.isConnected(state) + const isConnected = useAppSelector(ServerSelectors.getIsConnected); + return !isConnected ? :
; }; diff --git a/webclient/src/components/Guard/ModGuard.tsx b/webclient/src/components/Guard/ModGuard.tsx index 18b68adf1..96844b436 100644 --- a/webclient/src/components/Guard/ModGuard.tsx +++ b/webclient/src/components/Guard/ModGuard.tsx @@ -1,14 +1,12 @@ import React from 'react'; import { Navigate } from 'react-router-dom'; -import { ServerSelectors } from '@app/store'; -import { AuthenticationService } from '@app/api'; +import { ServerSelectors, useAppSelector } from '@app/store'; import { App } from '@app/types'; -import { useAppSelector } from '@app/store'; const ModGuard = () => { - const user = useAppSelector(state => ServerSelectors.getUser(state)); - return !AuthenticationService.isModerator(user) + const isModerator = useAppSelector(ServerSelectors.getIsUserModerator); + return !isModerator ? : <>; }; diff --git a/webclient/src/components/KnownHosts/KnownHosts.tsx b/webclient/src/components/KnownHosts/KnownHosts.tsx index 096a8bece..3efdf67ac 100644 --- a/webclient/src/components/KnownHosts/KnownHosts.tsx +++ b/webclient/src/components/KnownHosts/KnownHosts.tsx @@ -13,7 +13,7 @@ import AddIcon from '@mui/icons-material/Add'; import EditRoundedIcon from '@mui/icons-material/Edit'; import ErrorOutlinedIcon from '@mui/icons-material/ErrorOutlined'; -import { AuthenticationService } from '@app/api'; +import { request } from '@app/api'; import { KnownHostDialog } from '@app/dialogs'; import { useReduxEffect } from '@app/hooks'; import { HostDTO } from '@app/services'; @@ -197,7 +197,7 @@ const KnownHosts = (props) => { setTestingConnection(TestConnection.TESTING); const options = { ...App.getHostPort(hostsState.selectedHost) }; - AuthenticationService.testConnection(options); + request.authentication.testConnection(options); } return ( diff --git a/webclient/src/components/UserDisplay/UserDisplay.tsx b/webclient/src/components/UserDisplay/UserDisplay.tsx index a19bc9912..6e38fab5c 100644 --- a/webclient/src/components/UserDisplay/UserDisplay.tsx +++ b/webclient/src/components/UserDisplay/UserDisplay.tsx @@ -6,7 +6,7 @@ import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import { Images } from '@app/images'; -import { SessionService } from '@app/api'; +import { request } from '@app/api'; import { ServerSelectors } from '@app/store'; import { App, Data } from '@app/types'; import { useAppSelector } from '@app/store'; @@ -28,23 +28,23 @@ const UserDisplay = ({ user }: UserDisplayProps) => { const handleClose = () => setPosition(null); - const isABuddy = buddyList.filter(u => u.name === user.name).length; - const isIgnored = ignoreList.filter(u => u.name === user.name).length; + const isABuddy = Boolean(buddyList[user.name]); + const isIgnored = Boolean(ignoreList[user.name]); const onAddBuddy = () => { - SessionService.addToBuddyList(user.name); + request.session.addToBuddyList(user.name); handleClose(); }; const onRemoveBuddy = () => { - SessionService.removeFromBuddyList(user.name); + request.session.removeFromBuddyList(user.name); handleClose(); }; const onAddIgnore = () => { - SessionService.addToIgnoreList(user.name); + request.session.addToIgnoreList(user.name); handleClose(); }; const onRemoveIgnore = () => { - SessionService.removeFromIgnoreList(user.name); + request.session.removeFromIgnoreList(user.name); handleClose(); }; diff --git a/webclient/src/containers/Account/Account.tsx b/webclient/src/containers/Account/Account.tsx index 8e0088d06..05d0b1161 100644 --- a/webclient/src/containers/Account/Account.tsx +++ b/webclient/src/containers/Account/Account.tsx @@ -7,7 +7,7 @@ import ListItemButton from '@mui/material/ListItemButton'; import Paper from '@mui/material/Paper'; import { UserDisplay, VirtualList, AuthGuard, LanguageDropdown } from '@app/components'; -import { AuthenticationService, SessionService } from '@app/api'; +import { request } from '@app/api'; import { ServerSelectors } from '@app/store'; import Layout from '../Layout/Layout'; import { useAppSelector } from '@app/store'; @@ -18,8 +18,8 @@ import AddToIgnore from './AddToIgnore'; import './Account.css'; const Account = () => { - const buddyList = useAppSelector(state => ServerSelectors.getBuddyList(state)); - const ignoreList = useAppSelector(state => ServerSelectors.getIgnoreList(state)); + const buddyList = useAppSelector(state => ServerSelectors.getSortedBuddyList(state)); + const ignoreList = useAppSelector(state => ServerSelectors.getSortedIgnoreList(state)); const serverName = useAppSelector(state => ServerSelectors.getName(state)); const serverVersion = useAppSelector(state => ServerSelectors.getVersion(state)); const user = useAppSelector(state => ServerSelectors.getUser(state)); @@ -29,11 +29,11 @@ const Account = () => { const { t } = useTranslation(); const handleAddToBuddies = ({ userName }) => { - SessionService.addToBuddyList(userName); + request.session.addToBuddyList(userName); }; const handleAddToIgnore = ({ userName }) => { - SessionService.addToIgnoreList(userName); + request.session.addToIgnoreList(userName); }; return ( @@ -91,7 +91,13 @@ const Account = () => {

Server Name: {serverName}

Server Version: {serverVersion}

- +
diff --git a/webclient/src/containers/Layout/LeftNav.tsx b/webclient/src/containers/Layout/LeftNav.tsx index 0730011d5..b9b2d377c 100644 --- a/webclient/src/containers/Layout/LeftNav.tsx +++ b/webclient/src/containers/Layout/LeftNav.tsx @@ -8,7 +8,7 @@ import CloseIcon from '@mui/icons-material/Close'; import MailOutlineRoundedIcon from '@mui/icons-material/MailOutlineRounded'; import MenuRoundedIcon from '@mui/icons-material/MenuRounded'; -import { AuthenticationService, RoomsService } from '@app/api'; +import { request } from '@app/api'; import { CardImportDialog } from '@app/dialogs'; import { Images } from '@app/images'; import { RoomsSelectors, ServerSelectors } from '@app/store'; @@ -25,8 +25,8 @@ interface LeftNavState { const LeftNav = () => { const joinedRooms = useAppSelector(state => RoomsSelectors.getJoinedRooms(state)); - const serverState = useAppSelector(state => ServerSelectors.getState(state)); - const user = useAppSelector(state => ServerSelectors.getUser(state)); + const isConnected = useAppSelector(ServerSelectors.getIsConnected); + const isModerator = useAppSelector(ServerSelectors.getIsUserModerator); const navigate = useNavigate(); const [state, setState] = useState({ anchorEl: null, @@ -40,7 +40,7 @@ const LeftNav = () => { 'Replays', ]; - if (user && AuthenticationService.isModerator(user)) { + if (isModerator) { options = [ ...options, 'Administration', @@ -49,7 +49,7 @@ const LeftNav = () => { } setState(s => ({ ...s, options })); - }, [user]); + }, [isModerator]); const handleMenuOpen = (event) => { setState(s => ({ ...s, anchorEl: event.target })); @@ -66,7 +66,7 @@ const LeftNav = () => { const leaveRoom = (event, roomId) => { event.preventDefault(); - RoomsService.leaveRoom(roomId); + request.rooms.leaveRoom(roomId); }; const openImportCardWizard = () => { @@ -85,11 +85,11 @@ const LeftNav = () => { logo - { AuthenticationService.isConnected(serverState) && ( + { isConnected && ( ) }
- { AuthenticationService.isConnected(serverState) && ( + { isConnected && (