diff --git a/.env.example b/.env.example index dbee997ff..0db0e2f87 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,7 @@ SESSION_SECRET=secret // Auth https://discord.com/developers DISCORD_CLIENT_ID= DISCORD_CLIENT_SECRET= +DISCORD_ADMIN_WEBHOOK_URL= // Patreon integration to sync supporter status https://www.patreon.com/portal/registration/register-clients PATREON_ACCESS_TOKEN= @@ -42,5 +43,3 @@ VITE_SHOW_LUTI_NAV_ITEM=false VITE_VAPID_PUBLIC_KEY= VAPID_PRIVATE_KEY= VAPID_EMAIL= - -PUBLIC_API_TOKENS=secret,secret2 diff --git a/app/db/tables.ts b/app/db/tables.ts index 5b3f15d53..eeebe2673 100644 --- a/app/db/tables.ts +++ b/app/db/tables.ts @@ -1012,10 +1012,13 @@ export interface UserFriendCode { createdAt: GeneratedAlways; } +export type ApiTokenType = "read" | "write"; + export interface ApiToken { id: GeneratedAlways; userId: number; token: string; + type: Generated; createdAt: GeneratedAlways; } diff --git a/app/features/admin/actions/admin.server.ts b/app/features/admin/actions/admin.server.ts index ed98cc0e3..02bc583d6 100644 --- a/app/features/admin/actions/admin.server.ts +++ b/app/features/admin/actions/admin.server.ts @@ -14,6 +14,7 @@ import { import { errorIsSqliteForeignKeyConstraintFailure } from "~/utils/sql"; import { assertUnreachable } from "~/utils/types"; import { _action, actualNumber, friendCode } from "~/utils/zod"; +import * as AdminNotifications from "../core/admin-notifications.server"; import { plusTiersFromVotingAndLeaderboard } from "../core/plus-tier.server"; export const action = async ({ request }: ActionFunctionArgs) => { @@ -167,6 +168,14 @@ export const action = async ({ request }: ActionFunctionArgs) => { message = "API access granted"; break; } + case "TEST_ADMIN_NOTIFICATION": { + requireRole(user, "ADMIN"); + + await AdminNotifications.send("Test notification from admin panel"); + + message = "Test notification sent"; + break; + } default: { assertUnreachable(data); } @@ -229,4 +238,7 @@ export const adminActionSchema = z.union([ _action: _action("API_ACCESS"), user: z.preprocess(actualNumber, z.number().positive()), }), + z.object({ + _action: _action("TEST_ADMIN_NOTIFICATION"), + }), ]); diff --git a/app/features/admin/core/admin-notifications.server.ts b/app/features/admin/core/admin-notifications.server.ts new file mode 100644 index 000000000..4f96a65db --- /dev/null +++ b/app/features/admin/core/admin-notifications.server.ts @@ -0,0 +1,29 @@ +import { logger } from "~/utils/logger"; + +const DISCORD_ADMIN_WEBHOOK_URL = process.env.DISCORD_ADMIN_WEBHOOK_URL; + +if (!DISCORD_ADMIN_WEBHOOK_URL) { + logger.info( + "DISCORD_ADMIN_WEBHOOK_URL not set, admin notifications disabled", + ); +} + +export async function send(message: string): Promise { + if (!DISCORD_ADMIN_WEBHOOK_URL) { + return; + } + + try { + const response = await fetch(DISCORD_ADMIN_WEBHOOK_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ content: message }), + }); + + if (!response.ok) { + logger.error(`Failed to send admin notification: ${response.status}`); + } + } catch (error) { + logger.error("Failed to send admin notification", error); + } +} diff --git a/app/features/admin/routes/admin.tsx b/app/features/admin/routes/admin.tsx index 533872b41..3cd00ebcf 100644 --- a/app/features/admin/routes/admin.tsx +++ b/app/features/admin/routes/admin.tsx @@ -112,7 +112,8 @@ function AdminActions() { return (
- {DANGEROUS_CAN_ACCESS_DEV_CONTROLS && } + {DANGEROUS_CAN_ACCESS_DEV_CONTROLS ? : null} + {DANGEROUS_CAN_ACCESS_DEV_CONTROLS ? : null} {DANGEROUS_CAN_ACCESS_DEV_CONTROLS || isAdmin ? : null} {isStaff ? : null} @@ -459,4 +460,21 @@ function Seed() { ); } +function TestAdminNotification() { + const fetcher = useFetcher(); + + return ( + +

Test Admin Notification

+ + Send Test + +
+ ); +} + export const ErrorBoundary = Catcher; diff --git a/app/features/api-public/api-public-utils.server.ts b/app/features/api-public/api-public-utils.server.ts index 71facd609..13b92d734 100644 --- a/app/features/api-public/api-public-utils.server.ts +++ b/app/features/api-public/api-public-utils.server.ts @@ -1,9 +1,16 @@ +import type { ApiTokenType } from "~/db/tables"; import * as ApiRepository from "~/features/api/ApiRepository.server"; async function loadApiTokensCache() { - const envTokens = process.env.PUBLIC_API_TOKENS?.split(",") ?? []; const dbTokens = await ApiRepository.allApiTokens(); - return new Set([...envTokens, ...dbTokens]); + + const tokenMap = new Map(); + + for (const { token, type } of dbTokens) { + tokenMap.set(token, type); + } + + return tokenMap; } let apiTokens = await loadApiTokensCache(); @@ -12,12 +19,16 @@ export async function refreshApiTokensCache() { apiTokens = await loadApiTokensCache(); } -export function requireBearerAuth(req: Request) { +function extractToken(req: Request) { const authHeader = req.headers.get("Authorization"); if (!authHeader) { throw new Response("Missing Authorization header", { status: 401 }); } - const token = authHeader.replace("Bearer ", ""); + return authHeader.replace("Bearer ", ""); +} + +export function requireBearerAuth(req: Request) { + const token = extractToken(req); if (!apiTokens.has(token)) { throw new Response("Invalid token", { status: 401 }); } diff --git a/app/features/api-public/routes/tournament.$id.teams.ts b/app/features/api-public/routes/tournament.$id.teams.ts index 0aef9750f..69533a16c 100644 --- a/app/features/api-public/routes/tournament.$id.teams.ts +++ b/app/features/api-public/routes/tournament.$id.teams.ts @@ -89,8 +89,8 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => { "TournamentTeamMember.inGameName", "TournamentTeamMember.isOwner", "TournamentTeamMember.createdAt", - "RankedSeedingSkill.mu as rankedOrdinal", - "UnrankedSeedingSkill.mu as unrankedOrdinal", + "RankedSeedingSkill.ordinal as rankedOrdinal", + "UnrankedSeedingSkill.ordinal as unrankedOrdinal", ]) .whereRef( "TournamentTeamMember.tournamentTeamId", diff --git a/app/features/api/ApiRepository.server.test.ts b/app/features/api/ApiRepository.server.test.ts index ea57d064b..cc4f78b4d 100644 --- a/app/features/api/ApiRepository.server.test.ts +++ b/app/features/api/ApiRepository.server.test.ts @@ -12,15 +12,15 @@ describe("findTokenByUserId", () => { }); test("returns undefined when user has no token", async () => { - const result = await ApiRepository.findTokenByUserId(1); + const result = await ApiRepository.findTokenByUserId(1, "read"); expect(result).toBeUndefined(); }); test("finds existing token for user", async () => { - await ApiRepository.generateToken(1); + await ApiRepository.generateToken(1, "read"); - const result = await ApiRepository.findTokenByUserId(1); + const result = await ApiRepository.findTokenByUserId(1, "read"); expect(result).toBeDefined(); expect(result?.userId).toBe(1); @@ -28,16 +28,30 @@ describe("findTokenByUserId", () => { }); test("returns correct token for specific user", async () => { - const token1 = await ApiRepository.generateToken(1); - const token2 = await ApiRepository.generateToken(2); + const token1 = await ApiRepository.generateToken(1, "read"); + const token2 = await ApiRepository.generateToken(2, "read"); - const result1 = await ApiRepository.findTokenByUserId(1); - const result2 = await ApiRepository.findTokenByUserId(2); + const result1 = await ApiRepository.findTokenByUserId(1, "read"); + const result2 = await ApiRepository.findTokenByUserId(2, "read"); expect(result1?.token).toBe(token1.token); expect(result2?.token).toBe(token2.token); expect(result1?.token).not.toBe(result2?.token); }); + + test("finds correct token by type", async () => { + await ApiRepository.generateToken(1, "read"); + await ApiRepository.generateToken(1, "write"); + + const readResult = await ApiRepository.findTokenByUserId(1, "read"); + const writeResult = await ApiRepository.findTokenByUserId(1, "write"); + + expect(readResult).toBeDefined(); + expect(writeResult).toBeDefined(); + expect(readResult?.token).not.toBe(writeResult?.token); + expect(readResult?.type).toBe("read"); + expect(writeResult?.type).toBe("write"); + }); }); describe("generateToken", () => { @@ -50,7 +64,7 @@ describe("generateToken", () => { }); test("creates new token for user", async () => { - const result = await ApiRepository.generateToken(1); + const result = await ApiRepository.generateToken(1, "read"); expect(result.token).toBeDefined(); expect(typeof result.token).toBe("string"); @@ -58,19 +72,19 @@ describe("generateToken", () => { }); test("deletes existing token before creating new one", async () => { - const firstToken = await ApiRepository.generateToken(1); - const secondToken = await ApiRepository.generateToken(1); + const firstToken = await ApiRepository.generateToken(1, "read"); + const secondToken = await ApiRepository.generateToken(1, "read"); expect(firstToken.token).not.toBe(secondToken.token); - const storedToken = await ApiRepository.findTokenByUserId(1); + const storedToken = await ApiRepository.findTokenByUserId(1, "read"); expect(storedToken?.token).toBe(secondToken.token); }); test("generates unique tokens for different users", async () => { - const token1 = await ApiRepository.generateToken(1); - const token2 = await ApiRepository.generateToken(2); - const token3 = await ApiRepository.generateToken(3); + const token1 = await ApiRepository.generateToken(1, "read"); + const token2 = await ApiRepository.generateToken(2, "read"); + const token3 = await ApiRepository.generateToken(3, "read"); expect(token1.token).not.toBe(token2.token); expect(token1.token).not.toBe(token3.token); @@ -78,17 +92,28 @@ describe("generateToken", () => { }); test("replaces only the specific user's token", async () => { - const user1FirstToken = await ApiRepository.generateToken(1); - const user2Token = await ApiRepository.generateToken(2); - const user1SecondToken = await ApiRepository.generateToken(1); + const user1FirstToken = await ApiRepository.generateToken(1, "read"); + const user2Token = await ApiRepository.generateToken(2, "read"); + const user1SecondToken = await ApiRepository.generateToken(1, "read"); - const result1 = await ApiRepository.findTokenByUserId(1); - const result2 = await ApiRepository.findTokenByUserId(2); + const result1 = await ApiRepository.findTokenByUserId(1, "read"); + const result2 = await ApiRepository.findTokenByUserId(2, "read"); expect(result1?.token).toBe(user1SecondToken.token); expect(result1?.token).not.toBe(user1FirstToken.token); expect(result2?.token).toBe(user2Token.token); }); + + test("allows same user to have both read and write tokens", async () => { + const readToken = await ApiRepository.generateToken(1, "read"); + const writeToken = await ApiRepository.generateToken(1, "write"); + + const readResult = await ApiRepository.findTokenByUserId(1, "read"); + const writeResult = await ApiRepository.findTokenByUserId(1, "write"); + + expect(readResult?.token).toBe(readToken.token); + expect(writeResult?.token).toBe(writeToken.token); + }); }); describe("allApiTokens", () => { @@ -106,12 +131,17 @@ describe("allApiTokens", () => { expect(result).toEqual([]); }); - test("returns array of token strings", async () => { - await ApiRepository.generateToken(1); + test("returns array of token objects with type", async () => { + await ApiRepository.generateToken(1, "read"); const result = await ApiRepository.allApiTokens(); expect(Array.isArray(result)).toBe(true); - expect(result.every((token) => typeof token === "string")).toBe(true); + expect( + result.every( + (item) => + typeof item.token === "string" && typeof item.type === "string", + ), + ).toBe(true); }); }); diff --git a/app/features/api/ApiRepository.server.ts b/app/features/api/ApiRepository.server.ts index 31ef1a1c5..3756be348 100644 --- a/app/features/api/ApiRepository.server.ts +++ b/app/features/api/ApiRepository.server.ts @@ -1,48 +1,43 @@ import { nanoid } from "nanoid"; import { db } from "~/db/sql"; +import type { ApiTokenType } from "~/db/tables"; const API_TOKEN_LENGTH = 20; -/** - * Finds an API token for the given user ID. - * @returns API token record if found, undefined otherwise - */ -export function findTokenByUserId(userId: number) { +/** Finds an API token for the given user ID and type. */ +export function findTokenByUserId(userId: number, type: ApiTokenType) { return db .selectFrom("ApiToken") .selectAll() .where("userId", "=", userId) + .where("type", "=", type) .executeTakeFirst(); } -/** - * Generates a new API token for the given user. - * Deletes any existing token for the user before creating a new one. - * @returns Object containing the newly generated token - */ -export function generateToken(userId: number) { +/** Generates a new API token for the given user. Deletes any existing token of the same type before creating a new one. */ +export function generateToken(userId: number, type: ApiTokenType) { const token = nanoid(API_TOKEN_LENGTH); return db.transaction().execute(async (trx) => { - await trx.deleteFrom("ApiToken").where("userId", "=", userId).execute(); + await trx + .deleteFrom("ApiToken") + .where("userId", "=", userId) + .where("type", "=", type) + .execute(); return trx .insertInto("ApiToken") .values({ userId, token, + type, }) .returning("token") .executeTakeFirstOrThrow(); }); } -/** - * Retrieves all valid API tokens from users with API access. - * Includes tokens from users with the isApiAccesser flag enabled (includes supporters tier 2+), - * or users who are ADMIN, ORGANIZER, or STREAMER members of established tournament organizations. - * @returns Array of valid API token strings - */ +/** Retrieves all valid API tokens and their types from users with API access. */ export async function allApiTokens() { const tokens = await db .selectFrom("ApiToken") @@ -57,7 +52,7 @@ export async function allApiTokens() { "TournamentOrganization.id", "TournamentOrganizationMember.organizationId", ) - .select("ApiToken.token") + .select(["ApiToken.token", "ApiToken.type"]) // NOTE: permissions logic also exists in checkUserHasApiAccess function .where((eb) => eb.or([ @@ -77,5 +72,5 @@ export async function allApiTokens() { .groupBy("ApiToken.token") .execute(); - return tokens.map((row) => row.token); + return tokens.map((row) => ({ token: row.token, type: row.type })); } diff --git a/app/features/api/actions/api.server.ts b/app/features/api/actions/api.server.ts index 521e0a509..1d74ee5e7 100644 --- a/app/features/api/actions/api.server.ts +++ b/app/features/api/actions/api.server.ts @@ -3,12 +3,11 @@ import { z } from "zod"; import { refreshApiTokensCache } from "~/features/api-public/api-public-utils.server"; import { requireUser } from "~/features/auth/core/user.server"; import { parseRequestPayload, successToast } from "~/utils/remix.server"; -import { _action } from "~/utils/zod"; import * as ApiRepository from "../ApiRepository.server"; import { checkUserHasApiAccess } from "../core/perms"; const apiActionSchema = z.object({ - _action: _action("GENERATE"), + _action: z.enum(["GENERATE_READ", "GENERATE_WRITE"]), }); export const action = async ({ request }: ActionFunctionArgs) => { @@ -24,12 +23,16 @@ export const action = async ({ request }: ActionFunctionArgs) => { } switch (data._action) { - case "GENERATE": { - await ApiRepository.generateToken(user.id); - + case "GENERATE_READ": { + await ApiRepository.generateToken(user.id, "read"); await refreshApiTokensCache(); - - successToast("API token generated successfully"); + successToast("Read token generated successfully"); + break; + } + case "GENERATE_WRITE": { + await ApiRepository.generateToken(user.id, "write"); + await refreshApiTokensCache(); + successToast("Write token generated successfully"); break; } default: { diff --git a/app/features/api/core/perms.test.ts b/app/features/api/core/perms.test.ts index bc8680a65..4f1ec8f82 100644 --- a/app/features/api/core/perms.test.ts +++ b/app/features/api/core/perms.test.ts @@ -19,7 +19,7 @@ describe("Permission logic consistency between allApiTokens and checkUserHasApiA test("both functions grant access for isApiAccesser flag", async () => { await AdminRepository.makeApiAccesserByUserId(1); - await ApiRepository.generateToken(1); + await ApiRepository.generateToken(1, "read"); const tokens = await ApiRepository.allApiTokens(); const user = await UserRepository.findLeanById(1); @@ -32,7 +32,7 @@ describe("Permission logic consistency between allApiTokens and checkUserHasApiA test("both functions grant access for isTournamentOrganizer flag", async () => { await AdminRepository.makeTournamentOrganizerByUserId(1); - await ApiRepository.generateToken(1); + await ApiRepository.generateToken(1, "read"); const tokens = await ApiRepository.allApiTokens(); const user = await UserRepository.findLeanById(1); @@ -50,7 +50,7 @@ describe("Permission logic consistency between allApiTokens and checkUserHasApiA patronTill: add(new Date(), { months: 3 }), }); - await ApiRepository.generateToken(1); + await ApiRepository.generateToken(1, "read"); const tokens = await ApiRepository.allApiTokens(); const user = await UserRepository.findLeanById(1); @@ -68,7 +68,7 @@ describe("Permission logic consistency between allApiTokens and checkUserHasApiA patronTill: add(new Date(), { months: 3 }), }); - await ApiRepository.generateToken(1); + await ApiRepository.generateToken(1, "read"); const tokens = await ApiRepository.allApiTokens(); const user = await UserRepository.findLeanById(1); @@ -101,7 +101,7 @@ describe("Permission logic consistency between allApiTokens and checkUserHasApiA badges: [], }); - await ApiRepository.generateToken(userId); + await ApiRepository.generateToken(userId, "read"); const tokens = await ApiRepository.allApiTokens(); const user = await UserRepository.findLeanById(userId); @@ -131,7 +131,7 @@ describe("Permission logic consistency between allApiTokens and checkUserHasApiA badges: [], }); - await ApiRepository.generateToken(2); + await ApiRepository.generateToken(2, "read"); const tokens = await ApiRepository.allApiTokens(); const user = await UserRepository.findLeanById(2); @@ -158,7 +158,7 @@ describe("Permission logic consistency between allApiTokens and checkUserHasApiA badges: [], }); - await ApiRepository.generateToken(2); + await ApiRepository.generateToken(2, "read"); const tokens = await ApiRepository.allApiTokens(); const user = await UserRepository.findLeanById(2); diff --git a/app/features/api/loaders/api.server.ts b/app/features/api/loaders/api.server.ts index c498142df..ef8a33e58 100644 --- a/app/features/api/loaders/api.server.ts +++ b/app/features/api/loaders/api.server.ts @@ -10,14 +10,19 @@ export const loader = async () => { if (!hasApiAccess) { return { hasAccess: false, - apiToken: null, + readToken: null, + writeToken: null, }; } - const apiToken = await ApiRepository.findTokenByUserId(user.id); + const [readToken, writeToken] = await Promise.all([ + ApiRepository.findTokenByUserId(user.id, "read"), + ApiRepository.findTokenByUserId(user.id, "write"), + ]); return { hasAccess: true, - apiToken: apiToken?.token ?? null, + readToken: readToken?.token ?? null, + writeToken: writeToken?.token ?? null, }; }; diff --git a/app/features/api/routes/api.tsx b/app/features/api/routes/api.tsx index e5ac3a193..458b630a2 100644 --- a/app/features/api/routes/api.tsx +++ b/app/features/api/routes/api.tsx @@ -44,12 +44,55 @@ export default function ApiPage() {
{t("common:api.noAccess")}
- ) : data.apiToken ? ( + ) : ( +
+ + +
+ )} + + ); +} + +function TokenSection({ + token, + tokenType, + generateAction, +}: { + token: string | null; + tokenType: "read" | "write"; + generateAction: string; +}) { + const { t } = useTranslation(["common"]); + + const isWriteToken = tokenType === "write"; + const labelKey = isWriteToken + ? "common:api.writeTokenLabel" + : "common:api.readTokenLabel"; + const descriptionKey = isWriteToken + ? "common:api.writeTokenDescription" + : "common:api.readTokenDescription"; + + return ( +
+
+

{t(labelKey)}

+

{t(descriptionKey)}

+
+ + {token ? (
- }> {t("common:api.revealButton")} @@ -61,24 +104,20 @@ export default function ApiPage() { - } - > + }> {t("common:api.regenerate.button")}
) : (
- + {t("common:api.generate")} )} - +
); } diff --git a/app/features/auth/core/DiscordStrategy.server.ts b/app/features/auth/core/DiscordStrategy.server.ts index fc511a6ab..4e1121106 100644 --- a/app/features/auth/core/DiscordStrategy.server.ts +++ b/app/features/auth/core/DiscordStrategy.server.ts @@ -1,9 +1,13 @@ +import { add } from "date-fns"; import { OAuth2Strategy } from "remix-auth-oauth2"; import { z } from "zod"; +import * as AdminNotifications from "~/features/admin/core/admin-notifications.server"; import * as UserRepository from "~/features/user-page/UserRepository.server"; import invariant from "~/utils/invariant"; import { logger } from "~/utils/logger"; +let discordApiCooldownUntil: number | null = null; + const partialDiscordUserSchema = z.object({ avatar: z.string().nullish(), discriminator: z.string(), @@ -25,11 +29,28 @@ const discordUserDetailsSchema = z.tuple([ partialDiscordUserSchema, partialDiscordConnectionsSchema, ]); +const discordRateLimitSchema = z.object({ + retry_after: z.number(), +}); export const DiscordStrategy = () => { const envVars = authEnvVars(); - const jsonIfOk = (res: Response) => { + const jsonIfOk = async (res: Response) => { + if (res.status === 429) { + const body = discordRateLimitSchema.safeParse(await res.clone().json()); + const retryAfterSeconds = body.success ? body.data.retry_after : 60; + discordApiCooldownUntil = add(new Date(), { + seconds: retryAfterSeconds, + }).getTime(); + logger.warn( + `Discord API rate limited, cooldown for ${retryAfterSeconds}s${body.success ? "" : " (failed to parse retry_after)"}`, + ); + AdminNotifications.send( + `Discord API rate limited, cooldown for ${retryAfterSeconds}s`, + ); + } + if (!res.ok) { throw new Error( `Auth related call failed with status code ${res.status}`, @@ -40,6 +61,10 @@ export const DiscordStrategy = () => { }; const fetchProfileViaDiscordApi = (token: string) => { + if (discordApiCooldownUntil && Date.now() < discordApiCooldownUntil) { + throw new Error("Discord API is rate limited"); + } + const authHeader: [string, string] = ["Authorization", `Bearer ${token}`]; return Promise.all([ diff --git a/app/features/tournament/TournamentRepository.server.ts b/app/features/tournament/TournamentRepository.server.ts index a40713cc8..1792cd365 100644 --- a/app/features/tournament/TournamentRepository.server.ts +++ b/app/features/tournament/TournamentRepository.server.ts @@ -189,7 +189,7 @@ export async function findById(id: number) { "User.customUrl", "User.country", "User.twitch", - "SeedingSkill.mu as ordinal", + "SeedingSkill.ordinal", "PlusTier.tier as plusTier", "TournamentTeamMember.isOwner", "TournamentTeamMember.createdAt", diff --git a/app/utils/zod.test.ts b/app/utils/zod.test.ts index abd67e018..cd5cbe844 100644 --- a/app/utils/zod.test.ts +++ b/app/utils/zod.test.ts @@ -77,6 +77,18 @@ describe("actuallyNonEmptyStringOrNull", () => { it("returns null for a string with only tag space emoji", () => { expect(actuallyNonEmptyStringOrNull("󠀠󠀠󠀠󠀠󠀠")).toBeNull(); }); + + it("returns null for a string with only Hangul Filler", () => { + expect(actuallyNonEmptyStringOrNull("\u3164")).toBeNull(); + expect(actuallyNonEmptyStringOrNull("ㅤㅤㅤ")).toBeNull(); + }); + + it("returns null for other invisible characters", () => { + expect(actuallyNonEmptyStringOrNull("\u115F")).toBeNull(); + expect(actuallyNonEmptyStringOrNull("\u1160")).toBeNull(); + expect(actuallyNonEmptyStringOrNull("\uFEFF")).toBeNull(); + expect(actuallyNonEmptyStringOrNull("\u2060")).toBeNull(); + }); }); describe("timeString", () => { diff --git a/app/utils/zod.ts b/app/utils/zod.ts index 30a54641b..faf9045e1 100644 --- a/app/utils/zod.ts +++ b/app/utils/zod.ts @@ -275,7 +275,19 @@ export function safeJSONParse(value: unknown): unknown { } } -const EMPTY_CHARACTERS = ["\u200B", "\u200C", "\u200D", "\u200E", "\u200F", "󠀠"]; +const EMPTY_CHARACTERS = [ + "\u200B", + "\u200C", + "\u200D", + "\u200E", + "\u200F", + "󠀠", + "\u3164", + "\u115F", + "\u1160", + "\uFEFF", + "\u2060", +]; const EMPTY_CHARACTERS_REGEX = new RegExp(EMPTY_CHARACTERS.join("|"), "g"); const zalgoRe = /%CC%/g; diff --git a/db-test.sqlite3 b/db-test.sqlite3 index 12b1d787d..0696da07c 100644 Binary files a/db-test.sqlite3 and b/db-test.sqlite3 differ diff --git a/e2e/api-public.spec.ts b/e2e/api-public.spec.ts index 1c85c7a2d..5c71687bb 100644 --- a/e2e/api-public.spec.ts +++ b/e2e/api-public.spec.ts @@ -1,4 +1,5 @@ -import { expect, seed, test } from "~/utils/playwright"; +import { ADMIN_ID } from "~/features/admin/admin-constants"; +import { expect, impersonate, navigate, seed, test } from "~/utils/playwright"; test.describe("Public API", () => { test("OPTIONS preflight request returns 204 with CORS headers", async ({ @@ -25,4 +26,34 @@ test.describe("Public API", () => { expect(response.headers()["access-control-allow-origin"]).toBe("*"); }); + + test("creates read API token and calls public endpoint", async ({ page }) => { + await seed(page); + await impersonate(page); + + await navigate({ page, url: "/api" }); + + await page.locator("form").first().getByRole("button").click(); + await page.waitForURL("/api"); + + await page + .getByRole("button", { name: /reveal/i }) + .first() + .click(); + + const token = await page.locator("input[readonly]").inputValue(); + expect(token).toBeTruthy(); + expect(token.length).toBe(20); + + const response = await page.request.fetch(`/api/user/${ADMIN_ID}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + expect(response.status()).toBe(200); + const data = await response.json(); + expect(data.id).toBe(ADMIN_ID); + expect(data.name).toBe("Sendou"); + }); }); diff --git a/e2e/seeds/db-seed-DEFAULT.sqlite3 b/e2e/seeds/db-seed-DEFAULT.sqlite3 index 23ca7f152..ddad11556 100644 Binary files a/e2e/seeds/db-seed-DEFAULT.sqlite3 and b/e2e/seeds/db-seed-DEFAULT.sqlite3 differ diff --git a/e2e/seeds/db-seed-NO_SCRIMS.sqlite3 b/e2e/seeds/db-seed-NO_SCRIMS.sqlite3 index 6e54e7faf..55248757b 100644 Binary files a/e2e/seeds/db-seed-NO_SCRIMS.sqlite3 and b/e2e/seeds/db-seed-NO_SCRIMS.sqlite3 differ diff --git a/e2e/seeds/db-seed-NO_SQ_GROUPS.sqlite3 b/e2e/seeds/db-seed-NO_SQ_GROUPS.sqlite3 index a9bd99bd9..71a9065e6 100644 Binary files a/e2e/seeds/db-seed-NO_SQ_GROUPS.sqlite3 and b/e2e/seeds/db-seed-NO_SQ_GROUPS.sqlite3 differ diff --git a/e2e/seeds/db-seed-NO_TOURNAMENT_TEAMS.sqlite3 b/e2e/seeds/db-seed-NO_TOURNAMENT_TEAMS.sqlite3 index ea9404cce..2b1fab72e 100644 Binary files a/e2e/seeds/db-seed-NO_TOURNAMENT_TEAMS.sqlite3 and b/e2e/seeds/db-seed-NO_TOURNAMENT_TEAMS.sqlite3 differ diff --git a/e2e/seeds/db-seed-NZAP_IN_TEAM.sqlite3 b/e2e/seeds/db-seed-NZAP_IN_TEAM.sqlite3 index b5a338f25..36e163f9c 100644 Binary files a/e2e/seeds/db-seed-NZAP_IN_TEAM.sqlite3 and b/e2e/seeds/db-seed-NZAP_IN_TEAM.sqlite3 differ diff --git a/e2e/seeds/db-seed-REG_OPEN.sqlite3 b/e2e/seeds/db-seed-REG_OPEN.sqlite3 index 6cd319d42..9cae47f6c 100644 Binary files a/e2e/seeds/db-seed-REG_OPEN.sqlite3 and b/e2e/seeds/db-seed-REG_OPEN.sqlite3 differ diff --git a/e2e/seeds/db-seed-SMALL_SOS.sqlite3 b/e2e/seeds/db-seed-SMALL_SOS.sqlite3 index 5d3150b72..a256dd22c 100644 Binary files a/e2e/seeds/db-seed-SMALL_SOS.sqlite3 and b/e2e/seeds/db-seed-SMALL_SOS.sqlite3 differ diff --git a/locales/da/common.json b/locales/da/common.json index 5fd18419c..1dadc7232 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -336,7 +336,10 @@ "api.title": "", "api.description": "", "api.noAccess": "", - "api.tokenLabel": "", + "api.readTokenLabel": "", + "api.readTokenDescription": "", + "api.writeTokenLabel": "", + "api.writeTokenDescription": "", "api.revealButton": "", "api.regenerate.heading": "", "api.regenerate.button": "", diff --git a/locales/de/common.json b/locales/de/common.json index 29704aa35..a1bf73ce2 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -336,7 +336,10 @@ "api.title": "", "api.description": "", "api.noAccess": "", - "api.tokenLabel": "", + "api.readTokenLabel": "", + "api.readTokenDescription": "", + "api.writeTokenLabel": "", + "api.writeTokenDescription": "", "api.revealButton": "", "api.regenerate.heading": "", "api.regenerate.button": "", diff --git a/locales/en/common.json b/locales/en/common.json index 411b84c0a..a18ce17ea 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -345,7 +345,10 @@ "api.title": "API Access", "api.description": "Generate an API token to access the sendou.ink API. See the <1>API documentation for available endpoints, usage examples and guidelines to follow.", "api.noAccess": "You do not have access to the API. Access is granted to supporters (Supporter tier or higher) and admins, organizers, or streamers of established tournament organizations.", - "api.tokenLabel": "Your API Token", + "api.readTokenLabel": "Read Token", + "api.readTokenDescription": "Use this token to access read-only API endpoints.", + "api.writeTokenLabel": "Write Token", + "api.writeTokenDescription": "Use this token to access both read and write API endpoints.", "api.revealButton": "Click to reveal", "api.regenerate.heading": "Regenerating will invalidate your current token. Any applications using the old token will stop working.", "api.regenerate.button": "Regenerate token", diff --git a/locales/es-ES/common.json b/locales/es-ES/common.json index b2498ffcd..ab66f17af 100644 --- a/locales/es-ES/common.json +++ b/locales/es-ES/common.json @@ -338,7 +338,10 @@ "api.title": "", "api.description": "", "api.noAccess": "", - "api.tokenLabel": "", + "api.readTokenLabel": "", + "api.readTokenDescription": "", + "api.writeTokenLabel": "", + "api.writeTokenDescription": "", "api.revealButton": "", "api.regenerate.heading": "", "api.regenerate.button": "", diff --git a/locales/es-US/common.json b/locales/es-US/common.json index d83540d39..9aab422ef 100644 --- a/locales/es-US/common.json +++ b/locales/es-US/common.json @@ -338,7 +338,10 @@ "api.title": "", "api.description": "", "api.noAccess": "", - "api.tokenLabel": "", + "api.readTokenLabel": "", + "api.readTokenDescription": "", + "api.writeTokenLabel": "", + "api.writeTokenDescription": "", "api.revealButton": "", "api.regenerate.heading": "", "api.regenerate.button": "", diff --git a/locales/fr-CA/common.json b/locales/fr-CA/common.json index 875a6e210..9c2acaa10 100644 --- a/locales/fr-CA/common.json +++ b/locales/fr-CA/common.json @@ -338,7 +338,10 @@ "api.title": "", "api.description": "", "api.noAccess": "", - "api.tokenLabel": "", + "api.readTokenLabel": "", + "api.readTokenDescription": "", + "api.writeTokenLabel": "", + "api.writeTokenDescription": "", "api.revealButton": "", "api.regenerate.heading": "", "api.regenerate.button": "", diff --git a/locales/fr-EU/common.json b/locales/fr-EU/common.json index 5dfa57de5..fdf9bcd2b 100644 --- a/locales/fr-EU/common.json +++ b/locales/fr-EU/common.json @@ -338,7 +338,10 @@ "api.title": "", "api.description": "", "api.noAccess": "", - "api.tokenLabel": "", + "api.readTokenLabel": "", + "api.readTokenDescription": "", + "api.writeTokenLabel": "", + "api.writeTokenDescription": "", "api.revealButton": "", "api.regenerate.heading": "", "api.regenerate.button": "", diff --git a/locales/he/common.json b/locales/he/common.json index 4a75a3225..41baba4ef 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -337,7 +337,10 @@ "api.title": "", "api.description": "", "api.noAccess": "", - "api.tokenLabel": "", + "api.readTokenLabel": "", + "api.readTokenDescription": "", + "api.writeTokenLabel": "", + "api.writeTokenDescription": "", "api.revealButton": "", "api.regenerate.heading": "", "api.regenerate.button": "", diff --git a/locales/it/common.json b/locales/it/common.json index 306ba0df1..8d907f52f 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -338,7 +338,10 @@ "api.title": "", "api.description": "", "api.noAccess": "", - "api.tokenLabel": "", + "api.readTokenLabel": "", + "api.readTokenDescription": "", + "api.writeTokenLabel": "", + "api.writeTokenDescription": "", "api.revealButton": "", "api.regenerate.heading": "", "api.regenerate.button": "", diff --git a/locales/ja/common.json b/locales/ja/common.json index 3021ebee7..2a6cd55d2 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -332,7 +332,10 @@ "api.title": "", "api.description": "", "api.noAccess": "", - "api.tokenLabel": "", + "api.readTokenLabel": "", + "api.readTokenDescription": "", + "api.writeTokenLabel": "", + "api.writeTokenDescription": "", "api.revealButton": "", "api.regenerate.heading": "", "api.regenerate.button": "", diff --git a/locales/ko/common.json b/locales/ko/common.json index e182b027f..085337c43 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -332,7 +332,10 @@ "api.title": "", "api.description": "", "api.noAccess": "", - "api.tokenLabel": "", + "api.readTokenLabel": "", + "api.readTokenDescription": "", + "api.writeTokenLabel": "", + "api.writeTokenDescription": "", "api.revealButton": "", "api.regenerate.heading": "", "api.regenerate.button": "", diff --git a/locales/nl/common.json b/locales/nl/common.json index cf58c8df8..61b59f2b6 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -336,7 +336,10 @@ "api.title": "", "api.description": "", "api.noAccess": "", - "api.tokenLabel": "", + "api.readTokenLabel": "", + "api.readTokenDescription": "", + "api.writeTokenLabel": "", + "api.writeTokenDescription": "", "api.revealButton": "", "api.regenerate.heading": "", "api.regenerate.button": "", diff --git a/locales/pl/common.json b/locales/pl/common.json index ffc5f255b..581eefff4 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -339,7 +339,10 @@ "api.title": "", "api.description": "", "api.noAccess": "", - "api.tokenLabel": "", + "api.readTokenLabel": "", + "api.readTokenDescription": "", + "api.writeTokenLabel": "", + "api.writeTokenDescription": "", "api.revealButton": "", "api.regenerate.heading": "", "api.regenerate.button": "", diff --git a/locales/pt-BR/common.json b/locales/pt-BR/common.json index ab8da8781..c294edfa4 100644 --- a/locales/pt-BR/common.json +++ b/locales/pt-BR/common.json @@ -338,7 +338,10 @@ "api.title": "", "api.description": "", "api.noAccess": "", - "api.tokenLabel": "", + "api.readTokenLabel": "", + "api.readTokenDescription": "", + "api.writeTokenLabel": "", + "api.writeTokenDescription": "", "api.revealButton": "", "api.regenerate.heading": "", "api.regenerate.button": "", diff --git a/locales/ru/common.json b/locales/ru/common.json index 4e716d009..8a5b39fd0 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -339,7 +339,10 @@ "api.title": "", "api.description": "", "api.noAccess": "", - "api.tokenLabel": "", + "api.readTokenLabel": "", + "api.readTokenDescription": "", + "api.writeTokenLabel": "", + "api.writeTokenDescription": "", "api.revealButton": "", "api.regenerate.heading": "", "api.regenerate.button": "", diff --git a/locales/zh/common.json b/locales/zh/common.json index a777d51e8..526090abc 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -332,7 +332,10 @@ "api.title": "", "api.description": "", "api.noAccess": "", - "api.tokenLabel": "", + "api.readTokenLabel": "", + "api.readTokenDescription": "", + "api.writeTokenLabel": "", + "api.writeTokenDescription": "", "api.revealButton": "", "api.regenerate.heading": "", "api.regenerate.button": "", diff --git a/migrations/117-api-token-types.js b/migrations/117-api-token-types.js new file mode 100644 index 000000000..28f5f19a8 --- /dev/null +++ b/migrations/117-api-token-types.js @@ -0,0 +1,34 @@ +export function up(db) { + db.transaction(() => { + db.prepare( + /* sql */ ` + create table "ApiToken_new" ( + "id" integer primary key, + "token" text not null unique, + "userId" integer not null, + "type" text not null default 'read', + "createdAt" integer default (strftime('%s', 'now')) not null, + foreign key ("userId") references "User"("id") on delete cascade + ) strict + `, + ).run(); + + db.prepare( + /* sql */ ` + insert into "ApiToken_new" ("id", "token", "userId", "type", "createdAt") + select "id", "token", "userId", 'read', "createdAt" + from "ApiToken" + `, + ).run(); + + db.prepare(/* sql */ "drop table ApiToken").run(); + + db.prepare( + /* sql */ `alter table "ApiToken_new" rename to "ApiToken"`, + ).run(); + + db.prepare( + /* sql */ `create unique index api_token_user_id_type on "ApiToken"("userId", "type")`, + ).run(); + })(); +} diff --git a/migrations/117-friends-system.js b/migrations/119-friends-system.js similarity index 100% rename from migrations/117-friends-system.js rename to migrations/119-friends-system.js diff --git a/package-lock.json b/package-lock.json index 756a4be15..1d38a49c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "sendou.ink", "version": "3.0.0", "dependencies": { - "@aws-sdk/client-s3": "^3.971.0", - "@aws-sdk/lib-storage": "^3.971.0", + "@aws-sdk/client-s3": "^3.974.0", + "@aws-sdk/lib-storage": "^3.974.0", "@date-fns/tz": "^1.4.1", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", @@ -22,7 +22,7 @@ "@react-router/serve": "^7.12.0", "@remix-run/form-data-parser": "^0.14.0", "@tldraw/tldraw": "^3.12.1", - "@zumer/snapdom": "^2.0.1", + "@zumer/snapdom": "^2.0.2", "aws-sdk": "^2.1693.0", "better-sqlite3": "^12.6.2", "clsx": "^2.1.1", @@ -30,7 +30,7 @@ "date-fns": "^4.1.0", "edmonds-blossom-fixed": "^1.0.1", "gray-matter": "^4.0.3", - "i18next": "^25.7.4", + "i18next": "^25.8.0", "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^3.0.2", "ics": "^3.8.1", @@ -39,7 +39,7 @@ "kysely": "^0.28.10", "lru-cache": "^11.2.4", "lucide-react": "^0.562.0", - "markdown-to-jsx": "^9.6.0", + "markdown-to-jsx": "^9.6.1", "nanoid": "^5.1.6", "neverthrow": "^8.2.0", "node-cron": "4.2.1", @@ -64,20 +64,20 @@ "slugify": "^1.6.6", "swr": "^2.3.8", "web-push": "^3.6.7", - "zod": "^4.3.5" + "zod": "^4.3.6" }, "devDependencies": { "@biomejs/biome": "2.3.11", "@playwright/test": "^1.57.0", "@react-router/dev": "^7.12.0", "@types/better-sqlite3": "^7.6.13", - "@types/node": "^25.0.9", + "@types/node": "^25.0.10", "@types/node-cron": "^3.0.11", "@types/nprogress": "^0.2.3", - "@types/react": "^19.2.8", + "@types/react": "^19.2.9", "@types/react-dom": "^19.2.3", "@types/web-push": "^3.6.4", - "@vitest/browser-playwright": "^4.0.17", + "@vitest/browser-playwright": "^4.0.18", "@vitest/ui": "^4.0.16", "babel-plugin-react-compiler": "^19.1.0-rc.2", "cross-env": "^10.1.0", @@ -93,7 +93,7 @@ "vite-plugin-babel": "^1.4.1", "vite-tsconfig-paths": "^6.0.4", "vitest": "^4.0.16", - "vitest-browser-react": "^2.0.2" + "vitest-browser-react": "^2.0.4" } }, "node_modules/@aws-crypto/crc32": { @@ -299,34 +299,34 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.972.0.tgz", - "integrity": "sha512-ghpDQtjZvbhbnHWymq/V5TL8NppdAGF2THAxYRRBLCJ5JRlq71T24NdovAzvzYaGdH7HtcRkgErBRsFT1gtq4g==", + "version": "3.974.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.974.0.tgz", + "integrity": "sha512-X+vpXNJ8cU8Iw1FtDgDHxo9z6RxlXfcTtpdGnKws4rk+tCYKSAor/DG6BRMzbh4E5xAA7DiU1Ny3BTrRRSt/Yg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.972.0", - "@aws-sdk/credential-provider-node": "3.972.0", - "@aws-sdk/middleware-bucket-endpoint": "3.972.0", - "@aws-sdk/middleware-expect-continue": "3.972.0", - "@aws-sdk/middleware-flexible-checksums": "3.972.0", - "@aws-sdk/middleware-host-header": "3.972.0", - "@aws-sdk/middleware-location-constraint": "3.972.0", - "@aws-sdk/middleware-logger": "3.972.0", - "@aws-sdk/middleware-recursion-detection": "3.972.0", - "@aws-sdk/middleware-sdk-s3": "3.972.0", - "@aws-sdk/middleware-ssec": "3.972.0", - "@aws-sdk/middleware-user-agent": "3.972.0", - "@aws-sdk/region-config-resolver": "3.972.0", + "@aws-sdk/core": "^3.973.0", + "@aws-sdk/credential-provider-node": "^3.972.1", + "@aws-sdk/middleware-bucket-endpoint": "^3.972.1", + "@aws-sdk/middleware-expect-continue": "^3.972.1", + "@aws-sdk/middleware-flexible-checksums": "^3.972.1", + "@aws-sdk/middleware-host-header": "^3.972.1", + "@aws-sdk/middleware-location-constraint": "^3.972.1", + "@aws-sdk/middleware-logger": "^3.972.1", + "@aws-sdk/middleware-recursion-detection": "^3.972.1", + "@aws-sdk/middleware-sdk-s3": "^3.972.1", + "@aws-sdk/middleware-ssec": "^3.972.1", + "@aws-sdk/middleware-user-agent": "^3.972.1", + "@aws-sdk/region-config-resolver": "^3.972.1", "@aws-sdk/signature-v4-multi-region": "3.972.0", - "@aws-sdk/types": "3.972.0", + "@aws-sdk/types": "^3.973.0", "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "3.972.0", - "@aws-sdk/util-user-agent-node": "3.972.0", + "@aws-sdk/util-user-agent-browser": "^3.972.1", + "@aws-sdk/util-user-agent-node": "^3.972.1", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", + "@smithy/core": "^3.21.0", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/eventstream-serde-config-resolver": "^4.3.8", "@smithy/eventstream-serde-node": "^4.2.8", @@ -337,21 +337,21 @@ "@smithy/invalid-dependency": "^4.2.8", "@smithy/md5-js": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", + "@smithy/middleware-endpoint": "^4.4.10", + "@smithy/middleware-retry": "^4.4.26", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", + "@smithy/smithy-client": "^4.10.11", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", + "@smithy/util-defaults-mode-browser": "^4.3.25", + "@smithy/util-defaults-mode-node": "^4.2.28", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -365,44 +365,44 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.972.0.tgz", - "integrity": "sha512-5qw6qLiRE4SUiz0hWy878dSR13tSVhbTWhsvFT8mGHe37NRRiaobm5MA2sWD0deRAuO98djSiV+dhWXa1xIFNw==", + "version": "3.974.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.974.0.tgz", + "integrity": "sha512-ci+GiM0c4ULo4D79UMcY06LcOLcfvUfiyt8PzNY0vbt5O8BfCPYf4QomwVgkNcLLCYmroO4ge2Yy1EsLUlcD6g==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.972.0", - "@aws-sdk/middleware-host-header": "3.972.0", - "@aws-sdk/middleware-logger": "3.972.0", - "@aws-sdk/middleware-recursion-detection": "3.972.0", - "@aws-sdk/middleware-user-agent": "3.972.0", - "@aws-sdk/region-config-resolver": "3.972.0", - "@aws-sdk/types": "3.972.0", + "@aws-sdk/core": "^3.973.0", + "@aws-sdk/middleware-host-header": "^3.972.1", + "@aws-sdk/middleware-logger": "^3.972.1", + "@aws-sdk/middleware-recursion-detection": "^3.972.1", + "@aws-sdk/middleware-user-agent": "^3.972.1", + "@aws-sdk/region-config-resolver": "^3.972.1", + "@aws-sdk/types": "^3.973.0", "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "3.972.0", - "@aws-sdk/util-user-agent-node": "3.972.0", + "@aws-sdk/util-user-agent-browser": "^3.972.1", + "@aws-sdk/util-user-agent-node": "^3.972.1", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", + "@smithy/core": "^3.21.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", + "@smithy/middleware-endpoint": "^4.4.10", + "@smithy/middleware-retry": "^4.4.26", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", + "@smithy/smithy-client": "^4.10.11", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", + "@smithy/util-defaults-mode-browser": "^4.3.25", + "@smithy/util-defaults-mode-node": "^4.2.28", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -414,19 +414,19 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz", - "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==", + "version": "3.973.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.0.tgz", + "integrity": "sha512-qy3Fmt8z4PRInM3ZqJmHihQ2tfCdj/MzbGaZpuHjYjgl1/Gcar4Pyp/zzHXh9hGEb61WNbWgsJcDUhnGIiX1TA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.972.0", - "@aws-sdk/xml-builder": "3.972.0", - "@smithy/core": "^3.20.6", + "@aws-sdk/types": "^3.973.0", + "@aws-sdk/xml-builder": "^3.972.1", + "@smithy/core": "^3.21.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", + "@smithy/smithy-client": "^4.10.11", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", @@ -451,13 +451,13 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.0.tgz", - "integrity": "sha512-kKHoNv+maHlPQOAhYamhap0PObd16SAb3jwaY0KYgNTiSbeXlbGUZPLioo9oA3wU10zItJzx83ClU7d7h40luA==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.1.tgz", + "integrity": "sha512-/etNHqnx96phy/SjI0HRC588o4vKH5F0xfkZ13yAATV7aNrb+5gYGNE6ePWafP+FuZ3HkULSSlJFj0AxgrAqYw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/types": "3.972.0", + "@aws-sdk/core": "^3.973.0", + "@aws-sdk/types": "^3.973.0", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -467,18 +467,18 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.0.tgz", - "integrity": "sha512-xzEi81L7I5jGUbpmqEHCe7zZr54hCABdj4H+3LzktHYuovV/oqnvoDdvZpGFR0e/KAw1+PL38NbGrpG30j6qlA==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.1.tgz", + "integrity": "sha512-AeopObGW5lpWbDRZ+t4EAtS7wdfSrHPLeFts7jaBzgIaCCD7TL7jAyAB9Y5bCLOPF+17+GL54djCCsjePljUAw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/types": "3.972.0", + "@aws-sdk/core": "^3.973.0", + "@aws-sdk/types": "^3.973.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", + "@smithy/smithy-client": "^4.10.11", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.10", "tslib": "^2.6.2" @@ -488,20 +488,20 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.0.tgz", - "integrity": "sha512-ruhAMceUIq2aknFd3jhWxmO0P0Efab5efjyIXOkI9i80g+zDY5VekeSxfqRKStEEJSKSCHDLQuOu0BnAn4Rzew==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.1.tgz", + "integrity": "sha512-OdbJA3v+XlNDsrYzNPRUwr8l7gw1r/nR8l4r96MDzSBDU8WEo8T6C06SvwaXR8SpzsjO3sq5KMP86wXWg7Rj4g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/credential-provider-env": "3.972.0", - "@aws-sdk/credential-provider-http": "3.972.0", - "@aws-sdk/credential-provider-login": "3.972.0", - "@aws-sdk/credential-provider-process": "3.972.0", - "@aws-sdk/credential-provider-sso": "3.972.0", - "@aws-sdk/credential-provider-web-identity": "3.972.0", - "@aws-sdk/nested-clients": "3.972.0", - "@aws-sdk/types": "3.972.0", + "@aws-sdk/core": "^3.973.0", + "@aws-sdk/credential-provider-env": "^3.972.1", + "@aws-sdk/credential-provider-http": "^3.972.1", + "@aws-sdk/credential-provider-login": "^3.972.1", + "@aws-sdk/credential-provider-process": "^3.972.1", + "@aws-sdk/credential-provider-sso": "^3.972.1", + "@aws-sdk/credential-provider-web-identity": "^3.972.1", + "@aws-sdk/nested-clients": "3.974.0", + "@aws-sdk/types": "^3.973.0", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -513,14 +513,14 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.0.tgz", - "integrity": "sha512-SsrsFJsEYAJHO4N/r2P0aK6o8si6f1lprR+Ej8J731XJqTckSGs/HFHcbxOyW/iKt+LNUvZa59/VlJmjhF4bEQ==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.1.tgz", + "integrity": "sha512-CccqDGL6ZrF3/EFWZefvKW7QwwRdxlHUO8NVBKNVcNq6womrPDvqB6xc9icACtE0XB0a7PLoSTkAg8bQVkTO2w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/nested-clients": "3.972.0", - "@aws-sdk/types": "3.972.0", + "@aws-sdk/core": "^3.973.0", + "@aws-sdk/nested-clients": "3.974.0", + "@aws-sdk/types": "^3.973.0", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -532,18 +532,18 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.0.tgz", - "integrity": "sha512-wwJDpEGl6+sOygic8QKu0OHVB8SiodqF1fr5jvUlSFfS6tJss/E9vBc2aFjl7zI6KpAIYfIzIgM006lRrZtWCQ==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.1.tgz", + "integrity": "sha512-DwXPk9GfuU/xG9tmCyXFVkCr6X3W8ZCoL5Ptb0pbltEx1/LCcg7T+PBqDlPiiinNCD6ilIoMJDWsnJ8ikzZA7Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.972.0", - "@aws-sdk/credential-provider-http": "3.972.0", - "@aws-sdk/credential-provider-ini": "3.972.0", - "@aws-sdk/credential-provider-process": "3.972.0", - "@aws-sdk/credential-provider-sso": "3.972.0", - "@aws-sdk/credential-provider-web-identity": "3.972.0", - "@aws-sdk/types": "3.972.0", + "@aws-sdk/credential-provider-env": "^3.972.1", + "@aws-sdk/credential-provider-http": "^3.972.1", + "@aws-sdk/credential-provider-ini": "^3.972.1", + "@aws-sdk/credential-provider-process": "^3.972.1", + "@aws-sdk/credential-provider-sso": "^3.972.1", + "@aws-sdk/credential-provider-web-identity": "^3.972.1", + "@aws-sdk/types": "^3.973.0", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -555,13 +555,13 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.0.tgz", - "integrity": "sha512-nmzYhamLDJ8K+v3zWck79IaKMc350xZnWsf/GeaXO6E3MewSzd3lYkTiMi7lEp3/UwDm9NHfPguoPm+mhlSWQQ==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.1.tgz", + "integrity": "sha512-bi47Zigu3692SJwdBvo8y1dEwE6B61stCwCFnuRWJVTfiM84B+VTSCV661CSWJmIZzmcy7J5J3kWyxL02iHj0w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/types": "3.972.0", + "@aws-sdk/core": "^3.973.0", + "@aws-sdk/types": "^3.973.0", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", @@ -572,15 +572,15 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.0.tgz", - "integrity": "sha512-6mYyfk1SrMZ15cH9T53yAF4YSnvq4yU1Xlgm3nqV1gZVQzmF5kr4t/F3BU3ygbvzi4uSwWxG3I3TYYS5eMlAyg==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.1.tgz", + "integrity": "sha512-dLZVNhM7wSgVUFsgVYgI5hb5Z/9PUkT46pk/SHrSmUqfx6YDvoV4YcPtaiRqviPpEGGiRtdQMEadyOKIRqulUQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.972.0", - "@aws-sdk/core": "3.972.0", - "@aws-sdk/token-providers": "3.972.0", - "@aws-sdk/types": "3.972.0", + "@aws-sdk/client-sso": "3.974.0", + "@aws-sdk/core": "^3.973.0", + "@aws-sdk/token-providers": "3.974.0", + "@aws-sdk/types": "^3.973.0", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", @@ -591,14 +591,14 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.0.tgz", - "integrity": "sha512-vsJXBGL8H54kz4T6do3p5elATj5d1izVGUXMluRJntm9/I0be/zUYtdd4oDTM2kSUmd4Zhyw3fMQ9lw7CVhd4A==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.1.tgz", + "integrity": "sha512-YMDeYgi0u687Ay0dAq/pFPKuijrlKTgsaB/UATbxCs/FzZfMiG4If5ksywHmmW7MiYUF8VVv+uou3TczvLrN4w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/nested-clients": "3.972.0", - "@aws-sdk/types": "3.972.0", + "@aws-sdk/core": "^3.973.0", + "@aws-sdk/nested-clients": "3.974.0", + "@aws-sdk/types": "^3.973.0", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", @@ -609,14 +609,14 @@ } }, "node_modules/@aws-sdk/lib-storage": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.972.0.tgz", - "integrity": "sha512-4xNJ1B4zKzvwEedNuhJRoLmrNCfHorwBFvZjjR/bFSWxxCK0Mh5NSarB4sgeycVyZjCwOGFTFHxk4RtXi9JksA==", + "version": "3.974.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.974.0.tgz", + "integrity": "sha512-6okXc+jsVUszk04A+Xx01J/ThygiisBK0Y2sE8T+sPLKKSplnr8VAusud3Fn8w9D9RmYBEWFx8E6Mg+vLZ5J0A==", "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/smithy-client": "^4.10.8", + "@smithy/middleware-endpoint": "^4.4.10", + "@smithy/smithy-client": "^4.10.11", "buffer": "5.6.0", "events": "3.3.0", "stream-browserify": "3.0.0", @@ -626,17 +626,17 @@ "node": ">=20.0.0" }, "peerDependencies": { - "@aws-sdk/client-s3": "3.972.0" + "@aws-sdk/client-s3": "3.974.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.0.tgz", - "integrity": "sha512-IrIjAehc3PrseAGfk2ldtAf+N0BAnNHR1DCZIDh9IAcFrTVWC3Fi9KJdtabrxcY3Onpt/8opOco4EIEAWgMz7A==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.1.tgz", + "integrity": "sha512-YVvoitBdE8WOpHqIXvv49efT73F4bJ99XH2bi3Dn3mx7WngI4RwHwn/zF5i0q1Wdi5frGSCNF3vuh+pY817//w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.972.0", - "@aws-sdk/util-arn-parser": "3.972.0", + "@aws-sdk/types": "^3.973.0", + "@aws-sdk/util-arn-parser": "^3.972.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", @@ -648,12 +648,12 @@ } }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.0.tgz", - "integrity": "sha512-xyhDoY0qse8MvQC4RZCpT5WoIQ4/kwqv71Dh1s3mdXjL789Z4a6L/khBTSXECR5+egSZ960AInj3aR+CrezDRQ==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.1.tgz", + "integrity": "sha512-6lfl2/J/kutzw/RLu1kjbahsz4vrGPysrdxWaw8fkjLYG+6M6AswocIAZFS/LgAVi/IWRwPTx9YC0/NH2wDrSw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.972.0", + "@aws-sdk/types": "^3.973.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -663,17 +663,17 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.0.tgz", - "integrity": "sha512-zxK0ezmT7fLEPJ650S8QBc4rGDq5+5rdsLnnuZ6hPaZE4/+QtUoTw+gSDETyiWodNcRuz2ZWnqi17K+7nKtSRg==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.1.tgz", + "integrity": "sha512-kjVVREpqeUkYQsXr78AcsJbEUlxGH7+H6yS7zkjrnu6HyEVxbdSndkKX6VpKneFOihjCAhIXlk4wf3butDHkNQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "3.972.0", + "@aws-sdk/core": "^3.973.0", "@aws-sdk/crc64-nvme": "3.972.0", - "@aws-sdk/types": "3.972.0", + "@aws-sdk/types": "^3.973.0", "@smithy/is-array-buffer": "^4.2.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", @@ -688,12 +688,12 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.0.tgz", - "integrity": "sha512-3eztFI6F9/eHtkIaWKN3nT+PM+eQ6p1MALDuNshFk323ixuCZzOOVT8oUqtZa30Z6dycNXJwhlIq7NhUVFfimw==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.1.tgz", + "integrity": "sha512-/R82lXLPmZ9JaUGSUdKtBp2k/5xQxvBT3zZWyKiBOhyulFotlfvdlrO8TnqstBimsl4lYEYySDL+W6ldFh6ALg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.972.0", + "@aws-sdk/types": "^3.973.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -703,12 +703,12 @@ } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.0.tgz", - "integrity": "sha512-WpsxoVPzbGPQGb/jupNYjpE0REcCPtjz7Q7zAt+dyo7fxsLBn4J+Rp6AYzSa04J9VrmrvCqCbVLu6B88PlSKSQ==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.1.tgz", + "integrity": "sha512-YisPaCbvBk9gY5aUI8jDMDKXsLZ9Fet0WYj1MviK8tZYMgxBIYHM6l3O/OHaAIujojZvamd9F3haYYYWp5/V3w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.972.0", + "@aws-sdk/types": "^3.973.0", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -717,12 +717,12 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.0.tgz", - "integrity": "sha512-ZvdyVRwzK+ra31v1pQrgbqR/KsLD+wwJjHgko6JfoKUBIcEfAwJzQKO6HspHxdHWTVUz6MgvwskheR/TTYZl2g==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.1.tgz", + "integrity": "sha512-JGgFl6cHg9G2FHu4lyFIzmFN8KESBiRr84gLC3Aeni0Gt1nKm+KxWLBuha/RPcXxJygGXCcMM4AykkIwxor8RA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.972.0", + "@aws-sdk/types": "^3.973.0", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -731,12 +731,12 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.0.tgz", - "integrity": "sha512-F2SmUeO+S6l1h6dydNet3BQIk173uAkcfU1HDkw/bUdRLAnh15D3HP9vCZ7oCPBNcdEICbXYDmx0BR9rRUHGlQ==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.1.tgz", + "integrity": "sha512-taGzNRe8vPHjnliqXIHp9kBgIemLE/xCaRTMH1NH0cncHeaPcjxtnCroAAM9aOlPuKvBe2CpZESyvM1+D8oI7Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.972.0", + "@aws-sdk/types": "^3.973.0", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", @@ -747,19 +747,19 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.0.tgz", - "integrity": "sha512-0bcKFXWx+NZ7tIlOo7KjQ+O2rydiHdIQahrq+fN6k9Osky29v17guy68urUKfhTobR6iY6KvxkroFWaFtTgS5w==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.1.tgz", + "integrity": "sha512-q/hK0ZNf/aafFRv2wIlDM3p+izi5cXwktVNvRvW646A0MvVZmT4/vwadv/jPA9AORFbnpyf/0luxiMz181f9yg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@aws-sdk/util-arn-parser": "3.972.0", - "@smithy/core": "^3.20.6", + "@aws-sdk/core": "^3.973.0", + "@aws-sdk/types": "^3.973.0", + "@aws-sdk/util-arn-parser": "^3.972.1", + "@smithy/core": "^3.21.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", + "@smithy/smithy-client": "^4.10.11", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.8", @@ -772,12 +772,12 @@ } }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.0.tgz", - "integrity": "sha512-cEr2HtK4R2fi8Y0P95cjbr4KJOjKBt8ms95mEJhabJN8KM4CpD4iS/J1lhvMj+qWir0KBTV6gKmxECXdfL9S6w==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.1.tgz", + "integrity": "sha512-fLtRTPd/MxJT2drJKft2GVGKm35PiNEeQ1Dvz1vc/WhhgAteYrp4f1SfSgjgLaYWGMExESJL4bt8Dxqp6tVsog==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.972.0", + "@aws-sdk/types": "^3.973.0", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -786,15 +786,15 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.0.tgz", - "integrity": "sha512-kFHQm2OCBJCzGWRafgdWHGFjitUXY/OxXngymcX4l8CiyiNDZB27HDDBg2yLj3OUJc4z4fexLMmP8r9vgag19g==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.1.tgz", + "integrity": "sha512-6SVg4pY/9Oq9MLzO48xuM3lsOb8Rxg55qprEtFRpkUmuvKij31f5SQHEGxuiZ4RqIKrfjr2WMuIgXvqJ0eJsPA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/types": "3.972.0", + "@aws-sdk/core": "^3.973.0", + "@aws-sdk/types": "^3.973.0", "@aws-sdk/util-endpoints": "3.972.0", - "@smithy/core": "^3.20.6", + "@smithy/core": "^3.21.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -804,44 +804,44 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.972.0.tgz", - "integrity": "sha512-QGlbnuGzSQJVG6bR9Qw6G0Blh6abFR4VxNa61ttMbzy9jt28xmk2iGtrYLrQPlCCPhY6enHqjTWm3n3LOb0wAw==", + "version": "3.974.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.974.0.tgz", + "integrity": "sha512-k3dwdo/vOiHMJc9gMnkPl1BA5aQfTrZbz+8fiDkWrPagqAioZgmo5oiaOaeX0grObfJQKDtcpPFR4iWf8cgl8Q==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.972.0", - "@aws-sdk/middleware-host-header": "3.972.0", - "@aws-sdk/middleware-logger": "3.972.0", - "@aws-sdk/middleware-recursion-detection": "3.972.0", - "@aws-sdk/middleware-user-agent": "3.972.0", - "@aws-sdk/region-config-resolver": "3.972.0", - "@aws-sdk/types": "3.972.0", + "@aws-sdk/core": "^3.973.0", + "@aws-sdk/middleware-host-header": "^3.972.1", + "@aws-sdk/middleware-logger": "^3.972.1", + "@aws-sdk/middleware-recursion-detection": "^3.972.1", + "@aws-sdk/middleware-user-agent": "^3.972.1", + "@aws-sdk/region-config-resolver": "^3.972.1", + "@aws-sdk/types": "^3.973.0", "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "3.972.0", - "@aws-sdk/util-user-agent-node": "3.972.0", + "@aws-sdk/util-user-agent-browser": "^3.972.1", + "@aws-sdk/util-user-agent-node": "^3.972.1", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", + "@smithy/core": "^3.21.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", + "@smithy/middleware-endpoint": "^4.4.10", + "@smithy/middleware-retry": "^4.4.26", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", + "@smithy/smithy-client": "^4.10.11", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", + "@smithy/util-defaults-mode-browser": "^4.3.25", + "@smithy/util-defaults-mode-node": "^4.2.28", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -853,12 +853,12 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.0.tgz", - "integrity": "sha512-JyOf+R/6vJW8OEVFCAyzEOn2reri/Q+L0z9zx4JQSKWvTmJ1qeFO25sOm8VIfB8URKhfGRTQF30pfYaH2zxt/A==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.1.tgz", + "integrity": "sha512-voIY8RORpxLAEgEkYaTFnkaIuRwVBEc+RjVZYcSSllPV+ZEKAacai6kNhJeE3D70Le+JCfvRb52tng/AVHY+jQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.972.0", + "@aws-sdk/types": "^3.973.0", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", @@ -885,25 +885,56 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/token-providers": { + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@aws-sdk/core": { "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.972.0.tgz", - "integrity": "sha512-kWlXG+y5nZhgXGEtb72Je+EvqepBPs8E3vZse//1PYLWs2speFqbGE/ywCXmzEJgHgVqSB/u/lqBvs5WlYmSqQ==", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz", + "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/nested-clients": "3.972.0", "@aws-sdk/types": "3.972.0", + "@aws-sdk/xml-builder": "3.972.0", + "@smithy/core": "^3.20.6", + "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/types": { + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.0.tgz", + "integrity": "sha512-0bcKFXWx+NZ7tIlOo7KjQ+O2rydiHdIQahrq+fN6k9Osky29v17guy68urUKfhTobR6iY6KvxkroFWaFtTgS5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.972.0", + "@aws-sdk/types": "3.972.0", + "@aws-sdk/util-arn-parser": "3.972.0", + "@smithy/core": "^3.20.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.10.8", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.10", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@aws-sdk/types": { "version": "3.972.0", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", @@ -916,7 +947,7 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/util-arn-parser": { + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@aws-sdk/util-arn-parser": { "version": "3.972.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.0.tgz", "integrity": "sha512-RM5Mmo/KJ593iMSrALlHEOcc9YOIyOsDmS5x2NLOMdEmzv1o00fcpAkCQ02IGu1eFneBFT7uX0Mpag0HI+Cz2g==", @@ -928,6 +959,63 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@aws-sdk/xml-builder": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz", + "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.974.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.974.0.tgz", + "integrity": "sha512-cBykL0LiccKIgNhGWvQRTPvsBLPZxnmJU3pYxG538jpFX8lQtrCy1L7mmIHNEdxIdIGEPgAEHF8/JQxgBToqUQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.0", + "@aws-sdk/nested-clients": "3.974.0", + "@aws-sdk/types": "^3.973.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.0.tgz", + "integrity": "sha512-jYIdB7a7jhRTvyb378nsjyvJh1Si+zVduJ6urMNGpz8RjkmHZ+9vM2H07XaIB2Cfq0GhJRZYOfUCH8uqQhqBkQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.1.tgz", + "integrity": "sha512-XnNit6H9PPHhqUXW/usjX6JeJ6Pm8ZNqivTjmNjgWHeOfVpblUc/MTic02UmCNR0jJLPjQ3mBKiMen0tnkNQjQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/util-endpoints": { "version": "3.972.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.972.0.tgz", @@ -944,6 +1032,19 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/util-endpoints/node_modules/@aws-sdk/types": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", + "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/util-locate-window": { "version": "3.957.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.957.0.tgz", @@ -957,25 +1058,25 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.0.tgz", - "integrity": "sha512-eOLdkQyoRbDgioTS3Orr7iVsVEutJyMZxvyZ6WAF95IrF0kfWx5Rd/KXnfbnG/VKa2CvjZiitWfouLzfVEyvJA==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.1.tgz", + "integrity": "sha512-IgF55NFmJX8d9Wql9M0nEpk2eYbuD8G4781FN4/fFgwTXBn86DvlZJuRWDCMcMqZymnBVX7HW9r+3r9ylqfW0w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.972.0", + "@aws-sdk/types": "^3.973.0", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.0.tgz", - "integrity": "sha512-GOy+AiSrE9kGiojiwlZvVVSXwylu4+fmP0MJfvras/MwP09RB/YtQuOVR1E0fKQc6OMwaTNBjgAbOEhxuWFbAw==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.1.tgz", + "integrity": "sha512-oIs4JFcADzoZ0c915R83XvK2HltWupxNsXUIuZse2rgk7b97zTpkxaqXiH0h9ylh31qtgo/t8hp4tIqcsMrEbQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.972.0", - "@aws-sdk/types": "3.972.0", + "@aws-sdk/middleware-user-agent": "^3.972.1", + "@aws-sdk/types": "^3.973.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -993,9 +1094,9 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz", - "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==", + "version": "3.972.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.1.tgz", + "integrity": "sha512-6zZGlPOqn7Xb+25MAXGb1JhgvaC5HjZj6GzszuVrnEgbhvzBRFGKYemuHBV4bho+dtqeYKPgaZUv7/e80hIGNg==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.12.0", @@ -6705,9 +6806,9 @@ } }, "node_modules/@smithy/core": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.21.0.tgz", - "integrity": "sha512-bg2TfzgsERyETAxc/Ims/eJX8eAnIeTi4r4LHpMpfF/2NyO6RsWis0rjKcCPaGksljmOb23BZRiCeT/3NvwkXw==", + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.21.1.tgz", + "integrity": "sha512-NUH8R4O6FkN8HKMojzbGg/5pNjsfTjlMmeFclyPfPaXXUrbr5TzhWgbf7t92wfrpCHRgpjyz7ffASIS3wX28aA==", "license": "Apache-2.0", "dependencies": { "@smithy/middleware-serde": "^4.2.9", @@ -6925,12 +7026,12 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.10.tgz", - "integrity": "sha512-kwWpNltpxrvPabnjEFvwSmA+66l6s2ReCvgVSzW/z92LU4T28fTdgZ18IdYRYOrisu2NMQ0jUndRScbO65A/zg==", + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.11.tgz", + "integrity": "sha512-/WqsrycweGGfb9sSzME4CrsuayjJF6BueBmkKlcbeU5q18OhxRrvvKlmfw3tpDsK5ilx2XUJvoukwxHB0nHs/Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.21.0", + "@smithy/core": "^3.21.1", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -6944,15 +7045,15 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.26", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.26.tgz", - "integrity": "sha512-ozZMoTAr+B2aVYfLYfkssFvc8ZV3p/vLpVQ7/k277xxUOA9ykSPe5obL2j6yHfbdrM/SZV7qj0uk/hSqavHrLw==", + "version": "4.4.27", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.27.tgz", + "integrity": "sha512-xFUYCGRVsfgiN5EjsJJSzih9+yjStgMTCLANPlf0LVQkPDYCe0hz97qbdTZosFOiYlGBlHYityGRxrQ/hxhfVQ==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", - "@smithy/smithy-client": "^4.10.11", + "@smithy/smithy-client": "^4.10.12", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -7119,13 +7220,13 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.10.11", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.11.tgz", - "integrity": "sha512-6o804SCyHGMXAb5mFJ+iTy9kVKv7F91a9szN0J+9X6p8A0NrdpUxdaC57aye2ipQkP2C4IAqETEpGZ0Zj77Haw==", + "version": "4.10.12", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.12.tgz", + "integrity": "sha512-VKO/HKoQ5OrSHW6AJUmEnUKeXI1/5LfCwO9cwyao7CmLvGnZeM1i36Lyful3LK1XU7HwTVieTqO1y2C/6t3qtA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.21.0", - "@smithy/middleware-endpoint": "^4.4.10", + "@smithy/core": "^3.21.1", + "@smithy/middleware-endpoint": "^4.4.11", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", @@ -7226,13 +7327,13 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.25", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.25.tgz", - "integrity": "sha512-8ugoNMtss2dJHsXnqsibGPqoaafvWJPACmYKxJ4E6QWaDrixsAemmiMMAVbvwYadjR0H9G2+AlzsInSzRi8PSw==", + "version": "4.3.26", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.26.tgz", + "integrity": "sha512-vva0dzYUTgn7DdE0uaha10uEdAgmdLnNFowKFjpMm6p2R0XDk5FHPX3CBJLzWQkQXuEprsb0hGz9YwbicNWhjw==", "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.10.11", + "@smithy/smithy-client": "^4.10.12", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -7241,16 +7342,16 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.28", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.28.tgz", - "integrity": "sha512-mjUdcP8h3E0K/XvNMi9oBXRV3DMCzeRiYIieZ1LQ7jq5tu6GH/GTWym7a1xIIE0pKSoLcpGsaImuQhGPSIJzAA==", + "version": "4.2.29", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.29.tgz", + "integrity": "sha512-c6D7IUBsZt/aNnTBHMTf+OVh+h/JcxUUgfTcIJaWRe6zhOum1X+pNKSZtZ+7fbOn5I99XVFtmrnXKv8yHHErTQ==", "license": "Apache-2.0", "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.10.11", + "@smithy/smithy-client": "^4.10.12", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -8050,9 +8151,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", + "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", "dev": true, "license": "MIT", "dependencies": { @@ -8095,9 +8196,9 @@ } }, "node_modules/@types/react": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz", - "integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==", + "version": "19.2.9", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz", + "integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -8155,14 +8256,14 @@ } }, "node_modules/@vitest/browser": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.0.17.tgz", - "integrity": "sha512-cgf2JZk2fv5or3efmOrRJe1V9Md89BPgz4ntzbf84yAb+z2hW6niaGFinl9aFzPZ1q3TGfWZQWZ9gXTFThs2Qw==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.0.18.tgz", + "integrity": "sha512-gVQqh7paBz3gC+ZdcCmNSWJMk70IUjDeVqi+5m5vYpEHsIwRgw3Y545jljtajhkekIpIp5Gg8oK7bctgY0E2Ng==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/mocker": "4.0.17", - "@vitest/utils": "4.0.17", + "@vitest/mocker": "4.0.18", + "@vitest/utils": "4.0.18", "magic-string": "^0.30.21", "pixelmatch": "7.1.0", "pngjs": "^7.0.0", @@ -8174,18 +8275,18 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "4.0.17" + "vitest": "4.0.18" } }, "node_modules/@vitest/browser-playwright": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.0.17.tgz", - "integrity": "sha512-CE9nlzslHX6Qz//MVrjpulTC9IgtXTbJ+q7Rx1HD+IeSOWv4NHIRNHPA6dB4x01d9paEqt+TvoqZfmgq40DxEQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.0.18.tgz", + "integrity": "sha512-gfajTHVCiwpxRj1qh0Sh/5bbGLG4F/ZH/V9xvFVoFddpITfMta9YGow0W6ZpTTORv2vdJuz9TnrNSmjKvpOf4g==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/browser": "4.0.17", - "@vitest/mocker": "4.0.17", + "@vitest/browser": "4.0.18", + "@vitest/mocker": "4.0.18", "tinyrainbow": "^3.0.3" }, "funding": { @@ -8193,7 +8294,7 @@ }, "peerDependencies": { "playwright": "*", - "vitest": "4.0.17" + "vitest": "4.0.18" }, "peerDependenciesMeta": { "playwright": { @@ -8202,16 +8303,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.17.tgz", - "integrity": "sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.17", - "@vitest/utils": "4.0.17", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" }, @@ -8220,13 +8321,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.17.tgz", - "integrity": "sha512-+ZtQhLA3lDh1tI2wxe3yMsGzbp7uuJSWBM1iTIKCbppWTSBN09PUC+L+fyNlQApQoR+Ps8twt2pbSSXg2fQVEQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.17", + "@vitest/spy": "4.0.18", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -8247,9 +8348,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.17.tgz", - "integrity": "sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", "dev": true, "license": "MIT", "dependencies": { @@ -8260,13 +8361,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.17.tgz", - "integrity": "sha512-JmuQyf8aMWoo/LmNFppdpkfRVHJcsgzkbCA+/Bk7VfNH7RE6Ut2qxegeyx2j3ojtJtKIbIGy3h+KxGfYfk28YQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.0.17", + "@vitest/utils": "4.0.18", "pathe": "^2.0.3" }, "funding": { @@ -8281,13 +8382,13 @@ "license": "MIT" }, "node_modules/@vitest/snapshot": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.17.tgz", - "integrity": "sha512-npPelD7oyL+YQM2gbIYvlavlMVWUfNNGZPcu0aEUQXt7FXTuqhmgiYupPnAanhKvyP6Srs2pIbWo30K0RbDtRQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.17", + "@vitest/pretty-format": "4.0.18", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -8303,9 +8404,9 @@ "license": "MIT" }, "node_modules/@vitest/spy": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.17.tgz", - "integrity": "sha512-I1bQo8QaP6tZlTomQNWKJE6ym4SHf3oLS7ceNjozxxgzavRAgZDc06T7kD8gb9bXKEgcLNt00Z+kZO6KaJ62Ew==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", "dev": true, "license": "MIT", "funding": { @@ -8313,13 +8414,13 @@ } }, "node_modules/@vitest/ui": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.17.tgz", - "integrity": "sha512-hRDjg6dlDz7JlZAvjbiCdAJ3SDG+NH8tjZe21vjxfvT2ssYAn72SRXMge3dKKABm3bIJ3C+3wdunIdur8PHEAw==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.18.tgz", + "integrity": "sha512-CGJ25bc8fRi8Lod/3GHSvXRKi7nBo3kxh0ApW4yCjmrWmRmlT53B5E08XRSZRliygG0aVNxLrBEqPYdz/KcCtQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.0.17", + "@vitest/utils": "4.0.18", "fflate": "^0.8.2", "flatted": "^3.3.3", "pathe": "^2.0.3", @@ -8331,7 +8432,7 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "4.0.17" + "vitest": "4.0.18" } }, "node_modules/@vitest/ui/node_modules/pathe": { @@ -8342,13 +8443,13 @@ "license": "MIT" }, "node_modules/@vitest/utils": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.17.tgz", - "integrity": "sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.17", + "@vitest/pretty-format": "4.0.18", "tinyrainbow": "^3.0.3" }, "funding": { @@ -8362,9 +8463,9 @@ "license": "MIT" }, "node_modules/@zumer/snapdom": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@zumer/snapdom/-/snapdom-2.0.1.tgz", - "integrity": "sha512-78/qbYl2FTv4H6qaXcNfAujfIOSzdvs83NW63VbyC9QA3sqNPfPvhn4xYMO6Gy11hXwJUEhd0z65yKiNzDwy9w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@zumer/snapdom/-/snapdom-2.0.2.tgz", + "integrity": "sha512-W6quT4lMcPu8Q9O/Q6witSfc6/+xuY8C8yDoHug/+o7zYKCNE/e0I3//XsWDkyq9C0mDE0TAWF/8bwCR7x3gHQ==", "license": "MIT" }, "node_modules/accepts": { @@ -10360,9 +10461,9 @@ "license": "BSD-3-Clause" }, "node_modules/i18next": { - "version": "25.7.4", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.7.4.tgz", - "integrity": "sha512-hRkpEblXXcXSNbw8mBNq9042OEetgyB/ahc/X17uV/khPwzV+uB8RHceHh3qavyrkPJvmXFKXME2Sy1E0KjAfw==", + "version": "25.8.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.0.tgz", + "integrity": "sha512-urrg4HMFFMQZ2bbKRK7IZ8/CTE7D8H4JRlAwqA2ZwDRFfdd0K/4cdbNNLgfn9mo+I/h9wJu61qJzH7jCFAhUZQ==", "funding": [ { "type": "individual", @@ -14225,19 +14326,19 @@ } }, "node_modules/vitest": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.17.tgz", - "integrity": "sha512-FQMeF0DJdWY0iOnbv466n/0BudNdKj1l5jYgl5JVTwjSsZSlqyXFt/9+1sEyhR6CLowbZpV7O1sCHrzBhucKKg==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.0.17", - "@vitest/mocker": "4.0.17", - "@vitest/pretty-format": "4.0.17", - "@vitest/runner": "4.0.17", - "@vitest/snapshot": "4.0.17", - "@vitest/spy": "4.0.17", - "@vitest/utils": "4.0.17", + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", @@ -14265,10 +14366,10 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.17", - "@vitest/browser-preview": "4.0.17", - "@vitest/browser-webdriverio": "4.0.17", - "@vitest/ui": "4.0.17", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", "happy-dom": "*", "jsdom": "*" }, @@ -14303,9 +14404,9 @@ } }, "node_modules/vitest-browser-react": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/vitest-browser-react/-/vitest-browser-react-2.0.2.tgz", - "integrity": "sha512-zuSgTe/CKODU3ip+w4ls6Qm4xZ9+A4OHmDf0obt/mwAqavpOtqtq2YcioZt8nfDQE50EWmhdnRfDmpS1jCsbTQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vitest-browser-react/-/vitest-browser-react-2.0.4.tgz", + "integrity": "sha512-FQq2z519Bwp/rANaQXU+ox7M4d0q/bTQkF2pgwRAehE+pqJ6myYOLp+P2Dy2kuk+K4IQJHMyijMCSQ1da/xW8w==", "dev": true, "license": "MIT", "funding": { @@ -14599,9 +14700,9 @@ } }, "node_modules/zod": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", - "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index ea0a38380..8fb846883 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,8 @@ "sync-weapon-params": "tsx scripts/sync-weapon-params.ts" }, "dependencies": { - "@aws-sdk/client-s3": "^3.971.0", - "@aws-sdk/lib-storage": "^3.971.0", + "@aws-sdk/client-s3": "^3.974.0", + "@aws-sdk/lib-storage": "^3.974.0", "@date-fns/tz": "^1.4.1", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", @@ -49,7 +49,7 @@ "@react-router/serve": "^7.12.0", "@remix-run/form-data-parser": "^0.14.0", "@tldraw/tldraw": "^3.12.1", - "@zumer/snapdom": "^2.0.1", + "@zumer/snapdom": "^2.0.2", "aws-sdk": "^2.1693.0", "better-sqlite3": "^12.6.2", "clsx": "^2.1.1", @@ -57,7 +57,7 @@ "date-fns": "^4.1.0", "edmonds-blossom-fixed": "^1.0.1", "gray-matter": "^4.0.3", - "i18next": "^25.7.4", + "i18next": "^25.8.0", "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^3.0.2", "ics": "^3.8.1", @@ -66,7 +66,7 @@ "kysely": "^0.28.10", "lru-cache": "^11.2.4", "lucide-react": "^0.562.0", - "markdown-to-jsx": "^9.6.0", + "markdown-to-jsx": "^9.6.1", "nanoid": "^5.1.6", "neverthrow": "^8.2.0", "node-cron": "4.2.1", @@ -91,20 +91,20 @@ "slugify": "^1.6.6", "swr": "^2.3.8", "web-push": "^3.6.7", - "zod": "^4.3.5" + "zod": "^4.3.6" }, "devDependencies": { "@biomejs/biome": "2.3.11", "@playwright/test": "^1.57.0", "@react-router/dev": "^7.12.0", "@types/better-sqlite3": "^7.6.13", - "@types/node": "^25.0.9", + "@types/node": "^25.0.10", "@types/node-cron": "^3.0.11", "@types/nprogress": "^0.2.3", - "@types/react": "^19.2.8", + "@types/react": "^19.2.9", "@types/react-dom": "^19.2.3", "@types/web-push": "^3.6.4", - "@vitest/browser-playwright": "^4.0.17", + "@vitest/browser-playwright": "^4.0.18", "@vitest/ui": "^4.0.16", "babel-plugin-react-compiler": "^19.1.0-rc.2", "cross-env": "^10.1.0", @@ -120,6 +120,6 @@ "vite-plugin-babel": "^1.4.1", "vite-tsconfig-paths": "^6.0.4", "vitest": "^4.0.16", - "vitest-browser-react": "^2.0.2" + "vitest-browser-react": "^2.0.4" } }