From d139f0ac330284851d90feb61992e97c5f067661 Mon Sep 17 00:00:00 2001 From: Jarrod Lowe <41766555+jarrod-lowe@users.noreply.github.com> Date: Sat, 31 Aug 2024 19:44:07 +1200 Subject: [PATCH] UI and GraphQL to create and list games (#61) * Workflow fix * Can display and create game * Codacy happiness * Get DOM tests running under Jest * Codacy happiness --- .../workflows/environment-main-deploy.yaml | 6 +- .gitignore | 2 + Makefile | 1 + appsync/graphql.ts | 16 +- appsync/schema.ts | 12 +- design/PLANNING.md | 45 +- graphql/graphql.mk | 8 +- graphql/mutation/createGame/appsync.ts | 72 +- graphql/query/getGames/appsync.ts | 36 + graphql/schema.graphql | 15 +- graphql/tests/createGame.test.ts | 93 +- graphql/tsconfig.json | 3 +- terraform/module/iac-roles/policy.tf | 2 + terraform/module/wildsea/graphql.tf | 5 +- terraform/module/wildsea/table.tf | 12 + ui/index.html | 8 +- ui/package-lock.json | 1077 +++++++++++++++++ ui/package.json | 1 + ui/public/style.css | 78 ++ ui/src/game.ts | 138 +++ ui/src/main.ts | 4 +- ui/tests/game.test.ts | 59 + ui/ui.mk | 2 +- 23 files changed, 1620 insertions(+), 75 deletions(-) create mode 100644 graphql/query/getGames/appsync.ts create mode 100644 ui/public/style.css create mode 100644 ui/src/game.ts create mode 100644 ui/tests/game.test.ts diff --git a/.github/workflows/environment-main-deploy.yaml b/.github/workflows/environment-main-deploy.yaml index 67fc8e28..c4061c47 100644 --- a/.github/workflows/environment-main-deploy.yaml +++ b/.github/workflows/environment-main-deploy.yaml @@ -30,10 +30,10 @@ jobs: run: npm install -g esbuild - name: Compile, check and test graphql - run: IN_PIPELINE=true make graphql + run: IN_PIPELINE=true ENVIRONMENT=${{ vars.ENVIRONMENT }} make graphql - name: Test ui - run: IN_PIPELINE=true make ui-test + run: IN_PIPELINE=true ENVIRONMENT=${{ vars.ENVIRONMENT }} make ui-test - name: Run codacy-coverage-reporter uses: codacy/codacy-coverage-reporter-action@a38818475bb21847788496e9f0fddaa4e84955ba @@ -67,4 +67,4 @@ jobs: - name: Push UI run: | - IN_PIPELINE=true make ui/.push-${{ vars.ENVIRONMENT }} + IN_PIPELINE=true ENVIRONMENT=${{ vars.ENVIRONMENT }} make ui/.push-${{ vars.ENVIRONMENT }} diff --git a/.gitignore b/.gitignore index ded2e5cc..76497bf7 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ graphql/node_modules graphql/mutation/*/appsync.js graphql/query/*/appsync.js graphql/coverage/ +graphql/environment.json appsync/node_modules appsync/.graphqlconfig.yml @@ -51,6 +52,7 @@ ui/config/* !ui/config/.emptyDir ui/public/* !ui/public/.emptyDir +!ui/public/style.css ui/dist/* !ui/dist/.emptyDir ui/.push diff --git a/Makefile b/Makefile index c31ffc8f..a3f79c47 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ default: all +ENVIRONMENT ?= dev TERRAFORM_ENVIRONMENTS := aws github wildsea aws-dev wildsea-dev TERRAFOM_VALIDATE := $(addsuffix /.validate,$(addprefix terraform/environment/, $(TERRAFORM_ENVIRONMENTS))) TERRAFORM_MODULES := iac-roles oidc state-bucket wildsea diff --git a/appsync/graphql.ts b/appsync/graphql.ts index 5f8992a9..a70939ad 100644 --- a/appsync/graphql.ts +++ b/appsync/graphql.ts @@ -30,16 +30,25 @@ export type CreateGameInput = { export type Game = { __typename?: 'Game'; createdAt: Scalars['AWSDateTime']['output']; - description?: Maybe; fireflyUserId: Scalars['ID']['output']; - id: Scalars['ID']['output']; - name: Scalars['String']['output']; + gameDescription?: Maybe; + gameId: Scalars['ID']['output']; + gameName: Scalars['String']['output']; players?: Maybe>; privateNotes?: Maybe; publicNotes?: Maybe; + type: Scalars['String']['output']; updatedAt: Scalars['AWSDateTime']['output']; }; +export type GameSummary = { + __typename?: 'GameSummary'; + gameDescription: Scalars['String']['output']; + gameId: Scalars['ID']['output']; + gameName: Scalars['String']['output']; + type: Scalars['String']['output']; +}; + export type Mutation = { __typename?: 'Mutation'; createGame: Game; @@ -53,6 +62,7 @@ export type MutationCreateGameArgs = { export type Query = { __typename?: 'Query'; getGame: Game; + getGames?: Maybe>; }; diff --git a/appsync/schema.ts b/appsync/schema.ts index 8bdcb15b..a1ad081f 100644 --- a/appsync/schema.ts +++ b/appsync/schema.ts @@ -3,7 +3,15 @@ export const getGameQuery = ` query getGame($id: ID!) { getGame(id: $id) { - id name description publicNotes privateNotes fireflyUserId players createdAt updatedAt + gameId gameName gameDescription publicNotes privateNotes fireflyUserId players createdAt updatedAt type + } + } + `; + + export const getGamesQuery = ` + query getGames { + getGames { + gameId gameName gameDescription type } } `; @@ -13,7 +21,7 @@ export const createGameMutation = ` mutation createGame($input: CreateGameInput!) { createGame(input: $input) { - id name description publicNotes privateNotes fireflyUserId players createdAt updatedAt + gameId gameName gameDescription publicNotes privateNotes fireflyUserId players createdAt updatedAt type } } `; diff --git a/design/PLANNING.md b/design/PLANNING.md index 067539d2..f56a5913 100644 --- a/design/PLANNING.md +++ b/design/PLANNING.md @@ -296,11 +296,23 @@ Assistant: 2. **Game** * `PK`: `GAME#` * `SK`: `METADATA` - * Other fields: `name`, `description`, `publicNotes`, `privateNotes`, `fireflyUserId`, `createdAt`, `updatedAt`, `GSI2PK`, `GSI2SK` + * `gameId`: gameId + * Other fields: `gameName`, `gameDescription`, `publicNotes`, `privateNotes`, `fireflyUserId`, `createdAt`, `updatedAt`, `GSI1PK` -3. **Player Sheet** +3. **GM Info** * `PK`: `GAME#` - * `SK`: `PLAYER#` + * `SK`: `PLAYER#GM#` + * `GSI1PK`: `USER#` + * `userId`: userId + * `gameId`: gameId + * gameName and gameDescription: Duplicated from game + +4. **Player Sheet** + * `PK`: `GAME#` + * `SK`: `PLAYER#PC#` + * `GSI1PK`: `USER#` + * `userId`: userId + * `gameId`: gameId * `characterName` (String) * `pronouns` (String) * `bloodline` (String) @@ -327,9 +339,10 @@ Assistant: * Each map in the list represents an aspect and contains fields like `name` (String), `type` (String: "Trait", "Gear", or "Companion"), `track` (Map with fields like `length` (Number) and `currentValue` (Number)), `description` (String) * `temporaryTracks` (List of Maps) * Each map in the list represents a temporary track and contains fields like `name` (String), `type` (String: "Benefit", "Injury", or "Track"), `track` (Map with fields like `length` (Number) and `currentValue` (Number)), `description` (String) - * Other fields: `createdAt`, `updatedAt`, `GSI1PK`, `GSI1SK` + * Other fields: `createdAt`, `updatedAt` + * gameName and gameDescription: Duplicated from game -4. **Ship Sheet** +5. **Ship Sheet** * `PK`: `GAME#` * `SK`: `SHIP#` * `name` (String) @@ -353,23 +366,23 @@ Assistant: * `passengers` (List of Strings) * Other fields: `createdAt`, `updatedAt` -5. **Clock** +6. **Clock** * `PK`: `GAME#` * `SK`: `CLOCK#` * Other fields: `name`, `lengths`, `visibility`, `gmNotes`, `createdAt`, `updatedAt` -6. **Dice Roll** +7. **Dice Roll** * `PK`: `GAME#` * `SK`: `DICEROLL#` * Other fields: `playerCharacterName`, `numRolled`, `numCut`, `result`, `twist`, `rollValues`, `createdAt` -7. **Seed Data** +8. **Seed Data** * `PK`: `SEED#` * `SK`: `` * Other fields: vary based on the type, e.g., `name`, `description`, `trackLength`, etc. * ``'s include: `MIRE`, `EDGE`, `SKILL`, `LANGUAGE`, `RESOURCE`, `ASPECT`, `RATING`, `DESIGN`, `UNDERCREW`, `FITTINGTYPE` -8. **Level Name** +9. **Level Name** * `PK`: `LEVELNAME#` * `SK`: `` * Other fields: `name` @@ -384,18 +397,10 @@ No LSIs are required for this table design. 1. **GSI1: UserGameIndex** * Partition Key: `GSI1PK` (String) * Sort Key: `GSI1SK` (String) - * For Player Sheet records: + * For GM or Player Sheet records: * `GSI1PK`: `USER#` - * `GSI1SK`: `GAME#` - * This index allows us to query all games where the user is a player. - -2. **GSI2: FireflyGameIndex** - * Partition Key: `GSI2PK` (String) - * Sort Key: `GSI2SK` (String) - * For Game records: - * `GSI2PK`: `USER#` - * `GSI2SK`: `GAME#` - * This index allows us to query all games where the user is the firefly. + * The GSI SK is `PK` + * This index allows us to query all games where the user is a firefly or player. In this updated design, we have two generic record types: diff --git a/graphql/graphql.mk b/graphql/graphql.mk index 890d6fb8..10421e24 100644 --- a/graphql/graphql.mk +++ b/graphql/graphql.mk @@ -1,4 +1,4 @@ -graphql/%/appsync.js: graphql/node_modules graphql/%/appsync.ts appsync/graphql.ts appsync/schema.ts +graphql/%/appsync.js: graphql/node_modules graphql/%/appsync.ts appsync/graphql.ts appsync/schema.ts graphql/environment.json cd graphql && \ esbuild $*/*.ts \ --bundle \ @@ -10,6 +10,10 @@ graphql/%/appsync.js: graphql/node_modules graphql/%/appsync.ts appsync/graphql. --sources-content=false \ --outdir=$* +.PRECIOUS: graphql/environment.json +graphql/environment.json: + echo '{"name":"$(ENVIRONMENT)"}' >$@ + graphql/node_modules: graphql/package.json cd graphql && npm install && npm ci \ @@ -22,7 +26,7 @@ graphql: $(GRAPHQL_JS) appsync/graphql.ts appsync/schema.ts graphql-test echo $(GRAPHQL_JS) .PHONY: graphql-test -graphql-test: graphql/node_modules appsync/graphql.ts appsync/schema.ts +graphql-test: graphql/node_modules appsync/graphql.ts appsync/schema.ts graphql/environment.json if [ -z "$(IN_PIPELINE)" ] ; then \ docker run --rm -it --user $$(id -u):$$(id -g) -v $(PWD):/app -w /app/graphql --entrypoint ./node_modules/jest/bin/jest.js node:20 --coverage ; \ else \ diff --git a/graphql/mutation/createGame/appsync.ts b/graphql/mutation/createGame/appsync.ts index be739a2c..2514f6f9 100644 --- a/graphql/mutation/createGame/appsync.ts +++ b/graphql/mutation/createGame/appsync.ts @@ -1,8 +1,7 @@ import { util, Context, AppSyncIdentityCognito } from "@aws-appsync/utils"; -import type { - DynamoDBPutItemRequest, - PutItemInputAttributeMap, -} from "@aws-appsync/utils/lib/resolver-return-types"; +import type { PutItemInputAttributeMap } from "@aws-appsync/utils/lib/resolver-return-types"; +import environment from "../../environment.json"; +import { Game } from "../../../appsync/graphql"; /** * A CreateGameInput creates a Game. @@ -16,9 +15,7 @@ interface CreateGameInput { description?: string; } -export function request( - context: Context<{ input: CreateGameInput }>, -): DynamoDBPutItemRequest { +export function request(context: Context<{ input: CreateGameInput }>): unknown { if (!context.identity) { util.error("Unauthorized: Identity information is missing." as string); } @@ -31,25 +28,66 @@ export function request( const input = context.arguments.input; const id = util.autoId(); const timestamp = util.time.nowISO8601(); - return { - operation: "PutItem", + + context.stash.record = { + gameName: input.name, + gameDescription: input.description, + gameId: id, + fireflyUserId: identity.sub, + // players: no value yet + createdAt: timestamp, + updatedAt: timestamp, + type: "GAME", + }; + + const gameItem = { key: util.dynamodb.toMapValues({ PK: "GAME#" + id, SK: "GAME" }), + operation: "PutItem", + table: "Wildsea-" + environment.name, + attributeValues: util.dynamodb.toMapValues( + context.stash.record, + ) as PutItemInputAttributeMap, + }; + + const fireflyItem = { + key: util.dynamodb.toMapValues({ + PK: "GAME#" + id, + SK: "PLAYER#GM#" + identity.sub, + }), + operation: "PutItem", + table: "Wildsea-" + environment.name, attributeValues: util.dynamodb.toMapValues({ - name: input.name, - description: input.description, - id: id, - fireflyUserId: identity.sub, - // players: no value yet + userId: identity.sub, + gameId: id, + gameName: input.name, + gameDescription: input.description, + GSI1PK: "USER#" + identity.sub, createdAt: timestamp, updatedAt: timestamp, + type: "CHARACTER", }) as PutItemInputAttributeMap, }; + + return { + operation: "TransactWriteItems", + transactItems: [gameItem, fireflyItem], + }; } -export function response(context: Context): unknown { +export function response(context: Context): Game | null { if (context.error) { util.appendError(context.error.message, context.error.type, context.result); - return; + return null; } - return context.result; + return { + PK: context.result.keys[0].PK, + SK: context.result.keys[0].SK, + gameName: context.stash.record.gameName, + gameDescription: context.stash.record.gameDescription, + gameId: context.stash.record.gameId, + fireflyUserId: context.stash.record.fireflyUserId, + createdAt: context.stash.record.createdAt, + updatedAt: context.stash.record.updatedAt, + type: context.stash.record.type, + } as Game; } diff --git a/graphql/query/getGames/appsync.ts b/graphql/query/getGames/appsync.ts new file mode 100644 index 00000000..c687bf61 --- /dev/null +++ b/graphql/query/getGames/appsync.ts @@ -0,0 +1,36 @@ +import { util, Context, AppSyncIdentityCognito } from "@aws-appsync/utils"; +import type { DynamoDBQueryRequest } from "@aws-appsync/utils/lib/resolver-return-types"; +import type { GameSummary } from "../../../appsync/graphql"; + +export function request(context: Context): DynamoDBQueryRequest { + if (!context.identity) { + util.error("Unauthorized: Identity information is missing." as string); + } + + const identity = context.identity as AppSyncIdentityCognito; + if (!identity.sub) { + util.error("Unauthorized: User ID is missing." as string); + } + + return { + operation: "Query", + index: "GSI1", + query: { + expression: "#gsi1pk = :gsi1pk", + expressionNames: { + "#gsi1pk": "GSI1PK", + }, + expressionValues: { + ":gsi1pk": util.dynamodb.toString("USER#" + identity.sub), + }, + }, + }; +} + +export function response(context: Context): GameSummary[] { + if (context.error) { + util.appendError(context.error.message, context.error.type, context.result); + return []; + } + return context.result.items as GameSummary[]; +} diff --git a/graphql/schema.graphql b/graphql/schema.graphql index 0f82e346..e2c6c348 100644 --- a/graphql/schema.graphql +++ b/graphql/schema.graphql @@ -22,6 +22,7 @@ type Mutation { type Query { getGame(id: ID!): Game! @aws_cognito_user_pools + getGames: [GameSummary!] @aws_cognito_user_pools } input CreateGameInput { @@ -30,9 +31,9 @@ input CreateGameInput { } type Game @aws_cognito_user_pools { - id: ID! - name: String! - description: String + gameId: ID! + gameName: String! + gameDescription: String publicNotes: String privateNotes: String fireflyUserId: ID! @@ -42,4 +43,12 @@ type Game @aws_cognito_user_pools { # clocks: [Clock!]! createdAt: AWSDateTime! updatedAt: AWSDateTime! + type: String! +} + +type GameSummary @aws_cognito_user_pools { + gameId: ID! + gameName: String! + gameDescription: String! + type: String! } diff --git a/graphql/tests/createGame.test.ts b/graphql/tests/createGame.test.ts index fc4a140f..27fc8f95 100644 --- a/graphql/tests/createGame.test.ts +++ b/graphql/tests/createGame.test.ts @@ -1,6 +1,9 @@ import { awsAppsyncUtilsMock } from "./mocks"; jest.mock("@aws-appsync/utils", () => awsAppsyncUtilsMock); +jest.mock("../environment.json", () => ({ + name: "MOCK", +})); import { request, response } from "../mutation/createGame/appsync"; import { @@ -15,7 +18,7 @@ describe("request function", () => { jest.clearAllMocks(); }); - it("should return a valid DynamoDBPutItemRequest when context is valid", () => { + it("should return a valid DynamoDBTransactWriteItem when context is valid", () => { // Arrange const mockContext: Context<{ input: { name: string; description?: string }; @@ -65,19 +68,44 @@ describe("request function", () => { // Assert expect(result).toEqual({ - operation: "PutItem", - key: { - PK: { S: `GAME#${mockId}` }, - SK: { S: "GAME" }, - }, - attributeValues: { - name: { S: "Test Game" }, - description: { S: "Test Description" }, - id: { S: mockId }, - fireflyUserId: { S: "1234-5678-91011" }, - createdAt: { S: mockTimestamp }, - updatedAt: { S: mockTimestamp }, - }, + operation: "TransactWriteItems", + transactItems: [ + { + operation: "PutItem", + key: { + PK: { S: `GAME#${mockId}` }, + SK: { S: "GAME" }, + }, + table: "Wildsea-MOCK", + attributeValues: { + gameName: { S: "Test Game" }, + gameDescription: { S: "Test Description" }, + gameId: { S: mockId }, + fireflyUserId: { S: "1234-5678-91011" }, + createdAt: { S: mockTimestamp }, + updatedAt: { S: mockTimestamp }, + type: { S: "GAME" }, + }, + }, + { + operation: "PutItem", + key: { + PK: { S: `GAME#${mockId}` }, + SK: { S: `PLAYER#GM#1234-5678-91011` }, + }, + table: "Wildsea-MOCK", + attributeValues: { + userId: { S: "1234-5678-91011" }, + gameId: { S: mockId }, + gameName: { S: "Test Game" }, + gameDescription: { S: "Test Description" }, + GSI1PK: { S: "USER#1234-5678-91011" }, + createdAt: { S: mockTimestamp }, + updatedAt: { S: mockTimestamp }, + type: { S: "CHARACTER" }, + }, + }, + ], }); }); @@ -224,8 +252,29 @@ describe("response function", () => { selectionSetList: [], selectionSetGraphQL: "", } as Info, - result: { someKey: "someValue" }, - stash: {}, + result: { + keys: [ + { + PK: "testPK", + SK: "testSK", + }, + { + PK: "testPK2", + SK: "testSK2", + }, + ], + }, + stash: { + record: { + gameName: "testGameName", + gameDescription: "testGameDescription", + gameId: "testGameId", + fireflyUserId: "testFireflyUserId", + createdAt: "testCreatedAt", + updatedAt: "testUpdatedAt", + type: "testType", + }, + }, prev: undefined, request: { headers: {}, @@ -237,6 +286,16 @@ describe("response function", () => { const result = response(mockContext); // Assert - expect(result).toEqual({ someKey: "someValue" }); + expect(result).toEqual({ + PK: "testPK", + SK: "testSK", + gameName: "testGameName", + gameDescription: "testGameDescription", + gameId: "testGameId", + fireflyUserId: "testFireflyUserId", + createdAt: "testCreatedAt", + updatedAt: "testUpdatedAt", + type: "testType", + }); }); }); diff --git a/graphql/tsconfig.json b/graphql/tsconfig.json index 1ee1a4f3..0909380a 100644 --- a/graphql/tsconfig.json +++ b/graphql/tsconfig.json @@ -25,7 +25,6 @@ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ "module": "commonjs", /* Specify what module code is generated. */ // "rootDir": "./", /* Specify the root folder within your source files. */ // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ @@ -40,7 +39,7 @@ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ + "resolveJsonModule": true, /* Enable importing .json files. */ // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ diff --git a/terraform/module/iac-roles/policy.tf b/terraform/module/iac-roles/policy.tf index 0bf3e71c..54ef9ecc 100644 --- a/terraform/module/iac-roles/policy.tf +++ b/terraform/module/iac-roles/policy.tf @@ -240,6 +240,7 @@ data "aws_iam_policy_document" "rw" { actions = [ "iam:CreateRole", "iam:CreatePolicy", + "iam:CreatePolicyVersion", "iam:DeleteRole", "iam:DeletePolicy", "iam:TagRole", @@ -498,6 +499,7 @@ data "aws_iam_policy_document" "rw_boundary" { actions = [ "iam:CreateRole", "iam:CreatePolicy", + "iam:CreatePolicyVersion", "iam:DeleteRole", "iam:DeletePolicy", "iam:TagRole", diff --git a/terraform/module/wildsea/graphql.tf b/terraform/module/wildsea/graphql.tf index fdac257a..cd321750 100644 --- a/terraform/module/wildsea/graphql.tf +++ b/terraform/module/wildsea/graphql.tf @@ -218,7 +218,10 @@ data "aws_iam_policy_document" "graphql_datasource" { "dynamodb:UpdateutItem", "dynamodb:Query", ] - resources = [aws_dynamodb_table.table.arn] + resources = [ + aws_dynamodb_table.table.arn, + "${aws_dynamodb_table.table.arn}/index/*", + ] } } diff --git a/terraform/module/wildsea/table.tf b/terraform/module/wildsea/table.tf index 61795358..5f89419f 100644 --- a/terraform/module/wildsea/table.tf +++ b/terraform/module/wildsea/table.tf @@ -17,6 +17,18 @@ resource "aws_dynamodb_table" "table" { type = "S" } + attribute { + name = "GSI1PK" + type = "S" + } + + global_secondary_index { + name = "GSI1" + hash_key = "GSI1PK" + range_key = "PK" + projection_type = "ALL" + } + point_in_time_recovery { enabled = true } diff --git a/ui/index.html b/ui/index.html index 6f38f271..0ec0c71c 100644 --- a/ui/index.html +++ b/ui/index.html @@ -1,8 +1,9 @@ - - + + + Wildsea @@ -10,7 +11,8 @@ -
+ + diff --git a/ui/package-lock.json b/ui/package-lock.json index 208fef40..e3443b13 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -11,6 +11,7 @@ "@types/jest": "29.5.12", "@types/node": "22.5.1", "jest": "29.7.0", + "jest-environment-jsdom": "^29.7.0", "ts-jest": "29.2.5", "vite": "5.4.2" } @@ -3460,6 +3461,16 @@ "node": ">=16.0.0" } }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/@types/aws-lambda": { "version": "8.10.143", "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.143.tgz", @@ -3555,6 +3566,18 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, "node_modules/@types/node": { "version": "22.5.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.1.tgz", @@ -3570,6 +3593,13 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/uuid": { "version": "9.0.8", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", @@ -3590,6 +3620,64 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -3657,6 +3745,13 @@ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "dev": true }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/aws-amplify": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/aws-amplify/-/aws-amplify-6.5.3.tgz", @@ -4035,6 +4130,19 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4082,6 +4190,48 @@ "node": ">= 8" } }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/debug": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", @@ -4099,6 +4249,13 @@ } } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true, + "license": "MIT" + }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -4122,6 +4279,16 @@ "node": ">=0.10.0" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -4140,6 +4307,20 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "license": "MIT", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -4179,6 +4360,19 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -4244,6 +4438,28 @@ "node": ">=8" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -4257,6 +4473,26 @@ "node": ">=4" } }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -4396,6 +4632,21 @@ "node": ">=8" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4529,12 +4780,54 @@ "node": ">= 0.4" } }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -4544,6 +4837,19 @@ "node": ">=10.17.0" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/idb": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/idb/-/idb-5.0.6.tgz", @@ -4670,6 +4976,13 @@ "node": ">=0.12.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -4981,6 +5294,34 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-environment-jsdom": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", + "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0", + "jsdom": "^20.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/jest-environment-node": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", @@ -5383,6 +5724,52 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -5530,6 +5917,29 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -5614,6 +6024,13 @@ "node": ">=8" } }, + "node_modules/nwsapi": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz", + "integrity": "sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==", + "dev": true, + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5707,6 +6124,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5846,6 +6276,23 @@ "node": ">= 6" } }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -5862,6 +6309,13 @@ } ] }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -5877,6 +6331,13 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -5967,6 +6428,26 @@ "tslib": "^2.1.0" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -6162,6 +6643,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -6203,6 +6691,35 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/ts-jest": { "version": "29.2.5", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", @@ -6317,6 +6834,16 @@ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", @@ -6347,6 +6874,17 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -6432,6 +6970,19 @@ } } }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -6441,6 +6992,53 @@ "makeerror": "1.0.12" } }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6492,6 +7090,45 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -9166,6 +9803,12 @@ "tslib": "^2.6.2" } }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, "@types/aws-lambda": { "version": "8.10.143", "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.143.tgz", @@ -9261,6 +9904,17 @@ "pretty-format": "^29.0.0" } }, + "@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, "@types/node": { "version": "22.5.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.1.tgz", @@ -9276,6 +9930,12 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true + }, "@types/uuid": { "version": "9.0.8", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", @@ -9296,6 +9956,46 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true + }, + "acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "requires": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "acorn-walk": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", + "dev": true, + "requires": { + "acorn": "^8.11.0" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -9345,6 +10045,12 @@ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, "aws-amplify": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/aws-amplify/-/aws-amplify-6.5.3.tgz", @@ -9613,6 +10319,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -9651,6 +10366,40 @@ "which": "^2.0.1" } }, + "cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + } + }, "debug": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", @@ -9660,6 +10409,12 @@ "ms": "2.1.2" } }, + "decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, "dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -9673,6 +10428,12 @@ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -9685,6 +10446,15 @@ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true }, + "domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "requires": { + "webidl-conversions": "^7.0.0" + } + }, "ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -9712,6 +10482,12 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -9764,12 +10540,36 @@ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true }, + "escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "source-map": "~0.6.1" + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, "execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -9877,6 +10677,17 @@ "path-exists": "^4.0.0" } }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -9966,18 +10777,57 @@ "function-bind": "^1.1.2" } }, + "html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "requires": { + "whatwg-encoding": "^2.0.0" + } + }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, "idb": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/idb/-/idb-5.0.6.tgz", @@ -10058,6 +10908,12 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -10280,6 +11136,22 @@ "pretty-format": "^29.7.0" } }, + "jest-environment-jsdom": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", + "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0", + "jsdom": "^20.0.0" + } + }, "jest-environment-node": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", @@ -10601,6 +11473,40 @@ "esprima": "^4.0.0" } }, + "jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + } + }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -10714,6 +11620,21 @@ "picomatch": "^2.3.1" } }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -10774,6 +11695,12 @@ "path-key": "^3.0.0" } }, + "nwsapi": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz", + "integrity": "sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==", + "dev": true + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -10839,6 +11766,15 @@ "lines-and-columns": "^1.1.6" } }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "requires": { + "entities": "^4.4.0" + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -10930,12 +11866,30 @@ "sisteransi": "^1.0.5" } }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true + }, "pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "dev": true }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -10948,6 +11902,12 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -11014,6 +11974,21 @@ "tslib": "^2.1.0" } }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, "semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -11158,6 +12133,12 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, "test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -11190,6 +12171,27 @@ "is-number": "^7.0.0" } }, + "tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + } + }, + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, "ts-jest": { "version": "29.2.5", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", @@ -11250,6 +12252,12 @@ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true }, + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true + }, "update-browserslist-db": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", @@ -11260,6 +12268,16 @@ "picocolors": "^1.0.1" } }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -11288,6 +12306,15 @@ "rollup": "^4.20.0" } }, + "w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "requires": { + "xml-name-validator": "^4.0.0" + } + }, "walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -11297,6 +12324,37 @@ "makeerror": "1.0.12" } }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + } + }, + "whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -11333,6 +12391,25 @@ "signal-exit": "^3.0.7" } }, + "ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "requires": {} + }, + "xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/ui/package.json b/ui/package.json index f14ccbd4..819cf783 100644 --- a/ui/package.json +++ b/ui/package.json @@ -11,6 +11,7 @@ "@types/jest": "29.5.12", "@types/node": "22.5.1", "jest": "29.7.0", + "jest-environment-jsdom": "29.7.0", "ts-jest": "29.2.5", "vite": "5.4.2" } diff --git a/ui/public/style.css b/ui/public/style.css new file mode 100644 index 00000000..278cc073 --- /dev/null +++ b/ui/public/style.css @@ -0,0 +1,78 @@ +.hidden { + display: none; +} + +.gameslist { + font-family: Arial, sans-serif; + max-width: 600px; + margin: 0 auto; + padding: 20px; + background-color: #f2f2f2; + border-radius: 5px; + box-shadow: 0 2px 5px rgba(0 0 0 0.1); +} + +.allgames { + display: flex; + flex-wrap: wrap; + justify-content: space-between; +} + +.joingame, +.newgame { + width: 48%; + background-color: #fff; + padding: 20px; + border-radius: 5px; + box-shadow: 0 2px 5px rgba(0 0 0 0.1); +} + +h1 { + font-size: 24px; + margin-bottom: 10px; +} + +ul { + list-style-type: none; + padding: 0; +} + +li { + padding: 10px; + border-bottom: 1px solid #ddd; +} + +li:last-child { + border-bottom: none; +} + +form { + display: flex; + flex-direction: column; +} + +label { + font-weight: bold; + margin-bottom: 5px; +} + +input, +textarea { + padding: 5px; + margin-bottom: 10px; + border: 1px solid #ddd; + border-radius: 3px; +} + +button { + padding: 10px; + background-color: #4CAF50; + color: #fff; + border: none; + border-radius: 3px; + cursor: pointer; +} + +button:hover { + background-color: #45a049; +} diff --git a/ui/src/game.ts b/ui/src/game.ts new file mode 100644 index 00000000..d185a14f --- /dev/null +++ b/ui/src/game.ts @@ -0,0 +1,138 @@ +import { generateClient } from "aws-amplify/api"; +import { createGameMutation, getGamesQuery } from "../../appsync/schema"; +import { GameSummary, CreateGameInput, Game } from "../../appsync/graphql"; +import { GraphQLResult } from "@aws-amplify/api-graphql"; + +export async function fetchGames(): Promise { + const client = generateClient(); + const response = await client.graphql({ + query: getGamesQuery.replace(/\(\)/g, ''), + }) as GraphQLResult<{ getGames: GameSummary[] }>; + + return response.data.getGames; +} + +export function createGameElement(game: GameSummary): HTMLLIElement { + const li = document.createElement('li'); + li.textContent = `${game.gameName} - ${game.gameDescription}`; + return li; +} + +export function createGamesList(games: GameSummary[]): HTMLUListElement { + const ul = document.createElement('ul'); + for (const game of games) { + const li = createGameElement(game); + ul.appendChild(li); + } + return ul; +} + +export function createNewGameForm(): HTMLFormElement { + const form = document.createElement('form'); + + const gameNameLabel = document.createElement('label'); + gameNameLabel.setAttribute('for', 'gameName'); + gameNameLabel.textContent = 'Game Name:'; + + const gameNameInput = document.createElement('input'); + gameNameInput.setAttribute('type', 'text'); + gameNameInput.setAttribute('id', 'gameName'); + gameNameInput.setAttribute('name', 'gameName'); + gameNameInput.required = true; + + const gameDescriptionLabel = document.createElement('label'); + gameDescriptionLabel.setAttribute('for', 'gameDescription'); + gameDescriptionLabel.textContent = 'Game Description:'; + + const gameDescriptionTextarea = document.createElement('textarea'); + gameDescriptionTextarea.setAttribute('id', 'gameDescription'); + gameDescriptionTextarea.setAttribute('name', 'gameDescription'); + gameDescriptionTextarea.required = true; + + const submitButton = document.createElement('button'); + submitButton.setAttribute('type', 'submit'); + submitButton.textContent = 'Create Game'; + + form.addEventListener('submit', handleCreateGame); + form.appendChild(gameNameLabel); + form.appendChild(gameNameInput); + form.appendChild(gameDescriptionLabel); + form.appendChild(gameDescriptionTextarea); + form.appendChild(submitButton); + + return form; +} + +export async function doCreateGame(data: CreateGameInput): Promise { + try { + const client = generateClient(); + const result = await client.graphql({ + query: createGameMutation, + variables: {input: data}, + }) as GraphQLResult<{ createGame: Game }>; + + const newGame = result.data.createGame; + const gameId = newGame.gameId; + + return gameId; + } catch (error) { + console.error('Error creating game:', error); + // Handle error + return ""; + } + +} + +export async function handleCreateGame(event: SubmitEvent) { + event.preventDefault(); + + const formData = new FormData(event.target as HTMLFormElement); + const gameName = formData.get('gameName') as string; + const gameDescription = formData.get('gameDescription') as string; + + const createGameInput: CreateGameInput = { + name: gameName, + description: gameDescription, + }; + + const gameId = await doCreateGame(createGameInput); + // Redirect to the new game page + window.location.href = `${window.location.origin}/?gameId=${gameId}`; +} + +export async function gamesScreen(): Promise { + const games = await fetchGames(); + + const gamesList = document.getElementById('gameslist'); + if (gamesList) { + const all = document.createElement('div'); + all.className = 'allgames'; + + const join = document.createElement('div'); + join.className = 'joingame'; + all.appendChild(join); + + const h1 = document.createElement('h1'); + h1.innerText = 'Available Games'; + join.appendChild(h1); + + const ul = createGamesList(games); + join.appendChild(ul); + + const newGame = document.createElement('div'); + newGame.className = 'newgame'; + + const h1b = document.createElement('h1'); + h1b.innerText = 'Create New Game'; + newGame.appendChild(h1b); + + const form = createNewGameForm(); + newGame.appendChild(form); + + all.appendChild(newGame); + + gamesList.innerHTML = ''; + gamesList.appendChild(all); + gamesList.className = 'gameslist'; + } +} diff --git a/ui/src/main.ts b/ui/src/main.ts index 754037c1..057b0c30 100644 --- a/ui/src/main.ts +++ b/ui/src/main.ts @@ -1,7 +1,7 @@ -// npm install aws-amplify import { Amplify } from "aws-amplify"; import { signInWithRedirect, signOut } from "@aws-amplify/auth"; import amplifyconfig from "./amplifyconfiguration.json"; +import { gamesScreen } from "./game"; function handleSignOutClick() { signOut(); @@ -65,6 +65,8 @@ async function amplifySetup() { async function main() { await amplifySetup(); + + await gamesScreen(); } if (typeof window !== "undefined") { diff --git a/ui/tests/game.test.ts b/ui/tests/game.test.ts new file mode 100644 index 00000000..1e4dcd85 --- /dev/null +++ b/ui/tests/game.test.ts @@ -0,0 +1,59 @@ +/** + * @jest-environment jsdom + */ + +import { createGameElement, createGamesList, createNewGameForm } from "../src/game"; +import { GameSummary } from '../../appsync/graphql'; + +describe('createGameElement', () => { + it('should create a game element correctly', () => { + const game: GameSummary = { + __typename: 'GameSummary', + gameName: 'Test Game', + gameDescription: 'Test Description', + gameId: '1', + type: 'Test Type', + }; + + const li = createGameElement(game); + expect(li.textContent).toBe(`${game.gameName} - ${game.gameDescription}`); + }); +}); + +describe('createGamesList', () => { + it('should create a games list correctly', () => { + const games: GameSummary[] = [ + { + __typename: 'GameSummary', + gameName: 'Game 1', + gameDescription: 'Description 1', + gameId: '1', + type: 'Type 1', + }, + { + __typename: 'GameSummary', + gameName: 'Game 2', + gameDescription: 'Description 2', + gameId: '2', + type: 'Type 2', + }, + ]; + + const ul = createGamesList(games); + const liElements = ul.querySelectorAll('li'); + expect(liElements.length).toBe(games.length); + liElements.forEach((li, index) => { + expect(li.textContent).toBe( + `${games[index].gameName} - ${games[index].gameDescription}` + ); + }); + }); +}); + +describe('createNewGameForm', () => { + it('should create a new game form correctly', () => { + const form = createNewGameForm(); + expect(form.tagName).toBe('FORM'); + expect(form.querySelectorAll('input, textarea, button').length).toBe(3); + }); +}); diff --git a/ui/ui.mk b/ui/ui.mk index e99bd1ef..fe3944c3 100644 --- a/ui/ui.mk +++ b/ui/ui.mk @@ -16,7 +16,7 @@ ui/config/config-%.json: ui/config/output-%.json jq "$(UI_JQ_FILTER)" $< >$@ .PHONY: ui-local -ui-local: ui/config/config-dev.json appsync/schema.ts appsync/graphql.ts terraform/environment/wildsea-dev/.apply +ui-local: ui/config/config-dev.json appsync/schema.ts appsync/graphql.ts terraform/environment/wildsea-dev/.apply ui/node_modules cp $< ui/public/config.json docker run --rm -it --user $$(id -u):$$(id -g) -v $(PWD):/app -w /app/ui --network host node:20 npm run dev