From 464c77f0d8f3b0d367323c3cc6ab2e4db722d6b1 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Sun, 8 Jun 2025 20:40:53 +0200 Subject: [PATCH 001/103] feat: implement basic proto file --- .../backend/wrapper/proto/wrapper.proto | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Tokenization/backend/wrapper/proto/wrapper.proto diff --git a/Tokenization/backend/wrapper/proto/wrapper.proto b/Tokenization/backend/wrapper/proto/wrapper.proto new file mode 100644 index 000000000..e65311c6e --- /dev/null +++ b/Tokenization/backend/wrapper/proto/wrapper.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; +package wrapper; + +// ====================================== +// MESSAGES +// ====================================== +// Simulates an empty message because protobuffer doesn't support void +message EmptyMessage {} + +// Message with newly generated token and target address binded to it +message NewToken { + string token = 1; + string targetAddress = 2; +} + + +// ====================================== +// SERVICES +// ====================================== + +service ClientService { + // Sends newly generated token to the client for specific Connection + rpc sendNewToken(NewToken) returns(EmptyMessage) {} + + // Informs the client about token revocation + rpc revokeToken(NewToken) returns(EmptyMessage) {} +} \ No newline at end of file From 268a99af92653627994fd90a247fbca5d77265a3 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 10 Jun 2025 11:14:57 +0200 Subject: [PATCH 002/103] feat: Duplex stream with specific payload message events. --- .../backend/wrapper/proto/wrapper.proto | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/Tokenization/backend/wrapper/proto/wrapper.proto b/Tokenization/backend/wrapper/proto/wrapper.proto index e65311c6e..bdb4d1560 100644 --- a/Tokenization/backend/wrapper/proto/wrapper.proto +++ b/Tokenization/backend/wrapper/proto/wrapper.proto @@ -1,27 +1,48 @@ syntax = "proto3"; package wrapper; +// ====================================== +// ENUMS +// ====================================== +enum MessageEvent { + // Default value, represents an empty event + EMPTY_EVENT = 0; + + // New token message type, contains a new token and target address + NEW_TOKEN = 1; + + // Revoke token message type, contains a token to be revoked + REVOKE_TOKEN = 2; +} + + // ====================================== // MESSAGES // ====================================== // Simulates an empty message because protobuffer doesn't support void message EmptyMessage {} -// Message with newly generated token and target address binded to it -message NewToken { +// Message with token and target address binded to it +message Token { string token = 1; string targetAddress = 2; } +// Stream message that can contain one of specific messages +message Payload { + MessageEvent event = 1; + oneof { + EmptyMessage emptyMessage = 2; + Token newToken = 3; + Token revokeToken = 4; + } +} + // ====================================== // SERVICES // ====================================== -service ClientService { - // Sends newly generated token to the client for specific Connection - rpc sendNewToken(NewToken) returns(EmptyMessage) {} - - // Informs the client about token revocation - rpc revokeToken(NewToken) returns(EmptyMessage) {} +service CentralSystem { + rpc ClientStream(stream Payload) returns (stream Payload); } \ No newline at end of file From 7b7888aebf0201151af33bac558c56ed635b5b1a Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 10 Jun 2025 11:18:57 +0200 Subject: [PATCH 003/103] feat: Duplex stream with specific payload message events. --- .../backend/wrapper/proto/wrapper.proto | 37 +++++++++++++++---- Tokenization/backend/wrapper/wrapper.proto | 27 ++++++++++++++ 2 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 Tokenization/backend/wrapper/wrapper.proto diff --git a/Tokenization/backend/wrapper/proto/wrapper.proto b/Tokenization/backend/wrapper/proto/wrapper.proto index e65311c6e..bdb4d1560 100644 --- a/Tokenization/backend/wrapper/proto/wrapper.proto +++ b/Tokenization/backend/wrapper/proto/wrapper.proto @@ -1,27 +1,48 @@ syntax = "proto3"; package wrapper; +// ====================================== +// ENUMS +// ====================================== +enum MessageEvent { + // Default value, represents an empty event + EMPTY_EVENT = 0; + + // New token message type, contains a new token and target address + NEW_TOKEN = 1; + + // Revoke token message type, contains a token to be revoked + REVOKE_TOKEN = 2; +} + + // ====================================== // MESSAGES // ====================================== // Simulates an empty message because protobuffer doesn't support void message EmptyMessage {} -// Message with newly generated token and target address binded to it -message NewToken { +// Message with token and target address binded to it +message Token { string token = 1; string targetAddress = 2; } +// Stream message that can contain one of specific messages +message Payload { + MessageEvent event = 1; + oneof { + EmptyMessage emptyMessage = 2; + Token newToken = 3; + Token revokeToken = 4; + } +} + // ====================================== // SERVICES // ====================================== -service ClientService { - // Sends newly generated token to the client for specific Connection - rpc sendNewToken(NewToken) returns(EmptyMessage) {} - - // Informs the client about token revocation - rpc revokeToken(NewToken) returns(EmptyMessage) {} +service CentralSystem { + rpc ClientStream(stream Payload) returns (stream Payload); } \ No newline at end of file diff --git a/Tokenization/backend/wrapper/wrapper.proto b/Tokenization/backend/wrapper/wrapper.proto new file mode 100644 index 000000000..e65311c6e --- /dev/null +++ b/Tokenization/backend/wrapper/wrapper.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; +package wrapper; + +// ====================================== +// MESSAGES +// ====================================== +// Simulates an empty message because protobuffer doesn't support void +message EmptyMessage {} + +// Message with newly generated token and target address binded to it +message NewToken { + string token = 1; + string targetAddress = 2; +} + + +// ====================================== +// SERVICES +// ====================================== + +service ClientService { + // Sends newly generated token to the client for specific Connection + rpc sendNewToken(NewToken) returns(EmptyMessage) {} + + // Informs the client about token revocation + rpc revokeToken(NewToken) returns(EmptyMessage) {} +} \ No newline at end of file From 637354826f21cf43cdc4712d6cc7bebca2e2ef2f Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 10 Jun 2025 11:25:16 +0200 Subject: [PATCH 004/103] feat: Create typescript interfaces for duplex stream message model --- .../backend/wrapper/models/message.model.ts | 15 +++++++++++ Tokenization/backend/wrapper/wrapper.proto | 27 ------------------- 2 files changed, 15 insertions(+), 27 deletions(-) create mode 100644 Tokenization/backend/wrapper/models/message.model.ts delete mode 100644 Tokenization/backend/wrapper/wrapper.proto diff --git a/Tokenization/backend/wrapper/models/message.model.ts b/Tokenization/backend/wrapper/models/message.model.ts new file mode 100644 index 000000000..36cdf5e12 --- /dev/null +++ b/Tokenization/backend/wrapper/models/message.model.ts @@ -0,0 +1,15 @@ +enum DuplexMessageEvent { + EMPTY_EVENT, + NEW_TOKEN, + REVOKE_TOKEN, +} + +interface TokenMessage { + token: string; + targetAddress: string; +} + +interface DuplexMessageModel { + event: DuplexMessageEvent; + data?: TokenMessage; +} diff --git a/Tokenization/backend/wrapper/wrapper.proto b/Tokenization/backend/wrapper/wrapper.proto deleted file mode 100644 index e65311c6e..000000000 --- a/Tokenization/backend/wrapper/wrapper.proto +++ /dev/null @@ -1,27 +0,0 @@ -syntax = "proto3"; -package wrapper; - -// ====================================== -// MESSAGES -// ====================================== -// Simulates an empty message because protobuffer doesn't support void -message EmptyMessage {} - -// Message with newly generated token and target address binded to it -message NewToken { - string token = 1; - string targetAddress = 2; -} - - -// ====================================== -// SERVICES -// ====================================== - -service ClientService { - // Sends newly generated token to the client for specific Connection - rpc sendNewToken(NewToken) returns(EmptyMessage) {} - - // Informs the client about token revocation - rpc revokeToken(NewToken) returns(EmptyMessage) {} -} \ No newline at end of file From 255e56751ecbe2e44ae8e10bd76bfc86de88e4ee Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 10 Jun 2025 11:37:36 +0200 Subject: [PATCH 005/103] feat: Add description to typescript interfaces --- .../backend/wrapper/models/message.model.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Tokenization/backend/wrapper/models/message.model.ts b/Tokenization/backend/wrapper/models/message.model.ts index 36cdf5e12..3a90dd739 100644 --- a/Tokenization/backend/wrapper/models/message.model.ts +++ b/Tokenization/backend/wrapper/models/message.model.ts @@ -1,14 +1,30 @@ +/** + * @description Enum for duplex message events. + * EMPTY_EVENT: No event, used for initialization or no response. + * NEW_TOKEN: Event for replacing with newly generated token. + * REVOKE_TOKEN: Event for revoking an existing token. + */ enum DuplexMessageEvent { EMPTY_EVENT, NEW_TOKEN, REVOKE_TOKEN, } +/** + * @description Model for token generation and revocation messages. + * @property {string} token - The token to be replaced or revoked. + * @property {string} targetAddress - The address of connection binded to this token. + */ interface TokenMessage { token: string; targetAddress: string; } +/** + * @description Model for duplex stream messages between client and central system. + * @property {DuplexMessageEvent} event - The event type of the message. + * @property {TokenMessage} [data] - The data associated with the event, it may be undefined for some events. + */ interface DuplexMessageModel { event: DuplexMessageEvent; data?: TokenMessage; From 7edca58abd7e5b8434c449ceefd03ed02e00c2df Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 10 Jun 2025 11:43:36 +0200 Subject: [PATCH 006/103] feat: implement serialization and deserialization methods --- .../__tests__/serialization.utils.test.ts | 0 .../wrapper/utils/serialization.utils.ts | 64 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts create mode 100644 Tokenization/backend/wrapper/utils/serialization.utils.ts diff --git a/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts b/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts new file mode 100644 index 000000000..e69de29bb diff --git a/Tokenization/backend/wrapper/utils/serialization.utils.ts b/Tokenization/backend/wrapper/utils/serialization.utils.ts new file mode 100644 index 000000000..2f1e9bf41 --- /dev/null +++ b/Tokenization/backend/wrapper/utils/serialization.utils.ts @@ -0,0 +1,64 @@ +/** + * @description Serializes Json formatted request into binary payload with specific endpoint + * @param url - The endpoint URL to which the request is made + * @param options - Optional request options, such as headers or body + * @return {ArrayBuffer} - The serialized binary payload containing the URL and options + */ +export const serializeRequest = (url: string, options?: any) => { + const encoder = new TextEncoder(); + const contentTypeBytes = encoder.encode("application/json"); + + // build JSON data + const jsonData = options + ? { + url: url, + options: options, + } + : { + url: url, + }; + + // encode JSON + const jsonString = JSON.stringify(jsonData); + const jsonBytes = encoder.encode(jsonString); + + // Buffer following structure: + // 1 byte -> type length + // N bytes -> types + // rest -> JSON data + const buffer = new Uint8Array(1 + contentTypeBytes.length + jsonBytes.length); + + buffer[0] = contentTypeBytes.length; + buffer.set(contentTypeBytes, 1); + buffer.set(jsonBytes, 1 + contentTypeBytes.length); + + return buffer.buffer; +}; + +/** + * @description Deserializes binary payload to Json formated request + * @param payload - The binary payload to deserialize + * @return {any} - The deserialized request object containing the URL and options + */ +export const deserializeRequest = (payload: any) => { + const view = new Uint8Array(payload); + const decoder = new TextDecoder(); + + const contentTypeLength = view[0]; + const contentTypeBytes = view.slice(1, 1 + contentTypeLength); + const contentType = decoder.decode(contentTypeBytes); + + const dataBytes = view.slice(1 + contentTypeLength); + let data; + + // deserialization based on content type + switch (contentType) { + case "application/json": + data = JSON.parse(decoder.decode(dataBytes)); + break; + default: + throw new Error(`Unsupported content type: ${contentType}`); + } + + return data; +}; From 0485204c2a8147befef400b48c9d8943b3e4ff94 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 10 Jun 2025 22:29:50 +0200 Subject: [PATCH 007/103] feat: add package dependencies and write unit tests for serialization utils --- Tokenization/backend/wrapper/jest.config.js | 13 + .../backend/wrapper/package-lock.json | 4007 +++++++++++++++++ Tokenization/backend/wrapper/package.json | 20 + .../__tests__/serialization.utils.test.ts | 71 + 4 files changed, 4111 insertions(+) create mode 100644 Tokenization/backend/wrapper/jest.config.js create mode 100644 Tokenization/backend/wrapper/package-lock.json create mode 100644 Tokenization/backend/wrapper/package.json diff --git a/Tokenization/backend/wrapper/jest.config.js b/Tokenization/backend/wrapper/jest.config.js new file mode 100644 index 000000000..3a5675ad5 --- /dev/null +++ b/Tokenization/backend/wrapper/jest.config.js @@ -0,0 +1,13 @@ +/** @type {import('jest').Config} */ +const config = { + verbose: true, + transform: { + "^.+\\.ts$": ["ts-jest", { useESM: true }], + }, + extensionsToTreatAsEsm: [".ts"], + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, +}; + +export default config; diff --git a/Tokenization/backend/wrapper/package-lock.json b/Tokenization/backend/wrapper/package-lock.json new file mode 100644 index 000000000..500302d37 --- /dev/null +++ b/Tokenization/backend/wrapper/package-lock.json @@ -0,0 +1,4007 @@ +{ + "name": "grpc-wrapper", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "grpc-wrapper", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "ts-node": "^10.9.2", + "typescript": "^5.8.3" + }, + "devDependencies": { + "@types/jest": "^29.5.14", + "jest": "^29.7.0", + "ts-jest": "^29.3.4" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", + "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", + "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.4", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.4", + "@babel/types": "^7.27.3", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.0.tgz", + "integrity": "sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001721", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", + "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.166", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz", + "integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.3.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.4.tgz", + "integrity": "sha512-Iqbrm8IXOmV+ggWHOTEbjwyCf2xZlUMv5npExksXohL+tk8va4Fjhb+X2+Rt9NBmgO7bJ8WpnMLOwih/DnMlFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.2", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/Tokenization/backend/wrapper/package.json b/Tokenization/backend/wrapper/package.json new file mode 100644 index 000000000..a8b9f98a6 --- /dev/null +++ b/Tokenization/backend/wrapper/package.json @@ -0,0 +1,20 @@ +{ + "name": "grpc-wrapper", + "version": "1.0.0", + "type": "module", + "scripts": { + "test": "mocha --loader ts-node/esm \"./**/*.test.ts\"" + }, + "author": "", + "license": "ISC", + "description": "", + "devDependencies": { + "@types/jest": "^29.5.14", + "jest": "^29.7.0", + "ts-jest": "^29.3.4" + }, + "dependencies": { + "ts-node": "^10.9.2", + "typescript": "^5.8.3" + } +} diff --git a/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts b/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts index e69de29bb..655b0ccef 100644 --- a/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts +++ b/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts @@ -0,0 +1,71 @@ +import { deserializeRequest, serializeRequest } from "../serialization.utils"; +import { describe, expect, test } from "@jest/globals"; + +describe("serializeRequest", () => { + test("serializes URL and options correctly", () => { + const url = "/api/test"; + const options = { + method: "POST", + headers: { "Content-Type": "application/json" }, + }; + + const buffer = serializeRequest(url, options); + const view = new Uint8Array(buffer); + + const typeLength = view[0]; + const typeBytes = view.slice(1, 1 + typeLength); + const jsonBytes = view.slice(1 + typeLength); + + const contentType = new TextDecoder().decode(typeBytes); + const json = JSON.parse(new TextDecoder().decode(jsonBytes)); + + expect(contentType).toBe("application/json"); + expect(json.url).toBe(url); + expect(json.options.method).toBe(options.method); + }); + + test("serializes URL only if options are not provided", () => { + const url = "/api/simple"; + const buffer = serializeRequest(url); + const view = new Uint8Array(buffer); + + const typeLength = view[0]; + const typeBytes = view.slice(1, 1 + typeLength); + const jsonBytes = view.slice(1 + typeLength); + + const contentType = new TextDecoder().decode(typeBytes); + const json = JSON.parse(new TextDecoder().decode(jsonBytes)); + + expect(contentType).toBe("application/json"); + expect(json.url).toBe(url); + expect(json).not.toHaveProperty("options"); + }); +}); + +describe("deserializeRequest", () => { + test("deserializes payload into correct request object", () => { + const url = "/api/test"; + const options = { method: "GET" }; + + const buffer = serializeRequest(url, options); + const result = deserializeRequest(buffer); + + expect(result.url).toBe(url); + expect(result.options.method).toBe("GET"); + }); + + test("throws error on unsupported content type", () => { + const encoder = new TextEncoder(); + const badType = encoder.encode("text/plain"); + const json = encoder.encode(JSON.stringify({ url: "/x" })); + + const buffer = new Uint8Array(1 + badType.length + json.length); + buffer[0] = badType.length; + buffer.set(badType, 1); + buffer.set(json, 1 + badType.length); + + expect(() => { + deserializeRequest(buffer.buffer); + }).toThrow("Unsupported content type: text/plain"); + }); +}); From 58666d5796c0d811091dcc00687677ba9ea5973a Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 10 Jun 2025 23:55:34 +0200 Subject: [PATCH 008/103] fix: fixed oneof variable naming --- Tokenization/backend/wrapper/proto/wrapper.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tokenization/backend/wrapper/proto/wrapper.proto b/Tokenization/backend/wrapper/proto/wrapper.proto index bdb4d1560..fa2f9ba07 100644 --- a/Tokenization/backend/wrapper/proto/wrapper.proto +++ b/Tokenization/backend/wrapper/proto/wrapper.proto @@ -31,7 +31,7 @@ message Token { // Stream message that can contain one of specific messages message Payload { MessageEvent event = 1; - oneof { + oneof data { EmptyMessage emptyMessage = 2; Token newToken = 3; Token revokeToken = 4; From 01182719baa7dc2ba799db8b229ff6372903d7b1 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 10 Jun 2025 11:18:57 +0200 Subject: [PATCH 009/103] feat: Duplex stream with specific payload message events. --- Tokenization/backend/wrapper/wrapper.proto | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Tokenization/backend/wrapper/wrapper.proto diff --git a/Tokenization/backend/wrapper/wrapper.proto b/Tokenization/backend/wrapper/wrapper.proto new file mode 100644 index 000000000..e65311c6e --- /dev/null +++ b/Tokenization/backend/wrapper/wrapper.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; +package wrapper; + +// ====================================== +// MESSAGES +// ====================================== +// Simulates an empty message because protobuffer doesn't support void +message EmptyMessage {} + +// Message with newly generated token and target address binded to it +message NewToken { + string token = 1; + string targetAddress = 2; +} + + +// ====================================== +// SERVICES +// ====================================== + +service ClientService { + // Sends newly generated token to the client for specific Connection + rpc sendNewToken(NewToken) returns(EmptyMessage) {} + + // Informs the client about token revocation + rpc revokeToken(NewToken) returns(EmptyMessage) {} +} \ No newline at end of file From 7c5d552eb8660e04c3b225ce9d6a9e83ca07a3c1 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 10 Jun 2025 11:25:16 +0200 Subject: [PATCH 010/103] feat: Create typescript interfaces for duplex stream message model --- .../backend/wrapper/models/message.model.ts | 15 +++++++++++ Tokenization/backend/wrapper/wrapper.proto | 27 ------------------- 2 files changed, 15 insertions(+), 27 deletions(-) create mode 100644 Tokenization/backend/wrapper/models/message.model.ts delete mode 100644 Tokenization/backend/wrapper/wrapper.proto diff --git a/Tokenization/backend/wrapper/models/message.model.ts b/Tokenization/backend/wrapper/models/message.model.ts new file mode 100644 index 000000000..36cdf5e12 --- /dev/null +++ b/Tokenization/backend/wrapper/models/message.model.ts @@ -0,0 +1,15 @@ +enum DuplexMessageEvent { + EMPTY_EVENT, + NEW_TOKEN, + REVOKE_TOKEN, +} + +interface TokenMessage { + token: string; + targetAddress: string; +} + +interface DuplexMessageModel { + event: DuplexMessageEvent; + data?: TokenMessage; +} diff --git a/Tokenization/backend/wrapper/wrapper.proto b/Tokenization/backend/wrapper/wrapper.proto deleted file mode 100644 index e65311c6e..000000000 --- a/Tokenization/backend/wrapper/wrapper.proto +++ /dev/null @@ -1,27 +0,0 @@ -syntax = "proto3"; -package wrapper; - -// ====================================== -// MESSAGES -// ====================================== -// Simulates an empty message because protobuffer doesn't support void -message EmptyMessage {} - -// Message with newly generated token and target address binded to it -message NewToken { - string token = 1; - string targetAddress = 2; -} - - -// ====================================== -// SERVICES -// ====================================== - -service ClientService { - // Sends newly generated token to the client for specific Connection - rpc sendNewToken(NewToken) returns(EmptyMessage) {} - - // Informs the client about token revocation - rpc revokeToken(NewToken) returns(EmptyMessage) {} -} \ No newline at end of file From 4b1845969dbdc5c747c6a67ecff1ca528df8b76a Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 10 Jun 2025 11:43:36 +0200 Subject: [PATCH 011/103] feat: implement serialization and deserialization methods --- .../__tests__/serialization.utils.test.ts | 0 .../wrapper/utils/serialization.utils.ts | 64 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts create mode 100644 Tokenization/backend/wrapper/utils/serialization.utils.ts diff --git a/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts b/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts new file mode 100644 index 000000000..e69de29bb diff --git a/Tokenization/backend/wrapper/utils/serialization.utils.ts b/Tokenization/backend/wrapper/utils/serialization.utils.ts new file mode 100644 index 000000000..2f1e9bf41 --- /dev/null +++ b/Tokenization/backend/wrapper/utils/serialization.utils.ts @@ -0,0 +1,64 @@ +/** + * @description Serializes Json formatted request into binary payload with specific endpoint + * @param url - The endpoint URL to which the request is made + * @param options - Optional request options, such as headers or body + * @return {ArrayBuffer} - The serialized binary payload containing the URL and options + */ +export const serializeRequest = (url: string, options?: any) => { + const encoder = new TextEncoder(); + const contentTypeBytes = encoder.encode("application/json"); + + // build JSON data + const jsonData = options + ? { + url: url, + options: options, + } + : { + url: url, + }; + + // encode JSON + const jsonString = JSON.stringify(jsonData); + const jsonBytes = encoder.encode(jsonString); + + // Buffer following structure: + // 1 byte -> type length + // N bytes -> types + // rest -> JSON data + const buffer = new Uint8Array(1 + contentTypeBytes.length + jsonBytes.length); + + buffer[0] = contentTypeBytes.length; + buffer.set(contentTypeBytes, 1); + buffer.set(jsonBytes, 1 + contentTypeBytes.length); + + return buffer.buffer; +}; + +/** + * @description Deserializes binary payload to Json formated request + * @param payload - The binary payload to deserialize + * @return {any} - The deserialized request object containing the URL and options + */ +export const deserializeRequest = (payload: any) => { + const view = new Uint8Array(payload); + const decoder = new TextDecoder(); + + const contentTypeLength = view[0]; + const contentTypeBytes = view.slice(1, 1 + contentTypeLength); + const contentType = decoder.decode(contentTypeBytes); + + const dataBytes = view.slice(1 + contentTypeLength); + let data; + + // deserialization based on content type + switch (contentType) { + case "application/json": + data = JSON.parse(decoder.decode(dataBytes)); + break; + default: + throw new Error(`Unsupported content type: ${contentType}`); + } + + return data; +}; From dba904c82bdb7985d3645f96d34c8f815bf59868 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 10 Jun 2025 22:29:50 +0200 Subject: [PATCH 012/103] feat: add package dependencies and write unit tests for serialization utils --- Tokenization/backend/wrapper/jest.config.js | 13 + .../backend/wrapper/package-lock.json | 4007 +++++++++++++++++ Tokenization/backend/wrapper/package.json | 20 + .../__tests__/serialization.utils.test.ts | 71 + 4 files changed, 4111 insertions(+) create mode 100644 Tokenization/backend/wrapper/jest.config.js create mode 100644 Tokenization/backend/wrapper/package-lock.json create mode 100644 Tokenization/backend/wrapper/package.json diff --git a/Tokenization/backend/wrapper/jest.config.js b/Tokenization/backend/wrapper/jest.config.js new file mode 100644 index 000000000..3a5675ad5 --- /dev/null +++ b/Tokenization/backend/wrapper/jest.config.js @@ -0,0 +1,13 @@ +/** @type {import('jest').Config} */ +const config = { + verbose: true, + transform: { + "^.+\\.ts$": ["ts-jest", { useESM: true }], + }, + extensionsToTreatAsEsm: [".ts"], + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, +}; + +export default config; diff --git a/Tokenization/backend/wrapper/package-lock.json b/Tokenization/backend/wrapper/package-lock.json new file mode 100644 index 000000000..500302d37 --- /dev/null +++ b/Tokenization/backend/wrapper/package-lock.json @@ -0,0 +1,4007 @@ +{ + "name": "grpc-wrapper", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "grpc-wrapper", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "ts-node": "^10.9.2", + "typescript": "^5.8.3" + }, + "devDependencies": { + "@types/jest": "^29.5.14", + "jest": "^29.7.0", + "ts-jest": "^29.3.4" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", + "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", + "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.4", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.4", + "@babel/types": "^7.27.3", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.0.tgz", + "integrity": "sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001721", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", + "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.166", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz", + "integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.3.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.4.tgz", + "integrity": "sha512-Iqbrm8IXOmV+ggWHOTEbjwyCf2xZlUMv5npExksXohL+tk8va4Fjhb+X2+Rt9NBmgO7bJ8WpnMLOwih/DnMlFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.2", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/Tokenization/backend/wrapper/package.json b/Tokenization/backend/wrapper/package.json new file mode 100644 index 000000000..a8b9f98a6 --- /dev/null +++ b/Tokenization/backend/wrapper/package.json @@ -0,0 +1,20 @@ +{ + "name": "grpc-wrapper", + "version": "1.0.0", + "type": "module", + "scripts": { + "test": "mocha --loader ts-node/esm \"./**/*.test.ts\"" + }, + "author": "", + "license": "ISC", + "description": "", + "devDependencies": { + "@types/jest": "^29.5.14", + "jest": "^29.7.0", + "ts-jest": "^29.3.4" + }, + "dependencies": { + "ts-node": "^10.9.2", + "typescript": "^5.8.3" + } +} diff --git a/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts b/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts index e69de29bb..655b0ccef 100644 --- a/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts +++ b/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts @@ -0,0 +1,71 @@ +import { deserializeRequest, serializeRequest } from "../serialization.utils"; +import { describe, expect, test } from "@jest/globals"; + +describe("serializeRequest", () => { + test("serializes URL and options correctly", () => { + const url = "/api/test"; + const options = { + method: "POST", + headers: { "Content-Type": "application/json" }, + }; + + const buffer = serializeRequest(url, options); + const view = new Uint8Array(buffer); + + const typeLength = view[0]; + const typeBytes = view.slice(1, 1 + typeLength); + const jsonBytes = view.slice(1 + typeLength); + + const contentType = new TextDecoder().decode(typeBytes); + const json = JSON.parse(new TextDecoder().decode(jsonBytes)); + + expect(contentType).toBe("application/json"); + expect(json.url).toBe(url); + expect(json.options.method).toBe(options.method); + }); + + test("serializes URL only if options are not provided", () => { + const url = "/api/simple"; + const buffer = serializeRequest(url); + const view = new Uint8Array(buffer); + + const typeLength = view[0]; + const typeBytes = view.slice(1, 1 + typeLength); + const jsonBytes = view.slice(1 + typeLength); + + const contentType = new TextDecoder().decode(typeBytes); + const json = JSON.parse(new TextDecoder().decode(jsonBytes)); + + expect(contentType).toBe("application/json"); + expect(json.url).toBe(url); + expect(json).not.toHaveProperty("options"); + }); +}); + +describe("deserializeRequest", () => { + test("deserializes payload into correct request object", () => { + const url = "/api/test"; + const options = { method: "GET" }; + + const buffer = serializeRequest(url, options); + const result = deserializeRequest(buffer); + + expect(result.url).toBe(url); + expect(result.options.method).toBe("GET"); + }); + + test("throws error on unsupported content type", () => { + const encoder = new TextEncoder(); + const badType = encoder.encode("text/plain"); + const json = encoder.encode(JSON.stringify({ url: "/x" })); + + const buffer = new Uint8Array(1 + badType.length + json.length); + buffer[0] = badType.length; + buffer.set(badType, 1); + buffer.set(json, 1 + badType.length); + + expect(() => { + deserializeRequest(buffer.buffer); + }).toThrow("Unsupported content type: text/plain"); + }); +}); From 273b8e2c046da6bcc40ce3af23f14b418b529958 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 10 Jun 2025 23:58:09 +0200 Subject: [PATCH 013/103] feat: Create grpc wrapper with basic connection manager and central system wrapper --- .../backend/wrapper/central/CentralSystem.ts | 69 ++++++++ .../ConnectionManager/ConnectionManager.ts | 94 +++++++++++ .../backend/wrapper/client/gRPCWrapper.ts | 11 ++ .../backend/wrapper/package-lock.json | 159 ++++++++++++++++-- Tokenization/backend/wrapper/package.json | 5 +- Tokenization/backend/wrapper/tsconfig.json | 15 ++ 6 files changed, 336 insertions(+), 17 deletions(-) create mode 100644 Tokenization/backend/wrapper/central/CentralSystem.ts create mode 100644 Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts create mode 100644 Tokenization/backend/wrapper/client/gRPCWrapper.ts create mode 100644 Tokenization/backend/wrapper/tsconfig.json diff --git a/Tokenization/backend/wrapper/central/CentralSystem.ts b/Tokenization/backend/wrapper/central/CentralSystem.ts new file mode 100644 index 000000000..f22a8adf9 --- /dev/null +++ b/Tokenization/backend/wrapper/central/CentralSystem.ts @@ -0,0 +1,69 @@ +import * as grpc from "@grpc/grpc-js"; +import * as protoLoader from "@grpc/proto-loader"; +import path from "path"; +import { fileURLToPath } from "url"; + +export class CentralSystem { + private server: grpc.Server; + + constructor(private port: number) { + this.server = new grpc.Server(); + this.setupService(); + this.start(); + } + + private setupService() { + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); + const packageDef = protoLoader.loadSync(PROTO_PATH, { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + }); + + const proto = grpc.loadPackageDefinition(packageDef) as any; + const wrapper = proto.wrapper; + + this.server.addService(wrapper.CentralSystem.service, { + ClientStream: this.clientStreamHandler.bind(this), + }); + } + + private clientStreamHandler(call: grpc.ServerDuplexStream) { + console.log("Client connected to duplex stream"); + + // hartbeat message + call.write({ event: "EMPTY_EVENT", emptyMessage: {} }); + + call.on("data", (payload: any) => { + // TODO: Implement data handling logic + }); + + call.on("end", () => { + console.log("Client ended stream"); + call.end(); + }); + + call.on("error", (err) => console.error("Stream error:", err)); + } + + private start() { + const addr = `0.0.0.0:${this.port}`; + this.server.bindAsync( + addr, + grpc.ServerCredentials.createInsecure(), + (err, port) => { + if (err) { + console.error("Server bind error:", err); + return; + } + console.log(`Server listening on ${addr}`); + } + ); + } +} + +const cs = new CentralSystem(50051); diff --git a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts new file mode 100644 index 000000000..93d3cf6dc --- /dev/null +++ b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts @@ -0,0 +1,94 @@ +import * as grpc from "@grpc/grpc-js"; +import * as protoLoader from "@grpc/proto-loader"; +import path from "path"; +import { fileURLToPath } from "url"; + +/** + * @description Manages all the connection between clients and central system. + */ +export class ConnectionManager { + private client: any; + private stream?: grpc.ClientDuplexStream; + private readonly address: string; + private reconnectAttempts = 0; + + constructor(centralAddress = "localhost:50051") { + this.address = centralAddress; + + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + const PROTO_PATH = path.join(__dirname, "./wrapper.proto"); + const packageDef = protoLoader.loadSync(PROTO_PATH, { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + }); + + const proto = grpc.loadPackageDefinition(packageDef) as any; + const wrapper = proto.wrapper; + + // Create gRPC client + this.client = new wrapper.CentralSystem( + this.address, + grpc.credentials.createInsecure() + ); + + // Initial connection + this.connect(); + console.log(`ConnectionManager: connected to ${this.address}`); + } + + /** + * @description Initializes the duplex stream and sets up handlers. + */ + private connect() { + this.stream = this.client.ClientStream(); + + if (this.stream) { + this.stream.on("data", (payload) => { + // handle data received from the stream + }); + + this.stream.on("end", () => { + console.warn("Stream ended, attempting to reconnect..."); + this.scheduleReconnect(); + }); + + this.stream.on("error", (err: any) => { + console.error("Wrapper stream error:", err); + this.scheduleReconnect(); + }); + } + } + + /** + * @description Schedules a reconnect with exponential backoff. + */ + private scheduleReconnect() { + this.reconnectAttempts++; + const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 30000); + setTimeout(() => { + console.log(`Reconnecting (attempt ${this.reconnectAttempts})...`); + this.connect(); + }, delay); + } + + /** + * @description Disconnects from the gRPC stream and resets attempts. + */ + disconnect() { + if (this.stream) { + this.stream.end(); + this.stream = undefined; + } + this.reconnectAttempts = 0; + console.log("Disconnected from central"); + } +} + +// Usage example: +// const mgr = new ConnectionManager(); +// mgr.on('message', payload => console.log('Received:', payload)); +// mgr.disconnect(); diff --git a/Tokenization/backend/wrapper/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/client/gRPCWrapper.ts new file mode 100644 index 000000000..a4a02b19a --- /dev/null +++ b/Tokenization/backend/wrapper/client/gRPCWrapper.ts @@ -0,0 +1,11 @@ +import { ConnectionManager } from "./ConnectionManager/ConnectionManager.ts"; + +export class gRPCWrapper { + private ConnectionManager: ConnectionManager; + + constructor() { + this.ConnectionManager = new ConnectionManager(); + } +} + +const grpc = new gRPCWrapper(); diff --git a/Tokenization/backend/wrapper/package-lock.json b/Tokenization/backend/wrapper/package-lock.json index 500302d37..ba080a16e 100644 --- a/Tokenization/backend/wrapper/package-lock.json +++ b/Tokenization/backend/wrapper/package-lock.json @@ -9,6 +9,8 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@grpc/grpc-js": "^1.13.4", + "@grpc/proto-loader": "^0.7.15", "ts-node": "^10.9.2", "typescript": "^5.8.3" }, @@ -540,6 +542,37 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@grpc/grpc-js": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz", + "integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -910,6 +943,80 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1131,7 +1238,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -1141,7 +1247,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -1495,7 +1600,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -1528,7 +1632,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -1541,7 +1644,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/concat-map": { @@ -1713,7 +1815,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/error-ex": { @@ -1730,7 +1831,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -1923,7 +2023,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -2107,7 +2206,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2946,6 +3044,12 @@ "node": ">=8" } }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -2953,6 +3057,12 @@ "dev": true, "license": "MIT" }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3325,6 +3435,30 @@ "node": ">= 6" } }, + "node_modules/protobufjs": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz", + "integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -3353,7 +3487,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3539,7 +3672,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -3554,7 +3686,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -3900,7 +4031,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -3939,7 +4069,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -3956,7 +4085,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -3975,7 +4103,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" diff --git a/Tokenization/backend/wrapper/package.json b/Tokenization/backend/wrapper/package.json index a8b9f98a6..b65f10173 100644 --- a/Tokenization/backend/wrapper/package.json +++ b/Tokenization/backend/wrapper/package.json @@ -3,7 +3,8 @@ "version": "1.0.0", "type": "module", "scripts": { - "test": "mocha --loader ts-node/esm \"./**/*.test.ts\"" + "test": "mocha --loader ts-node/esm \"./**/*.test.ts\"", + "start": "node --loader ts-node/esm ./central/CentralSystem.ts" }, "author": "", "license": "ISC", @@ -14,6 +15,8 @@ "ts-jest": "^29.3.4" }, "dependencies": { + "@grpc/grpc-js": "^1.13.4", + "@grpc/proto-loader": "^0.7.15", "ts-node": "^10.9.2", "typescript": "^5.8.3" } diff --git a/Tokenization/backend/wrapper/tsconfig.json b/Tokenization/backend/wrapper/tsconfig.json new file mode 100644 index 000000000..32d70e3e9 --- /dev/null +++ b/Tokenization/backend/wrapper/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "ESNext", + "target": "ES2022", + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "allowImportingTsExtensions": true, + "noEmit": true + }, + "ts-node": { + "esm": true + } +} From 6467c55a118355e810706534f2a5b36590e1ca58 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Wed, 11 Jun 2025 00:04:33 +0200 Subject: [PATCH 014/103] fix: fix grpc addresses to properly connect both client and central system --- Tokenization/backend/wrapper/central/CentralSystem.ts | 4 ++-- .../wrapper/client/ConnectionManager/ConnectionManager.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tokenization/backend/wrapper/central/CentralSystem.ts b/Tokenization/backend/wrapper/central/CentralSystem.ts index f22a8adf9..5ec776524 100644 --- a/Tokenization/backend/wrapper/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/central/CentralSystem.ts @@ -51,7 +51,7 @@ export class CentralSystem { } private start() { - const addr = `0.0.0.0:${this.port}`; + const addr = `localhost:${this.port}`; this.server.bindAsync( addr, grpc.ServerCredentials.createInsecure(), @@ -66,4 +66,4 @@ export class CentralSystem { } } -const cs = new CentralSystem(50051); +const centralSystem = new CentralSystem(50051); diff --git a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts index 93d3cf6dc..f2936fb0c 100644 --- a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts @@ -17,7 +17,7 @@ export class ConnectionManager { const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); - const PROTO_PATH = path.join(__dirname, "./wrapper.proto"); + const PROTO_PATH = path.join(__dirname, "../../proto/wrapper.proto"); const packageDef = protoLoader.loadSync(PROTO_PATH, { keepCase: true, longs: String, From 1a86d4949815ee8e12bd4b634b85d3d04e513d33 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Wed, 11 Jun 2025 12:57:12 +0200 Subject: [PATCH 015/103] fix: refactor comments --- Tokenization/backend/wrapper/central/CentralSystem.ts | 3 +++ .../wrapper/client/ConnectionManager/ConnectionManager.ts | 5 ----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Tokenization/backend/wrapper/central/CentralSystem.ts b/Tokenization/backend/wrapper/central/CentralSystem.ts index 5ec776524..e30673b89 100644 --- a/Tokenization/backend/wrapper/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/central/CentralSystem.ts @@ -3,6 +3,9 @@ import * as protoLoader from "@grpc/proto-loader"; import path from "path"; import { fileURLToPath } from "url"; +/** + * @description Central System gRPC wrapper that manages client connections and handles gRPC streams with them. + */ export class CentralSystem { private server: grpc.Server; diff --git a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts index f2936fb0c..ae8f2cb1b 100644 --- a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts @@ -87,8 +87,3 @@ export class ConnectionManager { console.log("Disconnected from central"); } } - -// Usage example: -// const mgr = new ConnectionManager(); -// mgr.on('message', payload => console.log('Received:', payload)); -// mgr.disconnect(); From bec195ccc51d694da824c5b0f55698f4f09e73e8 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Wed, 11 Jun 2025 12:58:55 +0200 Subject: [PATCH 016/103] fix: change central system class name to avoid conflicts with central system --- Tokenization/backend/wrapper/central/CentralSystem.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tokenization/backend/wrapper/central/CentralSystem.ts b/Tokenization/backend/wrapper/central/CentralSystem.ts index e30673b89..3058ce7bd 100644 --- a/Tokenization/backend/wrapper/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/central/CentralSystem.ts @@ -6,7 +6,7 @@ import { fileURLToPath } from "url"; /** * @description Central System gRPC wrapper that manages client connections and handles gRPC streams with them. */ -export class CentralSystem { +export class CentralSystemWrapper { private server: grpc.Server; constructor(private port: number) { @@ -69,4 +69,4 @@ export class CentralSystem { } } -const centralSystem = new CentralSystem(50051); +const centralSystem = new CentralSystemWrapper(50051); From c6a3f014319f7270a37c712e70942587f919e7ea Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Wed, 11 Jun 2025 13:38:19 +0200 Subject: [PATCH 017/103] feat: implement Connection class and a map storing all receiving and sending connections in ConnectionManager --- .../wrapper/client/Connection/Connection.ts | 16 ++++++++++++++++ .../ConnectionManager/ConnectionManager.ts | 17 ++++++++++++++++- Tokenization/backend/wrapper/package.json | 5 +++-- 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 Tokenization/backend/wrapper/client/Connection/Connection.ts diff --git a/Tokenization/backend/wrapper/client/Connection/Connection.ts b/Tokenization/backend/wrapper/client/Connection/Connection.ts new file mode 100644 index 000000000..00ec4f381 --- /dev/null +++ b/Tokenization/backend/wrapper/client/Connection/Connection.ts @@ -0,0 +1,16 @@ +/** + * @description This class represents a connection to a target client and manages sending messages to it. + */ +export class Connection { + private token: string; + private targetAddress: string; + + constructor(token: string, targetAddress: string) { + this.token = token; + this.targetAddress = targetAddress; + } + + public handleNewToken(token: string): void { + this.token = token; + } +} diff --git a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts index ae8f2cb1b..60c994f37 100644 --- a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts @@ -2,6 +2,7 @@ import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; import path from "path"; import { fileURLToPath } from "url"; +import { Connection } from "../Connection/Connection.ts"; /** * @description Manages all the connection between clients and central system. @@ -12,6 +13,12 @@ export class ConnectionManager { private readonly address: string; private reconnectAttempts = 0; + // Map to store sending connections by target address + private sendingConnections: Map = new Map(); + + // Map to store receiving connections by target address + private receivingConnections: Map = new Map(); + constructor(centralAddress = "localhost:50051") { this.address = centralAddress; @@ -38,6 +45,9 @@ export class ConnectionManager { // Initial connection this.connect(); console.log(`ConnectionManager: connected to ${this.address}`); + + this.sendingConnections.set("a", new Connection("1", "a")); + this.sendingConnections.set("b", new Connection("2", "b")); } /** @@ -48,7 +58,12 @@ export class ConnectionManager { if (this.stream) { this.stream.on("data", (payload) => { - // handle data received from the stream + switch (payload.event) { + // Central system replacing a new token for existing connection + case "EMPTY_EVENT": + console.log("Empty event: ", payload?.data); + break; + } }); this.stream.on("end", () => { diff --git a/Tokenization/backend/wrapper/package.json b/Tokenization/backend/wrapper/package.json index b65f10173..f77932ebf 100644 --- a/Tokenization/backend/wrapper/package.json +++ b/Tokenization/backend/wrapper/package.json @@ -3,8 +3,9 @@ "version": "1.0.0", "type": "module", "scripts": { - "test": "mocha --loader ts-node/esm \"./**/*.test.ts\"", - "start": "node --loader ts-node/esm ./central/CentralSystem.ts" + "test": "jest", + "start-central": "node --loader ts-node/esm ./central/CentralSystem.ts", + "start-client": "node --loader ts-node/esm ./client/gRPCWrapper.ts" }, "author": "", "license": "ISC", From fb12e553ed46c463653cfb475f64512c5c4d8ef0 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Wed, 11 Jun 2025 14:26:35 +0200 Subject: [PATCH 018/103] feat: implement replacing new token for client from central system --- .../backend/wrapper/central/CentralSystem.ts | 53 +++++++++++++++++-- .../wrapper/client/Connection/Connection.ts | 4 ++ .../ConnectionManager/ConnectionManager.ts | 30 ++++++++++- .../backend/wrapper/client/gRPCWrapper.ts | 1 + .../backend/wrapper/models/message.model.ts | 15 +++--- 5 files changed, 91 insertions(+), 12 deletions(-) diff --git a/Tokenization/backend/wrapper/central/CentralSystem.ts b/Tokenization/backend/wrapper/central/CentralSystem.ts index 3058ce7bd..626dfbbbe 100644 --- a/Tokenization/backend/wrapper/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/central/CentralSystem.ts @@ -2,12 +2,17 @@ import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; import path from "path"; import { fileURLToPath } from "url"; +import { + DuplexMessageEvent, + DuplexMessageModel, +} from "../models/message.model.ts"; /** * @description Central System gRPC wrapper that manages client connections and handles gRPC streams with them. */ export class CentralSystemWrapper { private server: grpc.Server; + private clientStreams = new Map>(); constructor(private port: number) { this.server = new grpc.Server(); @@ -38,19 +43,28 @@ export class CentralSystemWrapper { private clientStreamHandler(call: grpc.ServerDuplexStream) { console.log("Client connected to duplex stream"); + const clientAddress = call.getPeer(); + + this.clientStreams.set(clientAddress, call); + console.log(`Registered client stream for: ${clientAddress}`); + // hartbeat message - call.write({ event: "EMPTY_EVENT", emptyMessage: {} }); + call.write({ event: "EMPTY_EVENT", data: "registered in central system." }); call.on("data", (payload: any) => { - // TODO: Implement data handling logic + console.log(`Received from ${clientAddress}:`, payload); }); call.on("end", () => { - console.log("Client ended stream"); + console.log(`Client ${clientAddress} ended stream`); + this.clientStreams.delete(clientAddress); call.end(); }); - call.on("error", (err) => console.error("Stream error:", err)); + call.on("error", (err) => { + console.error(`Stream error for ${clientAddress}:`, err); + this.clientStreams.delete(clientAddress); + }); } private start() { @@ -67,6 +81,37 @@ export class CentralSystemWrapper { } ); } + + /** + * @description Returns all client addresses + */ + public getClients() { + return this.clientStreams.keys(); + } + + /** + * @description Sends message event to specific client + */ + public clientSend(clientAddress: string, message: DuplexMessageModel) { + const stream = this.clientStreams.get(clientAddress); + if (!stream) { + console.warn(`No active stream for client ${clientAddress}`); + return; + } + stream.write(message); + } } +// tests const centralSystem = new CentralSystemWrapper(50051); +setTimeout(() => { + const client = Array.from(centralSystem.getClients())[0]; + console.log(client); + centralSystem.clientSend(client, { + event: DuplexMessageEvent.NEW_TOKEN, + newToken: { + token: "new token", + targetAddress: "a", + }, + }); +}, 5000); diff --git a/Tokenization/backend/wrapper/client/Connection/Connection.ts b/Tokenization/backend/wrapper/client/Connection/Connection.ts index 00ec4f381..6579a8de6 100644 --- a/Tokenization/backend/wrapper/client/Connection/Connection.ts +++ b/Tokenization/backend/wrapper/client/Connection/Connection.ts @@ -13,4 +13,8 @@ export class Connection { public handleNewToken(token: string): void { this.token = token; } + + public getToken(): string { + return this.token; + } } diff --git a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts index 60c994f37..f1b33b88b 100644 --- a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts @@ -3,6 +3,7 @@ import * as protoLoader from "@grpc/proto-loader"; import path from "path"; import { fileURLToPath } from "url"; import { Connection } from "../Connection/Connection.ts"; +import { DuplexMessageEvent } from "../../models/message.model.ts"; /** * @description Manages all the connection between clients and central system. @@ -60,9 +61,18 @@ export class ConnectionManager { this.stream.on("data", (payload) => { switch (payload.event) { // Central system replacing a new token for existing connection - case "EMPTY_EVENT": + case DuplexMessageEvent.EMPTY_EVENT: console.log("Empty event: ", payload?.data); break; + case DuplexMessageEvent.NEW_TOKEN: + console.log(payload); + this.handleNewToken( + payload.newToken.token, + payload.newToken.targetAddress + ); + break; + default: + console.warn(`Unhandled event: ${payload.event}`); } }); @@ -78,6 +88,24 @@ export class ConnectionManager { } } + /** + * @description Handles a new token received from the central system and replaces it in proper Connection object. + * @param newToken + */ + private handleNewToken(newToken: string, targetAddress: string) { + console.log(`Received new token for ${targetAddress}: ${newToken}`); + + // Check if we have a sending connection for this target address + const sendingConnection = this.sendingConnections.get(targetAddress); + if (sendingConnection) { + sendingConnection.handleNewToken(newToken); + console.log(`Updated sending connection for ${targetAddress}`); + console.log(sendingConnection.getToken()); + } else { + console.warn(`No sending connection found for ${targetAddress}`); + } + } + /** * @description Schedules a reconnect with exponential backoff. */ diff --git a/Tokenization/backend/wrapper/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/client/gRPCWrapper.ts index a4a02b19a..6400bf04a 100644 --- a/Tokenization/backend/wrapper/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/client/gRPCWrapper.ts @@ -8,4 +8,5 @@ export class gRPCWrapper { } } +// tests const grpc = new gRPCWrapper(); diff --git a/Tokenization/backend/wrapper/models/message.model.ts b/Tokenization/backend/wrapper/models/message.model.ts index 36cdf5e12..cdcc15eb1 100644 --- a/Tokenization/backend/wrapper/models/message.model.ts +++ b/Tokenization/backend/wrapper/models/message.model.ts @@ -1,15 +1,16 @@ -enum DuplexMessageEvent { - EMPTY_EVENT, - NEW_TOKEN, - REVOKE_TOKEN, +export enum DuplexMessageEvent { + EMPTY_EVENT = "EMPTY_EVENT", + NEW_TOKEN = "NEW_TOKEN", + REVOKE_TOKEN = "REVOKE_TOKEN", } -interface TokenMessage { +export interface TokenMessage { token: string; targetAddress: string; } -interface DuplexMessageModel { +export interface DuplexMessageModel { event: DuplexMessageEvent; - data?: TokenMessage; + newToken?: TokenMessage; + revokeToken?: TokenMessage; } From 63faee36a647d86d700f71a166c2b0d5ba251306 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Wed, 11 Jun 2025 14:55:20 +0200 Subject: [PATCH 019/103] feat: implement token revokation by changing connection status --- .../backend/wrapper/central/CentralSystem.ts | 11 ++++++++ .../wrapper/client/Connection/Connection.ts | 26 +++++++++++++++++++ .../ConnectionManager/ConnectionManager.ts | 21 ++++++++++++++- .../wrapper/models/connection.model.ts | 17 ++++++++++++ 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 Tokenization/backend/wrapper/models/connection.model.ts diff --git a/Tokenization/backend/wrapper/central/CentralSystem.ts b/Tokenization/backend/wrapper/central/CentralSystem.ts index 626dfbbbe..283532ba5 100644 --- a/Tokenization/backend/wrapper/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/central/CentralSystem.ts @@ -107,6 +107,8 @@ const centralSystem = new CentralSystemWrapper(50051); setTimeout(() => { const client = Array.from(centralSystem.getClients())[0]; console.log(client); + + // send new token centralSystem.clientSend(client, { event: DuplexMessageEvent.NEW_TOKEN, newToken: { @@ -114,4 +116,13 @@ setTimeout(() => { targetAddress: "a", }, }); + + // revoke token + centralSystem.clientSend(client, { + event: DuplexMessageEvent.REVOKE_TOKEN, + revokeToken: { + token: "new token", + targetAddress: "a", + }, + }); }, 5000); diff --git a/Tokenization/backend/wrapper/client/Connection/Connection.ts b/Tokenization/backend/wrapper/client/Connection/Connection.ts index 6579a8de6..8fb2d6ca4 100644 --- a/Tokenization/backend/wrapper/client/Connection/Connection.ts +++ b/Tokenization/backend/wrapper/client/Connection/Connection.ts @@ -1,20 +1,46 @@ +import { ConnectionStatus } from "../../models/connection.model.ts"; + /** * @description This class represents a connection to a target client and manages sending messages to it. */ export class Connection { private token: string; private targetAddress: string; + private status: ConnectionStatus; constructor(token: string, targetAddress: string) { this.token = token; this.targetAddress = targetAddress; + + this.status = ConnectionStatus.CONNECTED; } + /** + * @description Replace newly generated token + * @param token New token to be replaced + */ public handleNewToken(token: string): void { this.token = token; } + public handleRevokeToken(): void { + this.token = ""; + this.status = ConnectionStatus.UNAUTHORIZED; + } + + /** + * @description Returns token for this Connection object + * @returns Connection token + */ public getToken(): string { return this.token; } + + /** + * @description Returns status for specific + * @returns Connection status + */ + public getStatus(): string { + return this.status; + } } diff --git a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts index f1b33b88b..73184263b 100644 --- a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts @@ -65,12 +65,17 @@ export class ConnectionManager { console.log("Empty event: ", payload?.data); break; case DuplexMessageEvent.NEW_TOKEN: - console.log(payload); this.handleNewToken( payload.newToken.token, payload.newToken.targetAddress ); break; + case DuplexMessageEvent.REVOKE_TOKEN: + this.handleRevokeToken( + payload.revokeToken.token, + payload.revokeToken.targetAddress + ); + break; default: console.warn(`Unhandled event: ${payload.event}`); } @@ -106,6 +111,20 @@ export class ConnectionManager { } } + private handleRevokeToken(newToken: string, targetAddress: string) { + console.log(`Revoke token for ${targetAddress}`); + + const sendingConnection = this.sendingConnections.get(targetAddress); + if (sendingConnection) { + sendingConnection.handleRevokeToken(); + } + + const receivingConnection = this.receivingConnections.get(targetAddress); + if (receivingConnection) { + receivingConnection.handleRevokeToken(); + } + } + /** * @description Schedules a reconnect with exponential backoff. */ diff --git a/Tokenization/backend/wrapper/models/connection.model.ts b/Tokenization/backend/wrapper/models/connection.model.ts new file mode 100644 index 000000000..474a2964c --- /dev/null +++ b/Tokenization/backend/wrapper/models/connection.model.ts @@ -0,0 +1,17 @@ +export enum ConnectionStatus { + // The connection is in the process of being established + CONNECTING = "CONNECTING", + // The connection has been successfully established + CONNECTED = "CONNECTED", + // The connection attempt failed due to authorization issues + // or token has expired/been revoked + UNAUTHORIZED = "UNAUTHORIZED", + // The connection has been closed + CLOSED = "CLOSED", + // An error occurred with the connection + ERROR = "ERROR", + // The connection is attempting to re-establish after a disruption + RECONNECTING = "RECONNECTING", + // The connection is refreshing its authentication token + TOKEN_REFRESH = "TOKEN_REFRESH", +} From 456bf84f3cc2ed6f5350dc8602ad782277745cc8 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Wed, 11 Jun 2025 15:01:01 +0200 Subject: [PATCH 020/103] fix: remove unnecessary params --- .../wrapper/client/ConnectionManager/ConnectionManager.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts index 73184263b..261f0f456 100644 --- a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts @@ -71,10 +71,7 @@ export class ConnectionManager { ); break; case DuplexMessageEvent.REVOKE_TOKEN: - this.handleRevokeToken( - payload.revokeToken.token, - payload.revokeToken.targetAddress - ); + this.handleRevokeToken(payload.revokeToken.token); break; default: console.warn(`Unhandled event: ${payload.event}`); @@ -111,7 +108,7 @@ export class ConnectionManager { } } - private handleRevokeToken(newToken: string, targetAddress: string) { + private handleRevokeToken(targetAddress: string) { console.log(`Revoke token for ${targetAddress}`); const sendingConnection = this.sendingConnections.get(targetAddress); From 93c3a51a44a09f36d73e59d91c58d9fcc75fb8f0 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 17 Jun 2025 10:49:54 +0200 Subject: [PATCH 021/103] fix: fix enum naming --- Tokenization/backend/wrapper/proto/wrapper.proto | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Tokenization/backend/wrapper/proto/wrapper.proto b/Tokenization/backend/wrapper/proto/wrapper.proto index fa2f9ba07..ce1ec1c11 100644 --- a/Tokenization/backend/wrapper/proto/wrapper.proto +++ b/Tokenization/backend/wrapper/proto/wrapper.proto @@ -1,18 +1,18 @@ syntax = "proto3"; -package wrapper; +package webui.tokenization; // ====================================== // ENUMS // ====================================== enum MessageEvent { // Default value, represents an empty event - EMPTY_EVENT = 0; + MESSAGE_EVENT_EMPTY = 0; // New token message type, contains a new token and target address - NEW_TOKEN = 1; + MESSAGE_EVENT_NEW_TOKEN = 1; // Revoke token message type, contains a token to be revoked - REVOKE_TOKEN = 2; + MESSAGE_EVENT_REVOKE_TOKEN = 2; } @@ -30,7 +30,9 @@ message Token { // Stream message that can contain one of specific messages message Payload { + // Message event type MessageEvent event = 1; + // Data related to specific event type oneof data { EmptyMessage emptyMessage = 2; Token newToken = 3; @@ -43,6 +45,7 @@ message Payload { // SERVICES // ====================================== +// Central System service handling duplex communication with wrapper client service CentralSystem { rpc ClientStream(stream Payload) returns (stream Payload); } \ No newline at end of file From ff0faf0d89c73fa7024ab9f1e899b81cf463dd07 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 17 Jun 2025 11:02:20 +0200 Subject: [PATCH 022/103] fix: fix proto types order based on Bookkeeping standards --- .../backend/wrapper/proto/wrapper.proto | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/Tokenization/backend/wrapper/proto/wrapper.proto b/Tokenization/backend/wrapper/proto/wrapper.proto index ce1ec1c11..ee50f75f8 100644 --- a/Tokenization/backend/wrapper/proto/wrapper.proto +++ b/Tokenization/backend/wrapper/proto/wrapper.proto @@ -1,24 +1,20 @@ syntax = "proto3"; + package webui.tokenization; // ====================================== -// ENUMS +// SERVICES // ====================================== -enum MessageEvent { - // Default value, represents an empty event - MESSAGE_EVENT_EMPTY = 0; - - // New token message type, contains a new token and target address - MESSAGE_EVENT_NEW_TOKEN = 1; - // Revoke token message type, contains a token to be revoked - MESSAGE_EVENT_REVOKE_TOKEN = 2; +// Central System service handling duplex communication with wrapper client +service CentralSystem { + rpc ClientStream(stream Payload) returns (stream Payload); } - // ====================================== // MESSAGES // ====================================== + // Simulates an empty message because protobuffer doesn't support void message EmptyMessage {} @@ -40,12 +36,17 @@ message Payload { } } - // ====================================== -// SERVICES +// ENUMS // ====================================== -// Central System service handling duplex communication with wrapper client -service CentralSystem { - rpc ClientStream(stream Payload) returns (stream Payload); -} \ No newline at end of file +enum MessageEvent { + // Default value, represents an empty event + MESSAGE_EVENT_EMPTY = 0; + + // New token message type, contains a new token and target address + MESSAGE_EVENT_NEW_TOKEN = 1; + + // Revoke token message type, contains a token to be revoked + MESSAGE_EVENT_REVOKE_TOKEN = 2; +} From fb6c39cafd87f200224ec71257010b221d567dbd Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 17 Jun 2025 11:14:00 +0200 Subject: [PATCH 023/103] fix: add copyright banner --- Tokenization/backend/wrapper/proto/wrapper.proto | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Tokenization/backend/wrapper/proto/wrapper.proto b/Tokenization/backend/wrapper/proto/wrapper.proto index ee50f75f8..9cb03f258 100644 --- a/Tokenization/backend/wrapper/proto/wrapper.proto +++ b/Tokenization/backend/wrapper/proto/wrapper.proto @@ -1,3 +1,16 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ syntax = "proto3"; package webui.tokenization; From 72042c809050984a19cbeac5aabda5dcc1b2d8cc Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 17 Jun 2025 11:38:24 +0200 Subject: [PATCH 024/103] fix: add copyright banner and change enum naming convention --- .../backend/wrapper/models/message.model.ts | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/Tokenization/backend/wrapper/models/message.model.ts b/Tokenization/backend/wrapper/models/message.model.ts index 3a90dd739..8a2a82a3d 100644 --- a/Tokenization/backend/wrapper/models/message.model.ts +++ b/Tokenization/backend/wrapper/models/message.model.ts @@ -1,15 +1,37 @@ /** - * @description Enum for duplex message events. - * EMPTY_EVENT: No event, used for initialization or no response. - * NEW_TOKEN: Event for replacing with newly generated token. - * REVOKE_TOKEN: Event for revoking an existing token. + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +// ====================================== +// ENUMS +// ====================================== + +/** + * @enum Represents the types of events that can occur in a duplex message exchange. + * @property MESSAGE_EVENT_EMPTY: No event, used for initialization or no response. + * @property MESSAGE_EVENT_NEW_TOKEN: Event for replacing with newly generated token. + * @property MESSAGE_EVENT_REVOKE_TOKEN: Event for revoking an existing token. */ enum DuplexMessageEvent { - EMPTY_EVENT, - NEW_TOKEN, - REVOKE_TOKEN, + MESSAGE_EVENT_EMPTY = "MESSAGE_EVENT_EMPTY", + MESSAGE_EVENT_NEW_TOKEN = "MESSAGE_EVENT_NEW_TOKEN", + MESSAGE_EVENT_REVOKE_TOKEN = "MESSAGE_EVENT_REVOKE_TOKEN", } +// ====================================== +// INTERFACES +// ====================================== + /** * @description Model for token generation and revocation messages. * @property {string} token - The token to be replaced or revoked. @@ -23,7 +45,8 @@ interface TokenMessage { /** * @description Model for duplex stream messages between client and central system. * @property {DuplexMessageEvent} event - The event type of the message. - * @property {TokenMessage} [data] - The data associated with the event, it may be undefined for some events. + * @property {TokenMessage} data - The data associated with the event, it may be undefined for some events. + * @example {event: DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN, data: {token: '', targetAddress: ''}} */ interface DuplexMessageModel { event: DuplexMessageEvent; From b70cf9e57ea819c2a0e1e98e16a2f9464034b8ba Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 17 Jun 2025 11:59:18 +0200 Subject: [PATCH 025/103] fix: fix serialization comments and tests --- Tokenization/backend/wrapper/package.json | 6 +-- .../__tests__/serialization.utils.test.ts | 31 ++++++------ .../wrapper/utils/serialization.utils.ts | 48 ++++++++++--------- 3 files changed, 42 insertions(+), 43 deletions(-) diff --git a/Tokenization/backend/wrapper/package.json b/Tokenization/backend/wrapper/package.json index a8b9f98a6..5bb96d468 100644 --- a/Tokenization/backend/wrapper/package.json +++ b/Tokenization/backend/wrapper/package.json @@ -3,11 +3,9 @@ "version": "1.0.0", "type": "module", "scripts": { - "test": "mocha --loader ts-node/esm \"./**/*.test.ts\"" + "test": "jest" }, - "author": "", - "license": "ISC", - "description": "", + "author": "ALICEO2", "devDependencies": { "@types/jest": "^29.5.14", "jest": "^29.7.0", diff --git a/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts b/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts index 655b0ccef..4c2194784 100644 --- a/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts +++ b/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts @@ -1,3 +1,17 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + import { deserializeRequest, serializeRequest } from "../serialization.utils"; import { describe, expect, test } from "@jest/globals"; @@ -23,23 +37,6 @@ describe("serializeRequest", () => { expect(json.url).toBe(url); expect(json.options.method).toBe(options.method); }); - - test("serializes URL only if options are not provided", () => { - const url = "/api/simple"; - const buffer = serializeRequest(url); - const view = new Uint8Array(buffer); - - const typeLength = view[0]; - const typeBytes = view.slice(1, 1 + typeLength); - const jsonBytes = view.slice(1 + typeLength); - - const contentType = new TextDecoder().decode(typeBytes); - const json = JSON.parse(new TextDecoder().decode(jsonBytes)); - - expect(contentType).toBe("application/json"); - expect(json.url).toBe(url); - expect(json).not.toHaveProperty("options"); - }); }); describe("deserializeRequest", () => { diff --git a/Tokenization/backend/wrapper/utils/serialization.utils.ts b/Tokenization/backend/wrapper/utils/serialization.utils.ts index 2f1e9bf41..57ca8bcd2 100644 --- a/Tokenization/backend/wrapper/utils/serialization.utils.ts +++ b/Tokenization/backend/wrapper/utils/serialization.utils.ts @@ -1,24 +1,33 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + /** * @description Serializes Json formatted request into binary payload with specific endpoint * @param url - The endpoint URL to which the request is made - * @param options - Optional request options, such as headers or body + * @param options - Request options, such as headers or body * @return {ArrayBuffer} - The serialized binary payload containing the URL and options */ -export const serializeRequest = (url: string, options?: any) => { +export const serializeRequest = (url: string, options: any): ArrayBuffer => { const encoder = new TextEncoder(); const contentTypeBytes = encoder.encode("application/json"); // build JSON data - const jsonData = options - ? { - url: url, - options: options, - } - : { - url: url, - }; + const jsonData = { + url: url, + options: options, + }; - // encode JSON const jsonString = JSON.stringify(jsonData); const jsonBytes = encoder.encode(jsonString); @@ -40,7 +49,7 @@ export const serializeRequest = (url: string, options?: any) => { * @param payload - The binary payload to deserialize * @return {any} - The deserialized request object containing the URL and options */ -export const deserializeRequest = (payload: any) => { +export const deserializeRequest = (payload: ArrayBuffer): any => { const view = new Uint8Array(payload); const decoder = new TextDecoder(); @@ -49,16 +58,11 @@ export const deserializeRequest = (payload: any) => { const contentType = decoder.decode(contentTypeBytes); const dataBytes = view.slice(1 + contentTypeLength); - let data; - // deserialization based on content type - switch (contentType) { - case "application/json": - data = JSON.parse(decoder.decode(dataBytes)); - break; - default: - throw new Error(`Unsupported content type: ${contentType}`); + // deserialization of JSON content + if (contentType === "application/json") { + return JSON.parse(decoder.decode(dataBytes)); + } else { + throw new Error(`Unsupported content type: ${contentType}`); } - - return data; }; From bdba63de6f09804f1556a4f22dbeacfe94579386 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 8 Jul 2025 20:32:36 +0200 Subject: [PATCH 026/103] feat: add description comments and implement .listen() instead of automatic start of listener --- .../backend/wrapper/central/CentralSystem.ts | 37 +++- .../ConnectionManager/ConnectionManager.ts | 51 +++-- .../backend/wrapper/client/gRPCWrapper.ts | 34 +++- .../utils/__tests__/presentation.test.ts | 180 ++++++++++++++++++ 4 files changed, 278 insertions(+), 24 deletions(-) create mode 100644 Tokenization/backend/wrapper/utils/__tests__/presentation.test.ts diff --git a/Tokenization/backend/wrapper/central/CentralSystem.ts b/Tokenization/backend/wrapper/central/CentralSystem.ts index 3058ce7bd..0130635cf 100644 --- a/Tokenization/backend/wrapper/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/central/CentralSystem.ts @@ -9,16 +9,24 @@ import { fileURLToPath } from "url"; export class CentralSystemWrapper { private server: grpc.Server; + /** + * Initializes the Wrapper for CentralSystem. + * @param port The port number to bind the gRPC server to. + */ constructor(private port: number) { this.server = new grpc.Server(); this.setupService(); - this.start(); } - private setupService() { + /** + * @description Loads the gRPC proto definition and sets up the CentralSystem service. + */ + private setupService(): void { const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); + + // Load the proto definition with options const packageDef = protoLoader.loadSync(PROTO_PATH, { keepCase: true, longs: String, @@ -27,38 +35,46 @@ export class CentralSystemWrapper { oneofs: true, }); + // Load the package definition into a gRPC object const proto = grpc.loadPackageDefinition(packageDef) as any; const wrapper = proto.wrapper; + // Add the CentralSystem service and bind the stream handler this.server.addService(wrapper.CentralSystem.service, { ClientStream: this.clientStreamHandler.bind(this), }); } - private clientStreamHandler(call: grpc.ServerDuplexStream) { + /** + * @description Handles the duplex stream from the client. + * @param call The duplex stream call object. + */ + private clientStreamHandler(call: grpc.ServerDuplexStream): void { console.log("Client connected to duplex stream"); - - // hartbeat message - call.write({ event: "EMPTY_EVENT", emptyMessage: {} }); - + // Listen for data events from the client call.on("data", (payload: any) => { // TODO: Implement data handling logic }); + // Handle stream end event call.on("end", () => { console.log("Client ended stream"); call.end(); }); + // Handle stream error event call.on("error", (err) => console.error("Stream error:", err)); } - private start() { + /** + * @desciprion Starts the gRPC server and binds it to the specified in class port. + */ + public listen() { const addr = `localhost:${this.port}`; this.server.bindAsync( addr, grpc.ServerCredentials.createInsecure(), - (err, port) => { + (err, _port) => { if (err) { console.error("Server bind error:", err); return; @@ -69,4 +85,7 @@ export class CentralSystemWrapper { } } +// Instantiate the CentralSystemWrapper on port 50051, but don't start automatically const centralSystem = new CentralSystemWrapper(50051); +// Start listening explicitly +centralSystem.listen(); diff --git a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts index ae8f2cb1b..b14f65d85 100644 --- a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts @@ -1,24 +1,42 @@ import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; -import path from "path"; -import { fileURLToPath } from "url"; /** * @description Manages all the connection between clients and central system. */ +/** + * Manages the lifecycle and connection logic for a gRPC client communicating with the central system. + * + * This class is responsible for: + * - Initializing the gRPC client using the provided proto definition and address. + * - Managing a duplex stream (`stream`) for bidirectional communication. + * - Handling automatic reconnection with exponential backoff on stream errors or disconnects. + * - Providing methods to start (`connectToCentralSystem`) and stop (`disconnect`) the connection with central system. + * + * @remarks + * - `client`: The gRPC client instance for communicating with the central system. + * - `stream`: The active duplex stream for sending and receiving messages (optional). + * - `address`: The address of the central gRPC server. + * - `reconnectAttempts`: The number of consecutive reconnection attempts made after a disconnect or error. + */ export class ConnectionManager { private client: any; private stream?: grpc.ClientDuplexStream; private readonly address: string; private reconnectAttempts = 0; - constructor(centralAddress = "localhost:50051") { + /** + * @description Initializes a new instance of the ConnectionManager class. + * + * This constructor sets up the gRPC client for communication with the central system. + * + * @param protoPath - The file path to the gRPC proto definition. + * @param centralAddress - The address of the central gRPC server (default: "localhost:50051"). + */ + constructor(protoPath: string, centralAddress: string = "localhost:50051") { this.address = centralAddress; - const __filename = fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); - const PROTO_PATH = path.join(__dirname, "../../proto/wrapper.proto"); - const packageDef = protoLoader.loadSync(PROTO_PATH, { + const packageDef = protoLoader.loadSync(protoPath, { keepCase: true, longs: String, enums: String, @@ -34,16 +52,13 @@ export class ConnectionManager { this.address, grpc.credentials.createInsecure() ); - - // Initial connection - this.connect(); - console.log(`ConnectionManager: connected to ${this.address}`); } /** * @description Initializes the duplex stream and sets up handlers. */ private connect() { + if (this.stream) return; this.stream = this.client.ClientStream(); if (this.stream) { @@ -53,11 +68,13 @@ export class ConnectionManager { this.stream.on("end", () => { console.warn("Stream ended, attempting to reconnect..."); + this.stream = undefined; this.scheduleReconnect(); }); this.stream.on("error", (err: any) => { console.error("Wrapper stream error:", err); + this.stream = undefined; this.scheduleReconnect(); }); } @@ -75,10 +92,20 @@ export class ConnectionManager { }, delay); } + /** + * @description Starts the connection to the central system. + */ + public connectToCentralSystem() { + if (!this.stream) { + this.connect(); + console.log(`ConnectionManager: connected to ${this.address}`); + } + } + /** * @description Disconnects from the gRPC stream and resets attempts. */ - disconnect() { + public disconnect() { if (this.stream) { this.stream.end(); this.stream = undefined; diff --git a/Tokenization/backend/wrapper/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/client/gRPCWrapper.ts index a4a02b19a..898785caf 100644 --- a/Tokenization/backend/wrapper/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/client/gRPCWrapper.ts @@ -1,11 +1,39 @@ import { ConnectionManager } from "./ConnectionManager/ConnectionManager.ts"; +/** + * @description Wrapper class for managing secure gRPC wrapper. + * + * @remarks + * This class serves as a high-level abstraction over the underlying + * `ConnectionManager`, providing a simplified interface for establishing + * and managing gRPC connections within the application. + * + * @example + * ```typescript + * const grpcWrapper = new gRPCWrapper(); + * // Use grpcWrapper to interact with gRPC services + * ``` + */ export class gRPCWrapper { private ConnectionManager: ConnectionManager; - constructor() { - this.ConnectionManager = new ConnectionManager(); + /** + * @description Initializes an instance of gRPCWrapper class. + * + * @param protoPath - The file path to the gRPC proto definition. + * @param centralAddress - The address of the central gRPC server (default: "localhost:50051"). + */ + constructor(protoPath: string, centralAddress: string = "localhost:50051") { + this.ConnectionManager = new ConnectionManager(protoPath, centralAddress); + } + + /** + * @description Starts the Connection Manager stream connection with Central System + */ + public connectToCentralSystem(): void { + this.ConnectionManager.connectToCentralSystem(); } } -const grpc = new gRPCWrapper(); +const grpc = new gRPCWrapper("../proto/wrapper.proto", "localhost:50051"); +grpc.connectToCentralSystem(); diff --git a/Tokenization/backend/wrapper/utils/__tests__/presentation.test.ts b/Tokenization/backend/wrapper/utils/__tests__/presentation.test.ts new file mode 100644 index 000000000..039cb3c46 --- /dev/null +++ b/Tokenization/backend/wrapper/utils/__tests__/presentation.test.ts @@ -0,0 +1,180 @@ +import { serializeRequest } from "../serialization.utils"; +// 1. describe(), test(), it() +// test suite "Basic Jest methods" +describe("Basic Jest methods", () => { + test("adds numbers correctly", () => { + expect(1 + 2).toBe(3); + }); + + it("subtracts numbers correctly", () => { + expect(5 - 2).toBe(3); + }); +}); + +// 2. expect() + matchers +describe("expect + matchers", () => { + it("should present expect + matchers methods", () => { + expect(2 + 2).toBe(4); // strict equality + expect([1, 2]).toEqual([1, 2]); // deep equality for arrays + expect("hello world").toContain("hello"); + expect(() => JSON.parse("{")).toThrow(); // expects function to throw error + }); + + const add = (a, b) => { + return a + b; + }; + it.each([ + [1, 1, 2], + [2, 2, 4], + [1, 2, 3], + ])("should correctly add numbers", (a, b, result) => { + expect(add(a, b)).toBe(result); + }); +}); + +// 3. Mocking + SpyOn + +// SpyOn +class importedClass { + constructor() {} + + private testFunc = () => { + return "hello from test"; + }; + + public test() { + return this.testFunc(); + } +} + +// mock +jest.mock("../serialization.utils.ts", () => ({ + serializeRequest: jest.fn().mockResolvedValue("Hi"), +})); + +describe("Mocking + SpyOn", () => { + const testClass = new importedClass(); + const spy = jest.spyOn(testClass as any, "testFunc"); + + it("should mock return value of serializeRequest", async () => { + const result = await serializeRequest("/test", {}); + expect(result).toEqual("Hi"); + }); + + it("testFuncCall should be executed with spyon mock", () => { + expect(testClass.test()).toEqual("hello from test"); + spy.mockReturnValue("Hi"); + expect(testClass.test()).toEqual("Hi"); + expect(spy).toHaveBeenCalled(); + }); +}); + +// 4. Resets and cleanups +describe("Resets + mocks", () => { + it("should not call mock after cleanup", () => { + const mock = jest.fn(); + mock(); + jest.clearAllMocks(); + expect(mock).not.toHaveBeenCalled(); + }); + + it("should restore mocks", () => { + const obj = { + greet: () => "hi", + }; + + jest.spyOn(obj, "greet").mockImplementation(() => "mock"); + jest.restoreAllMocks(); + expect(obj.greet()).toBe("hi"); + }); +}); + +// 5. beforeEach +const mockFn = jest.fn(); + +describe("mockFn test suite", () => { + beforeEach(() => { + mockFn.mockClear(); + }); + + test("mockFn is called once", () => { + mockFn(); + expect(mockFn).toHaveBeenCalledTimes(1); + }); + + test("mockFn is clean before this test", () => { + expect(mockFn).not.toHaveBeenCalled(); + }); +}); + +// +// +// ConnectionManager.test.ts + +jest.mock("@grpc/grpc-js", () => ({ + credentials: { createInsecure: jest.fn() }, + loadPackageDefinition: jest.fn(), +})); + +jest.mock("@grpc/proto-loader", () => ({ + loadSync: jest.fn(() => ({})), +})); + +import * as grpc from "@grpc/grpc-js"; +import { ConnectionManager } from "../../client/ConnectionManager/ConnectionManager"; + +describe("ConnectionManager", () => { + let connectionManager: ConnectionManager; + const fakeStream = { + on: jest.fn(), + end: jest.fn(), + }; + + beforeAll(() => { + const fakeWrapper = { + CentralSystem: jest.fn().mockImplementation(() => ({ + ClientStream: jest.fn(() => fakeStream), + })), + }; + + (grpc.loadPackageDefinition as jest.Mock).mockReturnValue({ + wrapper: fakeWrapper, + }); + }); + + beforeEach(() => { + jest.clearAllMocks(); + connectionManager = new ConnectionManager( + "./proto/wrapper.proto", + "localhost:50051" + ); + (connectionManager as any).client = { + ClientStream: jest.fn(() => fakeStream), + }; + + (connectionManager as any).scheduleReconnect = jest.fn(); + }); + + it("should correctly set up data listener on ClientStream", () => { + connectionManager.connectToCentralSystem(); + expect(fakeStream.on).toHaveBeenCalledWith("data", expect.any(Function)); + }); + + it("should warn and reset stream on end event", () => { + connectionManager.connectToCentralSystem(); + const endHandler = fakeStream.on.mock.calls.find( + ([event]) => event === "end" + )![1]; + endHandler(); + expect((connectionManager as any).scheduleReconnect).toHaveBeenCalled(); + }); + + it("should handle error event and reset stream", () => { + connectionManager.connectToCentralSystem(); + const errorHandler = fakeStream.on.mock.calls.find( + ([event]) => event === "error" + )![1]; + errorHandler(new Error("test error")); + expect((connectionManager as any).scheduleReconnect).toHaveBeenCalled(); + }); +}); From 326eea5defe5d8cbc232adb23ff680589197ae84 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 8 Jul 2025 20:33:13 +0200 Subject: [PATCH 027/103] fix: change enum values to numbers --- Tokenization/backend/wrapper/models/message.model.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tokenization/backend/wrapper/models/message.model.ts b/Tokenization/backend/wrapper/models/message.model.ts index 8a2a82a3d..d74c98872 100644 --- a/Tokenization/backend/wrapper/models/message.model.ts +++ b/Tokenization/backend/wrapper/models/message.model.ts @@ -23,9 +23,9 @@ * @property MESSAGE_EVENT_REVOKE_TOKEN: Event for revoking an existing token. */ enum DuplexMessageEvent { - MESSAGE_EVENT_EMPTY = "MESSAGE_EVENT_EMPTY", - MESSAGE_EVENT_NEW_TOKEN = "MESSAGE_EVENT_NEW_TOKEN", - MESSAGE_EVENT_REVOKE_TOKEN = "MESSAGE_EVENT_REVOKE_TOKEN", + MESSAGE_EVENT_EMPTY = 0, + MESSAGE_EVENT_NEW_TOKEN = 1, + MESSAGE_EVENT_REVOKE_TOKEN = 2, } // ====================================== From 58576521187923a233a23992d94554722cb3601e Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 8 Jul 2025 20:44:43 +0200 Subject: [PATCH 028/103] fix: move test directory --- .../{utils/__tests__ => test/utils}/presentation.test.ts | 2 +- .../__tests__ => test/utils}/serialization.utils.test.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) rename Tokenization/backend/wrapper/{utils/__tests__ => test/utils}/presentation.test.ts (98%) rename Tokenization/backend/wrapper/{utils/__tests__ => test/utils}/serialization.utils.test.ts (95%) diff --git a/Tokenization/backend/wrapper/utils/__tests__/presentation.test.ts b/Tokenization/backend/wrapper/test/utils/presentation.test.ts similarity index 98% rename from Tokenization/backend/wrapper/utils/__tests__/presentation.test.ts rename to Tokenization/backend/wrapper/test/utils/presentation.test.ts index 039cb3c46..f90e445ba 100644 --- a/Tokenization/backend/wrapper/utils/__tests__/presentation.test.ts +++ b/Tokenization/backend/wrapper/test/utils/presentation.test.ts @@ -1,4 +1,4 @@ -import { serializeRequest } from "../serialization.utils"; +import { serializeRequest } from "../../utils/serialization.utils"; // 1. describe(), test(), it() // test suite "Basic Jest methods" describe("Basic Jest methods", () => { diff --git a/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts b/Tokenization/backend/wrapper/test/utils/serialization.utils.test.ts similarity index 95% rename from Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts rename to Tokenization/backend/wrapper/test/utils/serialization.utils.test.ts index 4c2194784..93d5c7d7c 100644 --- a/Tokenization/backend/wrapper/utils/__tests__/serialization.utils.test.ts +++ b/Tokenization/backend/wrapper/test/utils/serialization.utils.test.ts @@ -12,7 +12,10 @@ * or submit itself to any jurisdiction. */ -import { deserializeRequest, serializeRequest } from "../serialization.utils"; +import { + deserializeRequest, + serializeRequest, +} from "../../utils/serialization.utils"; import { describe, expect, test } from "@jest/globals"; describe("serializeRequest", () => { From 54447ea00ea07fdce40b9b65f4fa90f0f409ac9d Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 8 Jul 2025 21:29:32 +0200 Subject: [PATCH 029/103] feat: add webui logger instead of console logs and fix paths --- .../backend/wrapper/central/CentralSystem.ts | 25 ++- .../ConnectionManager/ConnectionManager.ts | 25 ++- .../backend/wrapper/client/gRPCWrapper.ts | 7 +- .../wrapper/test/utils/presentation.test.ts | 180 ------------------ 4 files changed, 44 insertions(+), 193 deletions(-) delete mode 100644 Tokenization/backend/wrapper/test/utils/presentation.test.ts diff --git a/Tokenization/backend/wrapper/central/CentralSystem.ts b/Tokenization/backend/wrapper/central/CentralSystem.ts index 0130635cf..baa5fbe46 100644 --- a/Tokenization/backend/wrapper/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/central/CentralSystem.ts @@ -2,11 +2,16 @@ import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; import path from "path"; import { fileURLToPath } from "url"; +import { LogManager } from "@aliceo2/web-ui"; /** * @description Central System gRPC wrapper that manages client connections and handles gRPC streams with them. */ export class CentralSystemWrapper { + // utilities + private logger = LogManager.getLogger("CentralSystemWrapper"); + + // class properties private server: grpc.Server; /** @@ -37,7 +42,7 @@ export class CentralSystemWrapper { // Load the package definition into a gRPC object const proto = grpc.loadPackageDefinition(packageDef) as any; - const wrapper = proto.wrapper; + const wrapper = proto.webui.tokenization; // Add the CentralSystem service and bind the stream handler this.server.addService(wrapper.CentralSystem.service, { @@ -50,7 +55,10 @@ export class CentralSystemWrapper { * @param call The duplex stream call object. */ private clientStreamHandler(call: grpc.ServerDuplexStream): void { - console.log("Client connected to duplex stream"); + this.logger.infoMessage( + `Client ${call.getPeer()} connected to CentralSystem stream stream` + ); + // Listen for data events from the client call.on("data", (payload: any) => { // TODO: Implement data handling logic @@ -58,12 +66,17 @@ export class CentralSystemWrapper { // Handle stream end event call.on("end", () => { - console.log("Client ended stream"); + this.logger.infoMessage(`Client ${call.getPeer()} ended stream.`); call.end(); }); // Handle stream error event - call.on("error", (err) => console.error("Stream error:", err)); + call.on("error", (err) => + this.logger.infoMessage( + `Stream error from client ${call.getPeer()}:`, + err + ) + ); } /** @@ -76,10 +89,10 @@ export class CentralSystemWrapper { grpc.ServerCredentials.createInsecure(), (err, _port) => { if (err) { - console.error("Server bind error:", err); + this.logger.infoMessage("Server bind error:", err); return; } - console.log(`Server listening on ${addr}`); + this.logger.infoMessage(`CentralSytem started listening on ${addr}`); } ); } diff --git a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts index b14f65d85..ed6ddb48b 100644 --- a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts @@ -1,5 +1,6 @@ import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; +import { LogManager } from "@aliceo2/web-ui"; /** * @description Manages all the connection between clients and central system. @@ -20,6 +21,10 @@ import * as protoLoader from "@grpc/proto-loader"; * - `reconnectAttempts`: The number of consecutive reconnection attempts made after a disconnect or error. */ export class ConnectionManager { + // utilities + private logger = LogManager.getLogger("ConnectionManager"); + + // class properties private client: any; private stream?: grpc.ClientDuplexStream; private readonly address: string; @@ -45,7 +50,7 @@ export class ConnectionManager { }); const proto = grpc.loadPackageDefinition(packageDef) as any; - const wrapper = proto.wrapper; + const wrapper = proto.webui.tokenization; // Create gRPC client this.client = new wrapper.CentralSystem( @@ -67,13 +72,17 @@ export class ConnectionManager { }); this.stream.on("end", () => { - console.warn("Stream ended, attempting to reconnect..."); + this.logger.infoMessage(`Stream ended, attempting to reconnect...`); this.stream = undefined; this.scheduleReconnect(); }); this.stream.on("error", (err: any) => { - console.error("Wrapper stream error:", err); + this.logger.infoMessage( + `Stream error:`, + err, + " attempting to reconnect..." + ); this.stream = undefined; this.scheduleReconnect(); }); @@ -87,7 +96,9 @@ export class ConnectionManager { this.reconnectAttempts++; const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 30000); setTimeout(() => { - console.log(`Reconnecting (attempt ${this.reconnectAttempts})...`); + this.logger.infoMessage( + `Reconnecting (attempt ${this.reconnectAttempts})...` + ); this.connect(); }, delay); } @@ -98,7 +109,9 @@ export class ConnectionManager { public connectToCentralSystem() { if (!this.stream) { this.connect(); - console.log(`ConnectionManager: connected to ${this.address}`); + this.logger.infoMessage( + `Connected to CentralSystem service at ${this.address}` + ); } } @@ -111,6 +124,6 @@ export class ConnectionManager { this.stream = undefined; } this.reconnectAttempts = 0; - console.log("Disconnected from central"); + this.logger.infoMessage(`Disconnected from CentralSystem service`); } } diff --git a/Tokenization/backend/wrapper/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/client/gRPCWrapper.ts index 898785caf..f7bc48c9a 100644 --- a/Tokenization/backend/wrapper/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/client/gRPCWrapper.ts @@ -1,4 +1,6 @@ +import path from "path"; import { ConnectionManager } from "./ConnectionManager/ConnectionManager.ts"; +import { fileURLToPath } from "url"; /** * @description Wrapper class for managing secure gRPC wrapper. @@ -35,5 +37,8 @@ export class gRPCWrapper { } } -const grpc = new gRPCWrapper("../proto/wrapper.proto", "localhost:50051"); +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); +const grpc = new gRPCWrapper(PROTO_PATH, "localhost:50051"); grpc.connectToCentralSystem(); diff --git a/Tokenization/backend/wrapper/test/utils/presentation.test.ts b/Tokenization/backend/wrapper/test/utils/presentation.test.ts deleted file mode 100644 index f90e445ba..000000000 --- a/Tokenization/backend/wrapper/test/utils/presentation.test.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { serializeRequest } from "../../utils/serialization.utils"; -// 1. describe(), test(), it() -// test suite "Basic Jest methods" -describe("Basic Jest methods", () => { - test("adds numbers correctly", () => { - expect(1 + 2).toBe(3); - }); - - it("subtracts numbers correctly", () => { - expect(5 - 2).toBe(3); - }); -}); - -// 2. expect() + matchers -describe("expect + matchers", () => { - it("should present expect + matchers methods", () => { - expect(2 + 2).toBe(4); // strict equality - expect([1, 2]).toEqual([1, 2]); // deep equality for arrays - expect("hello world").toContain("hello"); - expect(() => JSON.parse("{")).toThrow(); // expects function to throw error - }); - - const add = (a, b) => { - return a + b; - }; - it.each([ - [1, 1, 2], - [2, 2, 4], - [1, 2, 3], - ])("should correctly add numbers", (a, b, result) => { - expect(add(a, b)).toBe(result); - }); -}); - -// 3. Mocking + SpyOn - -// SpyOn -class importedClass { - constructor() {} - - private testFunc = () => { - return "hello from test"; - }; - - public test() { - return this.testFunc(); - } -} - -// mock -jest.mock("../serialization.utils.ts", () => ({ - serializeRequest: jest.fn().mockResolvedValue("Hi"), -})); - -describe("Mocking + SpyOn", () => { - const testClass = new importedClass(); - const spy = jest.spyOn(testClass as any, "testFunc"); - - it("should mock return value of serializeRequest", async () => { - const result = await serializeRequest("/test", {}); - expect(result).toEqual("Hi"); - }); - - it("testFuncCall should be executed with spyon mock", () => { - expect(testClass.test()).toEqual("hello from test"); - spy.mockReturnValue("Hi"); - expect(testClass.test()).toEqual("Hi"); - expect(spy).toHaveBeenCalled(); - }); -}); - -// 4. Resets and cleanups -describe("Resets + mocks", () => { - it("should not call mock after cleanup", () => { - const mock = jest.fn(); - mock(); - jest.clearAllMocks(); - expect(mock).not.toHaveBeenCalled(); - }); - - it("should restore mocks", () => { - const obj = { - greet: () => "hi", - }; - - jest.spyOn(obj, "greet").mockImplementation(() => "mock"); - jest.restoreAllMocks(); - expect(obj.greet()).toBe("hi"); - }); -}); - -// 5. beforeEach -const mockFn = jest.fn(); - -describe("mockFn test suite", () => { - beforeEach(() => { - mockFn.mockClear(); - }); - - test("mockFn is called once", () => { - mockFn(); - expect(mockFn).toHaveBeenCalledTimes(1); - }); - - test("mockFn is clean before this test", () => { - expect(mockFn).not.toHaveBeenCalled(); - }); -}); - -// -// -// ConnectionManager.test.ts - -jest.mock("@grpc/grpc-js", () => ({ - credentials: { createInsecure: jest.fn() }, - loadPackageDefinition: jest.fn(), -})); - -jest.mock("@grpc/proto-loader", () => ({ - loadSync: jest.fn(() => ({})), -})); - -import * as grpc from "@grpc/grpc-js"; -import { ConnectionManager } from "../../client/ConnectionManager/ConnectionManager"; - -describe("ConnectionManager", () => { - let connectionManager: ConnectionManager; - const fakeStream = { - on: jest.fn(), - end: jest.fn(), - }; - - beforeAll(() => { - const fakeWrapper = { - CentralSystem: jest.fn().mockImplementation(() => ({ - ClientStream: jest.fn(() => fakeStream), - })), - }; - - (grpc.loadPackageDefinition as jest.Mock).mockReturnValue({ - wrapper: fakeWrapper, - }); - }); - - beforeEach(() => { - jest.clearAllMocks(); - connectionManager = new ConnectionManager( - "./proto/wrapper.proto", - "localhost:50051" - ); - (connectionManager as any).client = { - ClientStream: jest.fn(() => fakeStream), - }; - - (connectionManager as any).scheduleReconnect = jest.fn(); - }); - - it("should correctly set up data listener on ClientStream", () => { - connectionManager.connectToCentralSystem(); - expect(fakeStream.on).toHaveBeenCalledWith("data", expect.any(Function)); - }); - - it("should warn and reset stream on end event", () => { - connectionManager.connectToCentralSystem(); - const endHandler = fakeStream.on.mock.calls.find( - ([event]) => event === "end" - )![1]; - endHandler(); - expect((connectionManager as any).scheduleReconnect).toHaveBeenCalled(); - }); - - it("should handle error event and reset stream", () => { - connectionManager.connectToCentralSystem(); - const errorHandler = fakeStream.on.mock.calls.find( - ([event]) => event === "error" - )![1]; - errorHandler(new Error("test error")); - expect((connectionManager as any).scheduleReconnect).toHaveBeenCalled(); - }); -}); From 9db612e159c0c4b8314be4586626e895eecd189d Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Wed, 9 Jul 2025 19:53:51 +0200 Subject: [PATCH 030/103] feat: write tests for central system and connection manager --- .../backend/wrapper/central/CentralSystem.ts | 12 +- .../backend/wrapper/client/gRPCWrapper.ts | 2 - .../test/central/CentralSystem.test.ts | 117 ++++++++++++++++++ .../ConnectionManager.test.ts | 108 ++++++++++++++++ 4 files changed, 229 insertions(+), 10 deletions(-) create mode 100644 Tokenization/backend/wrapper/test/central/CentralSystem.test.ts create mode 100644 Tokenization/backend/wrapper/test/client/ConnectionManager/ConnectionManager.test.ts diff --git a/Tokenization/backend/wrapper/central/CentralSystem.ts b/Tokenization/backend/wrapper/central/CentralSystem.ts index baa5fbe46..350984751 100644 --- a/Tokenization/backend/wrapper/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/central/CentralSystem.ts @@ -1,7 +1,6 @@ import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; import path from "path"; -import { fileURLToPath } from "url"; import { LogManager } from "@aliceo2/web-ui"; /** @@ -18,7 +17,7 @@ export class CentralSystemWrapper { * Initializes the Wrapper for CentralSystem. * @param port The port number to bind the gRPC server to. */ - constructor(private port: number) { + constructor(private protoPath: string, private port: number) { this.server = new grpc.Server(); this.setupService(); } @@ -27,12 +26,8 @@ export class CentralSystemWrapper { * @description Loads the gRPC proto definition and sets up the CentralSystem service. */ private setupService(): void { - const __filename = fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); - const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); - // Load the proto definition with options - const packageDef = protoLoader.loadSync(PROTO_PATH, { + const packageDef = protoLoader.loadSync(this.protoPath, { keepCase: true, longs: String, enums: String, @@ -99,6 +94,7 @@ export class CentralSystemWrapper { } // Instantiate the CentralSystemWrapper on port 50051, but don't start automatically -const centralSystem = new CentralSystemWrapper(50051); +const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); +const centralSystem = new CentralSystemWrapper(PROTO_PATH, 50051); // Start listening explicitly centralSystem.listen(); diff --git a/Tokenization/backend/wrapper/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/client/gRPCWrapper.ts index f7bc48c9a..486f30e6c 100644 --- a/Tokenization/backend/wrapper/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/client/gRPCWrapper.ts @@ -37,8 +37,6 @@ export class gRPCWrapper { } } -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); const grpc = new gRPCWrapper(PROTO_PATH, "localhost:50051"); grpc.connectToCentralSystem(); diff --git a/Tokenization/backend/wrapper/test/central/CentralSystem.test.ts b/Tokenization/backend/wrapper/test/central/CentralSystem.test.ts new file mode 100644 index 000000000..1599e0ccd --- /dev/null +++ b/Tokenization/backend/wrapper/test/central/CentralSystem.test.ts @@ -0,0 +1,117 @@ +const mockAddService = jest.fn(); +const mockBindAsync = jest.fn(); +const mockServerInstance = { + addService: mockAddService, + bindAsync: mockBindAsync, +}; + +const logger = { + infoMessage: jest.fn(), +}; + +jest.mock("@aliceo2/web-ui", () => ({ + LogManager: { + getLogger: () => logger, + }, +})); + +jest.mock("@grpc/proto-loader", () => ({ + loadSync: jest.fn(() => { + return {}; + }), +})); + +jest.mock("@grpc/grpc-js", () => { + const original = jest.requireActual("@grpc/grpc-js"); + return { + ...original, + Server: jest.fn(() => mockServerInstance), + ServerCredentials: { + createInsecure: jest.fn(() => "mock-credentials"), + }, + loadPackageDefinition: jest.fn(() => ({ + webui: { + tokenization: { + CentralSystem: { + service: "mock-service", + }, + }, + }, + })), + }; +}); + +import { CentralSystemWrapper } from "../../central/CentralSystem"; +import * as grpc from "@grpc/grpc-js"; + +describe("CentralSystemWrapper", () => { + let wrapper: CentralSystemWrapper; + + beforeEach(() => { + jest.clearAllMocks(); + wrapper = new CentralSystemWrapper("dummy.proto", 12345); + }); + + test("should set up gRPC service and add it to the server", () => { + expect(grpc.Server).toHaveBeenCalled(); + expect(grpc.loadPackageDefinition).toHaveBeenCalled(); + expect(grpc.ServerCredentials.createInsecure).not.toHaveBeenCalled(); + expect(wrapper).toBeDefined(); + }); + + test("should call listen and bind the server", () => { + mockBindAsync.mockImplementation((_addr, _creds, cb) => cb(null, 12345)); + + wrapper.listen(); + + expect(mockBindAsync).toHaveBeenCalledWith( + "localhost:12345", + "mock-credentials", + expect.any(Function) + ); + }); + + test("should log error if bind fails", () => { + const error = new Error("bind failed"); + mockBindAsync.mockImplementation((_addr, _creds, cb) => cb(error, null)); + + wrapper.listen(); + + expect(logger.infoMessage).toHaveBeenCalledWith( + "Server bind error:", + error + ); + }); + + test("should handle client stream events", () => { + const logger = require("@aliceo2/web-ui").LogManager.getLogger(); + + const mockCall = { + getPeer: jest.fn(() => "client123"), + on: jest.fn((event, cb) => { + if (event === "end") cb(); + if (event === "error") cb(new Error("stream error")); + }), + end: jest.fn(), + }; + + const handler = (wrapper as any).clientStreamHandler.bind(wrapper); + handler(mockCall); + + expect(mockCall.on).toHaveBeenCalledWith("data", expect.any(Function)); + expect(mockCall.on).toHaveBeenCalledWith("end", expect.any(Function)); + expect(mockCall.on).toHaveBeenCalledWith("error", expect.any(Function)); + + expect(mockCall.end).toHaveBeenCalled(); + expect(logger.infoMessage).toHaveBeenCalledWith( + "Client client123 connected to CentralSystem stream stream" + ); + expect(logger.infoMessage).toHaveBeenCalledWith( + "Client client123 ended stream." + ); + expect(logger.infoMessage).toHaveBeenCalledWith( + "Stream error from client client123:", + expect.any(Error) + ); + }); +}); diff --git a/Tokenization/backend/wrapper/test/client/ConnectionManager/ConnectionManager.test.ts b/Tokenization/backend/wrapper/test/client/ConnectionManager/ConnectionManager.test.ts new file mode 100644 index 000000000..7f55df35e --- /dev/null +++ b/Tokenization/backend/wrapper/test/client/ConnectionManager/ConnectionManager.test.ts @@ -0,0 +1,108 @@ +import * as grpc from "@grpc/grpc-js"; +import { ConnectionManager } from "../../../client/ConnectionManager/ConnectionManager"; + +// Mock of client and stream +const mockStream = { + on: jest.fn(), + end: jest.fn(), +}; + +const mockClient = { + ClientStream: jest.fn(() => mockStream), +}; + +jest.mock("@aliceo2/web-ui", () => ({ + LogManager: { + getLogger: () => ({ + infoMessage: jest.fn(), + }), + }, +})); + +jest.mock("@grpc/proto-loader", () => ({ + loadSync: jest.fn(() => { + return {}; + }), +})); + +jest.mock("@grpc/grpc-js", () => { + const original = jest.requireActual("@grpc/grpc-js"); + return { + ...original, + credentials: { + createInsecure: jest.fn(), + }, + loadPackageDefinition: jest.fn(() => ({ + webui: { + tokenization: { + CentralSystem: jest.fn(() => mockClient), + }, + }, + })), + }; +}); + +describe("ConnectionManager", () => { + let conn: ConnectionManager; + + beforeEach(() => { + jest.clearAllMocks(); + conn = new ConnectionManager("dummy.proto", "localhost:12345"); + }); + + test("should initialize client with correct address", () => { + expect(conn).toBeDefined(); + expect(grpc.loadPackageDefinition).toHaveBeenCalled(); + }); + + test("connectToCentralSystem() should create stream and log message", () => { + conn.connectToCentralSystem(); + + expect(mockClient.ClientStream).toHaveBeenCalled(); + expect(mockStream.on).toHaveBeenCalledWith("data", expect.any(Function)); + expect(mockStream.on).toHaveBeenCalledWith("end", expect.any(Function)); + expect(mockStream.on).toHaveBeenCalledWith("error", expect.any(Function)); + }); + + test("disconnect() should end stream and reset reconnectAttempts", () => { + conn.connectToCentralSystem(); + conn.disconnect(); + + expect(mockStream.end).toHaveBeenCalled(); + }); + + test("scheduleReconnect() should call connect after delay", () => { + jest.useFakeTimers(); + const spy = jest.spyOn(conn as any, "connect"); + + (conn as any).scheduleReconnect(); + + jest.advanceTimersByTime(1000 * 2); // pierwszy delay = 2^1 * 1000 + expect(spy).toHaveBeenCalled(); + jest.useRealTimers(); + }); + + test("should reconnect on stream 'end'", () => { + conn.connectToCentralSystem(); + const onEnd = mockStream.on.mock.calls.find( + ([event]) => event === "end" + )[1]; + + const reconnectSpy = jest.spyOn(conn as any, "scheduleReconnect"); + onEnd(); + + expect(reconnectSpy).toHaveBeenCalled(); + }); + + test("should reconnect on stream 'error'", () => { + conn.connectToCentralSystem(); + const onError = mockStream.on.mock.calls.find( + ([event]) => event === "error" + )[1]; + + const reconnectSpy = jest.spyOn(conn as any, "scheduleReconnect"); + onError(new Error("Stream failed")); + + expect(reconnectSpy).toHaveBeenCalled(); + }); +}); From d0f4daddc9f4d60f0c326038c83d5fe23c34d705 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 10 Jul 2025 19:32:18 +0200 Subject: [PATCH 031/103] feat: muldularize connection management. Add project building. --- Tokenization/backend/wrapper/.gitignore | 2 + .../backend/wrapper/central/CentralSystem.ts | 3 + .../wrapper/client/Connection/Connection.ts | 13 + .../ConnectionManager/CentralConnection.ts | 91 + .../ConnectionManager/ConnectionManager.ts | 130 +- .../EventManagement/EventDispatcher.ts | 37 + .../backend/wrapper/client/gRPCWrapper.ts | 4 +- .../backend/wrapper/models/events.model.ts | 23 + .../backend/wrapper/package-lock.json | 4399 ++--------------- Tokenization/backend/wrapper/package.json | 10 +- Tokenization/backend/wrapper/tsconfig.json | 15 +- .../backend/wrapper/utils/types/webui.d.ts | 10 + 12 files changed, 619 insertions(+), 4118 deletions(-) create mode 100644 Tokenization/backend/wrapper/.gitignore create mode 100644 Tokenization/backend/wrapper/client/ConnectionManager/CentralConnection.ts create mode 100644 Tokenization/backend/wrapper/client/ConnectionManager/EventManagement/EventDispatcher.ts create mode 100644 Tokenization/backend/wrapper/models/events.model.ts create mode 100644 Tokenization/backend/wrapper/utils/types/webui.d.ts diff --git a/Tokenization/backend/wrapper/.gitignore b/Tokenization/backend/wrapper/.gitignore new file mode 100644 index 000000000..763301fc0 --- /dev/null +++ b/Tokenization/backend/wrapper/.gitignore @@ -0,0 +1,2 @@ +dist/ +node_modules/ \ No newline at end of file diff --git a/Tokenization/backend/wrapper/central/CentralSystem.ts b/Tokenization/backend/wrapper/central/CentralSystem.ts index 350984751..a0a7a7858 100644 --- a/Tokenization/backend/wrapper/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/central/CentralSystem.ts @@ -2,6 +2,7 @@ import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; import path from "path"; import { LogManager } from "@aliceo2/web-ui"; +import { fileURLToPath } from "url"; /** * @description Central System gRPC wrapper that manages client connections and handles gRPC streams with them. @@ -94,6 +95,8 @@ export class CentralSystemWrapper { } // Instantiate the CentralSystemWrapper on port 50051, but don't start automatically +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); const centralSystem = new CentralSystemWrapper(PROTO_PATH, 50051); // Start listening explicitly diff --git a/Tokenization/backend/wrapper/client/Connection/Connection.ts b/Tokenization/backend/wrapper/client/Connection/Connection.ts index 00ec4f381..b45dd354e 100644 --- a/Tokenization/backend/wrapper/client/Connection/Connection.ts +++ b/Tokenization/backend/wrapper/client/Connection/Connection.ts @@ -1,3 +1,16 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ /** * @description This class represents a connection to a target client and manages sending messages to it. */ diff --git a/Tokenization/backend/wrapper/client/ConnectionManager/CentralConnection.ts b/Tokenization/backend/wrapper/client/ConnectionManager/CentralConnection.ts new file mode 100644 index 000000000..3a06c33fe --- /dev/null +++ b/Tokenization/backend/wrapper/client/ConnectionManager/CentralConnection.ts @@ -0,0 +1,91 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ +import * as grpc from "@grpc/grpc-js"; +import { LogManager } from "@aliceo2/web-ui"; +import type { MessageHandler } from "../../models/events.model"; + +/** + * @description This class manages the duplex stream with the CentralSystem gRPC service. + * It is responsible for connecting, reconnecting with backoff, and delegating received messages. + */ +export class CentralConnection { + private logger = LogManager.getLogger("CentralConnection"); + private stream?: grpc.ClientDuplexStream; + private reconnectAttempts = 0; + + constructor(private client: any, private handler: MessageHandler) {} + + /** + * @description Initializes the duplex stream and sets up event handlers. + */ + connect() { + if (this.stream) return; + + this.stream = this.client.ClientStream(); + + this.stream!.on("data", (payload) => { + this.handler.handle(payload); + }); + + this.stream!.on("end", () => { + this.logger.infoMessage(`Stream ended, attempting to reconnect...`); + this.stream = undefined; + this.scheduleReconnect(); + }); + + this.stream!.on("error", (err: any) => { + this.logger.infoMessage( + "Stream error:", + err, + " attempting to reconnect..." + ); + this.stream = undefined; + this.scheduleReconnect(); + }); + } + + /** + * @description Schedules a reconnect with exponential backoff. + */ + private scheduleReconnect() { + this.reconnectAttempts++; + const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 30000); + setTimeout(() => { + this.logger.infoMessage( + `Reconnecting (attempt ${this.reconnectAttempts})...` + ); + this.connect(); + }, delay); + } + + /** + * @description Starts the connection to the central system. + */ + start() { + this.connect(); + this.logger.infoMessage(`Connected to CentralSystem`); + } + + /** + * @description Disconnects from the gRPC stream and resets attempts. + */ + disconnect() { + if (this.stream) { + this.stream.end(); + this.stream = undefined; + } + this.reconnectAttempts = 0; + this.logger.infoMessage(`Disconnected from CentralSystem`); + } +} diff --git a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts index 50a2a9b67..7d5bb415c 100644 --- a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts @@ -1,7 +1,22 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; +import { CentralConnection } from "./CentralConnection"; +import { EventDispatcher } from "../ConnectionManager/EventManagement/EventDispatcher"; +import { Connection } from "../Connection/Connection"; import { LogManager } from "@aliceo2/web-ui"; -import { Connection } from "../Connection/Connection.ts"; /** * @description Manages all the connection between clients and central system. @@ -11,37 +26,19 @@ import { Connection } from "../Connection/Connection.ts"; * * This class is responsible for: * - Initializing the gRPC client using the provided proto definition and address. - * - Managing a duplex stream (`stream`) for bidirectional communication. - * - Handling automatic reconnection with exponential backoff on stream errors or disconnects. - * - Providing methods to start (`connectToCentralSystem`) and stop (`disconnect`) the connection with central system. + * - Delegating stream handling to CentralConnection. + * - Managing sending/receiving connections to other clients. * * @remarks - * - `client`: The gRPC client instance for communicating with the central system. - * - `stream`: The active duplex stream for sending and receiving messages (optional). - * - `address`: The address of the central gRPC server. - * - `reconnectAttempts`: The number of consecutive reconnection attempts made after a disconnect or error. + * - `centralConnection`: Handles the duplex stream with the central gRPC server. + * - `sendingConnections`: Map of active outbound connections. + * - `receivingConnections`: Map of active inbound connections. */ export class ConnectionManager { - // utilities private logger = LogManager.getLogger("ConnectionManager"); - - // class properties - private client: any; - private stream?: grpc.ClientDuplexStream; - private readonly address: string; - private reconnectAttempts = 0; - - // Map to store sending connections by target address - private sendingConnections: Map = new Map(); - - // Map to store receiving connections by target address - private receivingConnections: Map = new Map(); - - // Map to store sending connections by target address - private sendingConnections: Map = new Map(); - - // Map to store receiving connections by target address - private receivingConnections: Map = new Map(); + private centralConnection: CentralConnection; + private sendingConnections = new Map(); + private receivingConnections = new Map(); /** * @description Initializes a new instance of the ConnectionManager class. @@ -52,8 +49,6 @@ export class ConnectionManager { * @param centralAddress - The address of the central gRPC server (default: "localhost:50051"). */ constructor(protoPath: string, centralAddress: string = "localhost:50051") { - this.address = centralAddress; - const packageDef = protoLoader.loadSync(protoPath, { keepCase: true, longs: String, @@ -65,86 +60,29 @@ export class ConnectionManager { const proto = grpc.loadPackageDefinition(packageDef) as any; const wrapper = proto.webui.tokenization; - // Create gRPC client - this.client = new wrapper.CentralSystem( - this.address, + const client = new wrapper.CentralSystem( + centralAddress, grpc.credentials.createInsecure() ); + const dispatcher = new EventDispatcher(); + this.centralConnection = new CentralConnection(client, dispatcher); + this.sendingConnections.set("a", new Connection("1", "a")); this.sendingConnections.set("b", new Connection("2", "b")); } - /** - * @description Initializes the duplex stream and sets up handlers. - */ - private connect() { - if (this.stream) return; - this.stream = this.client.ClientStream(); - - if (this.stream) { - this.stream.on("data", (payload) => { - switch (payload.event) { - // Central system replacing a new token for existing connection - case "EMPTY_EVENT": - console.log("Empty event: ", payload?.data); - break; - } - }); - - this.stream.on("end", () => { - this.logger.infoMessage(`Stream ended, attempting to reconnect...`); - this.stream = undefined; - this.scheduleReconnect(); - }); - - this.stream.on("error", (err: any) => { - this.logger.infoMessage( - `Stream error:`, - err, - " attempting to reconnect..." - ); - this.stream = undefined; - this.scheduleReconnect(); - }); - } - } - - /** - * @description Schedules a reconnect with exponential backoff. - */ - private scheduleReconnect() { - this.reconnectAttempts++; - const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 30000); - setTimeout(() => { - this.logger.infoMessage( - `Reconnecting (attempt ${this.reconnectAttempts})...` - ); - this.connect(); - }, delay); - } - /** * @description Starts the connection to the central system. */ - public connectToCentralSystem() { - if (!this.stream) { - this.connect(); - this.logger.infoMessage( - `Connected to CentralSystem service at ${this.address}` - ); - } + connectToCentralSystem() { + this.centralConnection.start(); } /** - * @description Disconnects from the gRPC stream and resets attempts. + * @description Disconnects from the central system. */ - public disconnect() { - if (this.stream) { - this.stream.end(); - this.stream = undefined; - } - this.reconnectAttempts = 0; - this.logger.infoMessage(`Disconnected from CentralSystem service`); + disconnectFromCentralSystem() { + this.centralConnection.disconnect(); } } diff --git a/Tokenization/backend/wrapper/client/ConnectionManager/EventManagement/EventDispatcher.ts b/Tokenization/backend/wrapper/client/ConnectionManager/EventManagement/EventDispatcher.ts new file mode 100644 index 000000000..1f9d17be0 --- /dev/null +++ b/Tokenization/backend/wrapper/client/ConnectionManager/EventManagement/EventDispatcher.ts @@ -0,0 +1,37 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ +import type { MessageHandler } from "../../../models/events.model"; +import { LogManager } from "@aliceo2/web-ui"; + +/** + * @description Dispatches gRPC stream events received from CentralSystem. + */ +export class EventDispatcher implements MessageHandler { + private logger = LogManager.getLogger("ConnectionManager"); + + /** + * @description Handles incoming events from the gRPC stream. + * + * @param event - The event object received from the stream. + */ + handle(event: any): void { + switch (event.event) { + case "EMPTY_EVENT": + // handle empty event + break; + default: + this.logger.infoMessage("Unknown event type:", event.event); + } + } +} diff --git a/Tokenization/backend/wrapper/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/client/gRPCWrapper.ts index 486f30e6c..66cfb6c48 100644 --- a/Tokenization/backend/wrapper/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/client/gRPCWrapper.ts @@ -1,5 +1,5 @@ import path from "path"; -import { ConnectionManager } from "./ConnectionManager/ConnectionManager.ts"; +import { ConnectionManager } from "./ConnectionManager/ConnectionManager"; import { fileURLToPath } from "url"; /** @@ -37,6 +37,8 @@ export class gRPCWrapper { } } +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); const grpc = new gRPCWrapper(PROTO_PATH, "localhost:50051"); grpc.connectToCentralSystem(); diff --git a/Tokenization/backend/wrapper/models/events.model.ts b/Tokenization/backend/wrapper/models/events.model.ts new file mode 100644 index 000000000..ff20b71ff --- /dev/null +++ b/Tokenization/backend/wrapper/models/events.model.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +/** + * Interface representing a handler for processing events. + * + * @remarks + * The `handle` method receives an event object and performs the necessary processing. + */ +export interface MessageHandler { + handle(event: any): void; +} diff --git a/Tokenization/backend/wrapper/package-lock.json b/Tokenization/backend/wrapper/package-lock.json index ef28121ab..6c5036d55 100644 --- a/Tokenization/backend/wrapper/package-lock.json +++ b/Tokenization/backend/wrapper/package-lock.json @@ -7,4015 +7,6 @@ "": { "name": "grpc-wrapper", "version": "1.0.0", - "license": "ISC", - "dependencies": { - "ts-node": "^10.9.2", - "typescript": "^5.8.3" - }, - "devDependencies": { - "@types/jest": "^29.5.14", - "jest": "^29.7.0", - "ts-jest": "^29.3.4" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", - "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", - "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "license": "MIT" - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", - "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", - "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/node": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.0.tgz", - "integrity": "sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.8.0" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "license": "MIT" - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, - "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", - "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", - "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001721", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", - "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dedent": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", - "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.166", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz", - "integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/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, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "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, - "license": "MIT" - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "license": "ISC" - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-jest": { - "version": "29.3.4", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.4.tgz", - "integrity": "sha512-Iqbrm8IXOmV+ggWHOTEbjwyCf2xZlUMv5npExksXohL+tk8va4Fjhb+X2+Rt9NBmgO7bJ8WpnMLOwih/DnMlFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bs-logger": "^0.2.6", - "ejs": "^3.1.10", - "fast-json-stable-stringify": "^2.1.0", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.7.2", - "type-fest": "^4.41.0", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "license": "MIT" - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} - -{ - "name": "grpc-wrapper", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "grpc-wrapper", - "version": "1.0.0", - "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.13.4", "@grpc/proto-loader": "^0.7.15", @@ -4025,7 +16,8 @@ "devDependencies": { "@types/jest": "^29.5.14", "jest": "^29.7.0", - "ts-jest": "^29.3.4" + "ts-jest": "^29.3.4", + "tsc-alias": "^1.8.16" } }, "node_modules/@ampproject/remapping": { @@ -4961,6 +953,44 @@ "url": "https://opencollective.com/js-sdsl" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -5296,6 +1326,16 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -5426,6 +1466,19 @@ "dev": true, "license": "MIT" }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5581,6 +1634,31 @@ "node": ">=10" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -5654,6 +1732,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5783,6 +1871,19 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -5908,6 +2009,23 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -5915,6 +2033,16 @@ "dev": true, "license": "MIT" }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -6059,6 +2187,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -6081,6 +2222,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -6091,6 +2245,27 @@ "node": ">=4" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -6138,6 +2313,16 @@ "node": ">=10.17.0" } }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -6194,6 +2379,19 @@ "dev": true, "license": "MIT" }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -6210,6 +2408,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -6229,6 +2437,19 @@ "node": ">=6" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -7133,6 +3354,16 @@ "dev": true, "license": "MIT" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -7177,6 +3408,20 @@ "dev": true, "license": "MIT" }, + "node_modules/mylas": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.13.tgz", + "integrity": "sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/raouldeheer" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -7358,6 +3603,16 @@ "dev": true, "license": "MIT" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -7401,6 +3656,19 @@ "node": ">=8" } }, + "node_modules/plimit-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "queue-lit": "^1.5.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -7484,6 +3752,37 @@ ], "license": "MIT" }, + "node_modules/queue-lit": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -7491,6 +3790,19 @@ "dev": true, "license": "MIT" }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7544,6 +3856,16 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve.exports": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", @@ -7554,6 +3876,41 @@ "node": ">=10" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -7915,6 +4272,28 @@ } } }, + "node_modules/tsc-alias": { + "version": "1.8.16", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz", + "integrity": "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "get-tsconfig": "^4.10.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + }, + "bin": { + "tsc-alias": "dist/bin/index.js" + }, + "engines": { + "node": ">=16.20.2" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", diff --git a/Tokenization/backend/wrapper/package.json b/Tokenization/backend/wrapper/package.json index 3247319db..02ce46ed5 100644 --- a/Tokenization/backend/wrapper/package.json +++ b/Tokenization/backend/wrapper/package.json @@ -4,14 +4,18 @@ "type": "module", "scripts": { "test": "jest", - "start-central": "node --loader ts-node/esm ./central/CentralSystem.ts", - "start-client": "node --loader ts-node/esm ./client/gRPCWrapper.ts" + "build": "tsc && tsc-alias --resolve-full-paths && cp -r proto dist", + "start-client": "node dist/client/gRPCWrapper.js", + "start-central": "node dist/central/CentralSystem.js", + "dev-client": "ts-node-esm client/gRPCWrapper.ts", + "dev-central": "ts-node-esm central/CentralSystem.ts" }, "author": "ALICEO2", "devDependencies": { "@types/jest": "^29.5.14", "jest": "^29.7.0", - "ts-jest": "^29.3.4" + "ts-jest": "^29.3.4", + "tsc-alias": "^1.8.16" }, "dependencies": { "@grpc/grpc-js": "^1.13.4", diff --git a/Tokenization/backend/wrapper/tsconfig.json b/Tokenization/backend/wrapper/tsconfig.json index 32d70e3e9..d2e69b3a7 100644 --- a/Tokenization/backend/wrapper/tsconfig.json +++ b/Tokenization/backend/wrapper/tsconfig.json @@ -1,15 +1,14 @@ { "compilerOptions": { - "module": "ESNext", "target": "ES2022", + "module": "ESNext", "moduleResolution": "node", + "outDir": "./dist", + "rootDir": "./", "esModuleInterop": true, - "skipLibCheck": true, - "resolveJsonModule": true, - "allowImportingTsExtensions": true, - "noEmit": true + "forceConsistentCasingInFileNames": true, + "verbatimModuleSyntax": true, + "strict": true }, - "ts-node": { - "esm": true - } + "include": ["client", "central", "utils", "types"] } diff --git a/Tokenization/backend/wrapper/utils/types/webui.d.ts b/Tokenization/backend/wrapper/utils/types/webui.d.ts new file mode 100644 index 000000000..1f2aeee51 --- /dev/null +++ b/Tokenization/backend/wrapper/utils/types/webui.d.ts @@ -0,0 +1,10 @@ +declare module "@aliceo2/web-ui" { + export const LogManager: { + getLogger: (name: string) => { + infoMessage: (...args: any[]) => void; + errorMessage: (...args: any[]) => void; + warnMessage: (...args: any[]) => void; + debugMessage: (...args: any[]) => void; + }; + }; +} From b4a0bf9fecfffdbf06752a306fc93bb6713267d7 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 10 Jul 2025 20:00:19 +0200 Subject: [PATCH 032/103] fix: fix connection and remove exponential backoff for simplified version --- .../client/ConnectionManager/CentralConnection.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Tokenization/backend/wrapper/client/ConnectionManager/CentralConnection.ts b/Tokenization/backend/wrapper/client/ConnectionManager/CentralConnection.ts index 3a06c33fe..fee5a82b5 100644 --- a/Tokenization/backend/wrapper/client/ConnectionManager/CentralConnection.ts +++ b/Tokenization/backend/wrapper/client/ConnectionManager/CentralConnection.ts @@ -22,7 +22,6 @@ import type { MessageHandler } from "../../models/events.model"; export class CentralConnection { private logger = LogManager.getLogger("CentralConnection"); private stream?: grpc.ClientDuplexStream; - private reconnectAttempts = 0; constructor(private client: any, private handler: MessageHandler) {} @@ -59,14 +58,10 @@ export class CentralConnection { * @description Schedules a reconnect with exponential backoff. */ private scheduleReconnect() { - this.reconnectAttempts++; - const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 30000); setTimeout(() => { - this.logger.infoMessage( - `Reconnecting (attempt ${this.reconnectAttempts})...` - ); + this.logger.infoMessage(`Trying to reconnect...`); this.connect(); - }, delay); + }, 2000); } /** @@ -85,7 +80,6 @@ export class CentralConnection { this.stream.end(); this.stream = undefined; } - this.reconnectAttempts = 0; this.logger.infoMessage(`Disconnected from CentralSystem`); } } From 3b499234a1572cb7316ca93979aa7a8295829949 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki <42175519+OmegaCreations@users.noreply.github.com> Date: Sun, 13 Jul 2025 14:09:52 +0200 Subject: [PATCH 033/103] Fix: Unused function import Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- Tokenization/backend/wrapper/client/gRPCWrapper.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/Tokenization/backend/wrapper/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/client/gRPCWrapper.ts index 486f30e6c..d77b8fa33 100644 --- a/Tokenization/backend/wrapper/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/client/gRPCWrapper.ts @@ -1,6 +1,5 @@ import path from "path"; import { ConnectionManager } from "./ConnectionManager/ConnectionManager.ts"; -import { fileURLToPath } from "url"; /** * @description Wrapper class for managing secure gRPC wrapper. From 8d9c89a31ef973eb8801f3cb2f62f843afc1ea86 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Sun, 20 Jul 2025 15:06:04 +0200 Subject: [PATCH 034/103] fix: test setup fixes --- Tokenization/backend/wrapper/jest.config.js | 13 ------------ Tokenization/backend/wrapper/jest.config.mjs | 11 ++++++++++ .../backend/wrapper/package-lock.json | 21 +++++++++++-------- Tokenization/backend/wrapper/package.json | 2 +- .../ConnectionManager.test.ts | 2 +- Tokenization/backend/wrapper/tsconfig.json | 8 +++---- .../backend/wrapper/tsconfig.test.json | 13 ++++++++++++ Tokenization/package-lock.json | 6 ++++++ 8 files changed, 48 insertions(+), 28 deletions(-) delete mode 100644 Tokenization/backend/wrapper/jest.config.js create mode 100644 Tokenization/backend/wrapper/jest.config.mjs create mode 100644 Tokenization/backend/wrapper/tsconfig.test.json create mode 100644 Tokenization/package-lock.json diff --git a/Tokenization/backend/wrapper/jest.config.js b/Tokenization/backend/wrapper/jest.config.js deleted file mode 100644 index 3a5675ad5..000000000 --- a/Tokenization/backend/wrapper/jest.config.js +++ /dev/null @@ -1,13 +0,0 @@ -/** @type {import('jest').Config} */ -const config = { - verbose: true, - transform: { - "^.+\\.ts$": ["ts-jest", { useESM: true }], - }, - extensionsToTreatAsEsm: [".ts"], - moduleNameMapper: { - "^(\\.{1,2}/.*)\\.js$": "$1", - }, -}; - -export default config; diff --git a/Tokenization/backend/wrapper/jest.config.mjs b/Tokenization/backend/wrapper/jest.config.mjs new file mode 100644 index 000000000..d74308377 --- /dev/null +++ b/Tokenization/backend/wrapper/jest.config.mjs @@ -0,0 +1,11 @@ +export default { + preset: "ts-jest/presets/default-esm", + transform: { + "^.+\\.ts$": ["ts-jest", { useESM: true, tsconfig: "tsconfig.test.json" }], + }, + extensionsToTreatAsEsm: [".ts"], + testEnvironment: "node", + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, +}; diff --git a/Tokenization/backend/wrapper/package-lock.json b/Tokenization/backend/wrapper/package-lock.json index 6c5036d55..1a24c0038 100644 --- a/Tokenization/backend/wrapper/package-lock.json +++ b/Tokenization/backend/wrapper/package-lock.json @@ -16,7 +16,7 @@ "devDependencies": { "@types/jest": "^29.5.14", "jest": "^29.7.0", - "ts-jest": "^29.3.4", + "ts-jest": "^29.4.0", "tsc-alias": "^1.8.16" } }, @@ -4154,16 +4154,15 @@ } }, "node_modules/ts-jest": { - "version": "29.3.4", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.4.tgz", - "integrity": "sha512-Iqbrm8IXOmV+ggWHOTEbjwyCf2xZlUMv5npExksXohL+tk8va4Fjhb+X2+Rt9NBmgO7bJ8WpnMLOwih/DnMlFA==", + "version": "29.4.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.0.tgz", + "integrity": "sha512-d423TJMnJGu80/eSgfQ5w/R+0zFJvdtTxwtF9KzFFunOpSeD+79lHJQIiAhluJoyGRbvj9NZJsl9WjCUo0ND7Q==", "dev": true, "license": "MIT", "dependencies": { "bs-logger": "^0.2.6", "ejs": "^3.1.10", "fast-json-stable-stringify": "^2.1.0", - "jest-util": "^29.0.0", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", @@ -4179,10 +4178,11 @@ }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", "typescript": ">=4.3 <6" }, "peerDependenciesMeta": { @@ -4200,6 +4200,9 @@ }, "esbuild": { "optional": true + }, + "jest-util": { + "optional": true } } }, diff --git a/Tokenization/backend/wrapper/package.json b/Tokenization/backend/wrapper/package.json index 02ce46ed5..060701af5 100644 --- a/Tokenization/backend/wrapper/package.json +++ b/Tokenization/backend/wrapper/package.json @@ -14,7 +14,7 @@ "devDependencies": { "@types/jest": "^29.5.14", "jest": "^29.7.0", - "ts-jest": "^29.3.4", + "ts-jest": "^29.4.0", "tsc-alias": "^1.8.16" }, "dependencies": { diff --git a/Tokenization/backend/wrapper/test/client/ConnectionManager/ConnectionManager.test.ts b/Tokenization/backend/wrapper/test/client/ConnectionManager/ConnectionManager.test.ts index 7f55df35e..de0a626a6 100644 --- a/Tokenization/backend/wrapper/test/client/ConnectionManager/ConnectionManager.test.ts +++ b/Tokenization/backend/wrapper/test/client/ConnectionManager/ConnectionManager.test.ts @@ -66,7 +66,7 @@ describe("ConnectionManager", () => { test("disconnect() should end stream and reset reconnectAttempts", () => { conn.connectToCentralSystem(); - conn.disconnect(); + conn.disconnectFromCentralSystem(); expect(mockStream.end).toHaveBeenCalled(); }); diff --git a/Tokenization/backend/wrapper/tsconfig.json b/Tokenization/backend/wrapper/tsconfig.json index d2e69b3a7..17a9fe200 100644 --- a/Tokenization/backend/wrapper/tsconfig.json +++ b/Tokenization/backend/wrapper/tsconfig.json @@ -1,14 +1,14 @@ { "compilerOptions": { - "target": "ES2022", + "target": "ES2020", "module": "ESNext", "moduleResolution": "node", - "outDir": "./dist", "rootDir": "./", + "outDir": "./dist", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "verbatimModuleSyntax": true, - "strict": true + "strict": true, + "skipLibCheck": true }, "include": ["client", "central", "utils", "types"] } diff --git a/Tokenization/backend/wrapper/tsconfig.test.json b/Tokenization/backend/wrapper/tsconfig.test.json new file mode 100644 index 000000000..32ee3a99a --- /dev/null +++ b/Tokenization/backend/wrapper/tsconfig.test.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "target": "ES2022", + "moduleResolution": "node", + "verbatimModuleSyntax": true, + "allowImportingTsExtensions": false, + "esModuleInterop": true, + "types": ["jest"] + }, + "include": ["test", "client", "central", "utils", "types"] +} diff --git a/Tokenization/package-lock.json b/Tokenization/package-lock.json new file mode 100644 index 000000000..c7142dc66 --- /dev/null +++ b/Tokenization/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "Tokenization", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} From b6695a497e6fef472f2edf1c8605538375f59363 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Mon, 28 Jul 2025 20:09:14 +0200 Subject: [PATCH 035/103] fix: fix tscofing for js builds and typescript files/tests runtime --- Tokenization/backend/wrapper/jest.config.mjs | 11 ---- Tokenization/backend/wrapper/jest.config.ts | 13 ++++ Tokenization/backend/wrapper/package.json | 8 +-- .../{ => src}/central/CentralSystem.ts | 9 +-- .../{ => src}/client/Connection/Connection.ts | 2 +- .../ConnectionManager/CentralConnection.ts | 0 .../ConnectionManager/ConnectionManager.ts | 2 +- .../EventManagement/EventDispatcher.ts | 5 +- .../wrapper/{ => src}/client/gRPCWrapper.ts | 3 - .../{ => src}/models/connection.model.ts | 0 .../wrapper/{ => src}/models/events.model.ts | 0 .../wrapper/{ => src}/models/message.model.ts | 0 .../wrapper/{ => src}/proto/wrapper.proto | 0 .../test/central/CentralSystem.test.ts | 0 .../ConnectionManager.test.ts | 59 +++++++++++-------- .../test/utils/serialization.utils.test.ts | 0 .../{ => src}/utils/serialization.utils.ts | 0 .../wrapper/{ => src}/utils/types/webui.d.ts | 0 .../backend/wrapper/tsconfig.build.json | 12 ++++ Tokenization/backend/wrapper/tsconfig.json | 22 ++++--- .../backend/wrapper/tsconfig.test.json | 13 ---- 21 files changed, 85 insertions(+), 74 deletions(-) delete mode 100644 Tokenization/backend/wrapper/jest.config.mjs create mode 100644 Tokenization/backend/wrapper/jest.config.ts rename Tokenization/backend/wrapper/{ => src}/central/CentralSystem.ts (91%) rename Tokenization/backend/wrapper/{ => src}/client/Connection/Connection.ts (95%) rename Tokenization/backend/wrapper/{ => src}/client/ConnectionManager/CentralConnection.ts (100%) rename Tokenization/backend/wrapper/{ => src}/client/ConnectionManager/ConnectionManager.ts (97%) rename Tokenization/backend/wrapper/{ => src}/client/ConnectionManager/EventManagement/EventDispatcher.ts (85%) rename Tokenization/backend/wrapper/{ => src}/client/gRPCWrapper.ts (91%) rename Tokenization/backend/wrapper/{ => src}/models/connection.model.ts (100%) rename Tokenization/backend/wrapper/{ => src}/models/events.model.ts (100%) rename Tokenization/backend/wrapper/{ => src}/models/message.model.ts (100%) rename Tokenization/backend/wrapper/{ => src}/proto/wrapper.proto (100%) rename Tokenization/backend/wrapper/{ => src}/test/central/CentralSystem.test.ts (100%) rename Tokenization/backend/wrapper/{ => src}/test/client/ConnectionManager/ConnectionManager.test.ts (66%) rename Tokenization/backend/wrapper/{ => src}/test/utils/serialization.utils.test.ts (100%) rename Tokenization/backend/wrapper/{ => src}/utils/serialization.utils.ts (100%) rename Tokenization/backend/wrapper/{ => src}/utils/types/webui.d.ts (100%) create mode 100644 Tokenization/backend/wrapper/tsconfig.build.json delete mode 100644 Tokenization/backend/wrapper/tsconfig.test.json diff --git a/Tokenization/backend/wrapper/jest.config.mjs b/Tokenization/backend/wrapper/jest.config.mjs deleted file mode 100644 index d74308377..000000000 --- a/Tokenization/backend/wrapper/jest.config.mjs +++ /dev/null @@ -1,11 +0,0 @@ -export default { - preset: "ts-jest/presets/default-esm", - transform: { - "^.+\\.ts$": ["ts-jest", { useESM: true, tsconfig: "tsconfig.test.json" }], - }, - extensionsToTreatAsEsm: [".ts"], - testEnvironment: "node", - moduleNameMapper: { - "^(\\.{1,2}/.*)\\.js$": "$1", - }, -}; diff --git a/Tokenization/backend/wrapper/jest.config.ts b/Tokenization/backend/wrapper/jest.config.ts new file mode 100644 index 000000000..9187fb239 --- /dev/null +++ b/Tokenization/backend/wrapper/jest.config.ts @@ -0,0 +1,13 @@ +import type { Config } from "jest"; + +const config: Config = { + preset: "ts-jest", + testEnvironment: "node", + testMatch: ["**/test/**/*.test.ts"], + moduleFileExtensions: ["ts", "js", "json"], + moduleNameMapper: { + "^@/(.*)$": "/src/$1", + }, +}; + +export default config; diff --git a/Tokenization/backend/wrapper/package.json b/Tokenization/backend/wrapper/package.json index 060701af5..04922d113 100644 --- a/Tokenization/backend/wrapper/package.json +++ b/Tokenization/backend/wrapper/package.json @@ -1,14 +1,12 @@ { "name": "grpc-wrapper", "version": "1.0.0", - "type": "module", + "type": "commonjs", "scripts": { "test": "jest", - "build": "tsc && tsc-alias --resolve-full-paths && cp -r proto dist", + "build": "tsc -p tsconfig.build.json && cp -r src/proto dist", "start-client": "node dist/client/gRPCWrapper.js", - "start-central": "node dist/central/CentralSystem.js", - "dev-client": "ts-node-esm client/gRPCWrapper.ts", - "dev-central": "ts-node-esm central/CentralSystem.ts" + "start-central": "node dist/central/CentralSystem.js" }, "author": "ALICEO2", "devDependencies": { diff --git a/Tokenization/backend/wrapper/central/CentralSystem.ts b/Tokenization/backend/wrapper/src/central/CentralSystem.ts similarity index 91% rename from Tokenization/backend/wrapper/central/CentralSystem.ts rename to Tokenization/backend/wrapper/src/central/CentralSystem.ts index 2a73f08d5..62b61b6ef 100644 --- a/Tokenization/backend/wrapper/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/src/central/CentralSystem.ts @@ -2,11 +2,6 @@ import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; import path from "path"; import { LogManager } from "@aliceo2/web-ui"; -import { fileURLToPath } from "url"; -import { - DuplexMessageEvent, - DuplexMessageModel, -} from "../models/message.model.ts"; /** * @description Central System gRPC wrapper that manages client connections and handles gRPC streams with them. @@ -61,7 +56,7 @@ export class CentralSystemWrapper { // Listen for data events from the client call.on("data", (payload: any) => { - console.log(`Received from ${clientAddress}:`, payload); + console.log(`Received from ${call.getPeer()}:`, payload); }); // Handle stream end event @@ -99,8 +94,6 @@ export class CentralSystemWrapper { } // Instantiate the CentralSystemWrapper on port 50051, but don't start automatically -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); const centralSystem = new CentralSystemWrapper(PROTO_PATH, 50051); // Start listening explicitly diff --git a/Tokenization/backend/wrapper/client/Connection/Connection.ts b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts similarity index 95% rename from Tokenization/backend/wrapper/client/Connection/Connection.ts rename to Tokenization/backend/wrapper/src/client/Connection/Connection.ts index 80bf12f8c..c1eebcaec 100644 --- a/Tokenization/backend/wrapper/client/Connection/Connection.ts +++ b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts @@ -1,4 +1,4 @@ -import { ConnectionStatus } from "../../models/connection.model.ts"; +import { ConnectionStatus } from "../../models/connection.model"; /** * @license diff --git a/Tokenization/backend/wrapper/client/ConnectionManager/CentralConnection.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts similarity index 100% rename from Tokenization/backend/wrapper/client/ConnectionManager/CentralConnection.ts rename to Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts diff --git a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts similarity index 97% rename from Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts rename to Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts index 7d5bb415c..3089a549c 100644 --- a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts @@ -14,7 +14,7 @@ import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; import { CentralConnection } from "./CentralConnection"; -import { EventDispatcher } from "../ConnectionManager/EventManagement/EventDispatcher"; +import { EventDispatcher } from "./EventManagement/EventDispatcher"; import { Connection } from "../Connection/Connection"; import { LogManager } from "@aliceo2/web-ui"; diff --git a/Tokenization/backend/wrapper/client/ConnectionManager/EventManagement/EventDispatcher.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/EventDispatcher.ts similarity index 85% rename from Tokenization/backend/wrapper/client/ConnectionManager/EventManagement/EventDispatcher.ts rename to Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/EventDispatcher.ts index 1f9d17be0..1b17efb7e 100644 --- a/Tokenization/backend/wrapper/client/ConnectionManager/EventManagement/EventDispatcher.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/EventDispatcher.ts @@ -27,9 +27,12 @@ export class EventDispatcher implements MessageHandler { */ handle(event: any): void { switch (event.event) { - case "EMPTY_EVENT": + case DuplexMessageEvent.MESSAGE_EVENT_EMPTY: // handle empty event break; + case DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN: + this.logger.infoMessage("Token revoked:", event.revokeToken); + // handle token revocation default: this.logger.infoMessage("Unknown event type:", event.event); } diff --git a/Tokenization/backend/wrapper/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts similarity index 91% rename from Tokenization/backend/wrapper/client/gRPCWrapper.ts rename to Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index 66cfb6c48..bf514c088 100644 --- a/Tokenization/backend/wrapper/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -1,6 +1,5 @@ import path from "path"; import { ConnectionManager } from "./ConnectionManager/ConnectionManager"; -import { fileURLToPath } from "url"; /** * @description Wrapper class for managing secure gRPC wrapper. @@ -37,8 +36,6 @@ export class gRPCWrapper { } } -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); const grpc = new gRPCWrapper(PROTO_PATH, "localhost:50051"); grpc.connectToCentralSystem(); diff --git a/Tokenization/backend/wrapper/models/connection.model.ts b/Tokenization/backend/wrapper/src/models/connection.model.ts similarity index 100% rename from Tokenization/backend/wrapper/models/connection.model.ts rename to Tokenization/backend/wrapper/src/models/connection.model.ts diff --git a/Tokenization/backend/wrapper/models/events.model.ts b/Tokenization/backend/wrapper/src/models/events.model.ts similarity index 100% rename from Tokenization/backend/wrapper/models/events.model.ts rename to Tokenization/backend/wrapper/src/models/events.model.ts diff --git a/Tokenization/backend/wrapper/models/message.model.ts b/Tokenization/backend/wrapper/src/models/message.model.ts similarity index 100% rename from Tokenization/backend/wrapper/models/message.model.ts rename to Tokenization/backend/wrapper/src/models/message.model.ts diff --git a/Tokenization/backend/wrapper/proto/wrapper.proto b/Tokenization/backend/wrapper/src/proto/wrapper.proto similarity index 100% rename from Tokenization/backend/wrapper/proto/wrapper.proto rename to Tokenization/backend/wrapper/src/proto/wrapper.proto diff --git a/Tokenization/backend/wrapper/test/central/CentralSystem.test.ts b/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts similarity index 100% rename from Tokenization/backend/wrapper/test/central/CentralSystem.test.ts rename to Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts diff --git a/Tokenization/backend/wrapper/test/client/ConnectionManager/ConnectionManager.test.ts b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts similarity index 66% rename from Tokenization/backend/wrapper/test/client/ConnectionManager/ConnectionManager.test.ts rename to Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts index de0a626a6..ea1d2498f 100644 --- a/Tokenization/backend/wrapper/test/client/ConnectionManager/ConnectionManager.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts @@ -1,16 +1,31 @@ import * as grpc from "@grpc/grpc-js"; import { ConnectionManager } from "../../../client/ConnectionManager/ConnectionManager"; -// Mock of client and stream +// Mock duplex stream const mockStream = { on: jest.fn(), end: jest.fn(), }; +// Mock gRPC client const mockClient = { ClientStream: jest.fn(() => mockStream), }; +// Mock CentralSystem constructor +const CentralSystemMock = jest.fn(() => mockClient); + +// Mock EventDispatcher +jest.mock( + "../../../client/ConnectionManager/EventManagement/EventDispatcher", + () => ({ + EventDispatcher: jest.fn(() => ({ + handle: jest.fn(), + })), + }) +); + +// Mock logger jest.mock("@aliceo2/web-ui", () => ({ LogManager: { getLogger: () => ({ @@ -19,6 +34,7 @@ jest.mock("@aliceo2/web-ui", () => ({ }, })); +// Mock gRPC proto loader and client jest.mock("@grpc/proto-loader", () => ({ loadSync: jest.fn(() => { return {}; @@ -35,7 +51,7 @@ jest.mock("@grpc/grpc-js", () => { loadPackageDefinition: jest.fn(() => ({ webui: { tokenization: { - CentralSystem: jest.fn(() => mockClient), + CentralSystem: CentralSystemMock, }, }, })), @@ -53,9 +69,13 @@ describe("ConnectionManager", () => { test("should initialize client with correct address", () => { expect(conn).toBeDefined(); expect(grpc.loadPackageDefinition).toHaveBeenCalled(); + expect(CentralSystemMock).toHaveBeenCalledWith( + "localhost:12345", + undefined + ); }); - test("connectToCentralSystem() should create stream and log message", () => { + test("connectToCentralSystem() should set up stream listeners", () => { conn.connectToCentralSystem(); expect(mockClient.ClientStream).toHaveBeenCalled(); @@ -64,45 +84,38 @@ describe("ConnectionManager", () => { expect(mockStream.on).toHaveBeenCalledWith("error", expect.any(Function)); }); - test("disconnect() should end stream and reset reconnectAttempts", () => { + test("disconnectFromCentralSystem() should end stream", () => { conn.connectToCentralSystem(); conn.disconnectFromCentralSystem(); expect(mockStream.end).toHaveBeenCalled(); }); - test("scheduleReconnect() should call connect after delay", () => { - jest.useFakeTimers(); - const spy = jest.spyOn(conn as any, "connect"); - - (conn as any).scheduleReconnect(); - - jest.advanceTimersByTime(1000 * 2); // pierwszy delay = 2^1 * 1000 - expect(spy).toHaveBeenCalled(); - jest.useRealTimers(); - }); - test("should reconnect on stream 'end'", () => { + jest.useFakeTimers(); conn.connectToCentralSystem(); const onEnd = mockStream.on.mock.calls.find( ([event]) => event === "end" - )[1]; + )?.[1]; - const reconnectSpy = jest.spyOn(conn as any, "scheduleReconnect"); - onEnd(); + onEnd?.(); // simulate 'end' + jest.advanceTimersByTime(2000); - expect(reconnectSpy).toHaveBeenCalled(); + expect(mockClient.ClientStream).toHaveBeenCalledTimes(2); // initial + reconnect + jest.useRealTimers(); }); test("should reconnect on stream 'error'", () => { + jest.useFakeTimers(); conn.connectToCentralSystem(); const onError = mockStream.on.mock.calls.find( ([event]) => event === "error" - )[1]; + )?.[1]; - const reconnectSpy = jest.spyOn(conn as any, "scheduleReconnect"); - onError(new Error("Stream failed")); + onError?.(new Error("Simulated error")); + jest.advanceTimersByTime(2000); - expect(reconnectSpy).toHaveBeenCalled(); + expect(mockClient.ClientStream).toHaveBeenCalledTimes(2); + jest.useRealTimers(); }); }); diff --git a/Tokenization/backend/wrapper/test/utils/serialization.utils.test.ts b/Tokenization/backend/wrapper/src/test/utils/serialization.utils.test.ts similarity index 100% rename from Tokenization/backend/wrapper/test/utils/serialization.utils.test.ts rename to Tokenization/backend/wrapper/src/test/utils/serialization.utils.test.ts diff --git a/Tokenization/backend/wrapper/utils/serialization.utils.ts b/Tokenization/backend/wrapper/src/utils/serialization.utils.ts similarity index 100% rename from Tokenization/backend/wrapper/utils/serialization.utils.ts rename to Tokenization/backend/wrapper/src/utils/serialization.utils.ts diff --git a/Tokenization/backend/wrapper/utils/types/webui.d.ts b/Tokenization/backend/wrapper/src/utils/types/webui.d.ts similarity index 100% rename from Tokenization/backend/wrapper/utils/types/webui.d.ts rename to Tokenization/backend/wrapper/src/utils/types/webui.d.ts diff --git a/Tokenization/backend/wrapper/tsconfig.build.json b/Tokenization/backend/wrapper/tsconfig.build.json new file mode 100644 index 000000000..681ec5d54 --- /dev/null +++ b/Tokenization/backend/wrapper/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "outDir": "dist", + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*.ts"], + "exclude": ["tests", "node_modules"] +} diff --git a/Tokenization/backend/wrapper/tsconfig.json b/Tokenization/backend/wrapper/tsconfig.json index 17a9fe200..eda421baa 100644 --- a/Tokenization/backend/wrapper/tsconfig.json +++ b/Tokenization/backend/wrapper/tsconfig.json @@ -1,14 +1,20 @@ { "compilerOptions": { - "target": "ES2020", - "module": "ESNext", - "moduleResolution": "node", - "rootDir": "./", - "outDir": "./dist", + "target": "es2020", + "module": "CommonJS", + "moduleResolution": "Node", "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, "strict": true, - "skipLibCheck": true + "skipLibCheck": true, + "resolveJsonModule": true, + "baseUrl": ".", + "paths": { + "*": ["src/*"] + }, + "noEmit": true, + "allowImportingTsExtensions": false, + "forceConsistentCasingInFileNames": true }, - "include": ["client", "central", "utils", "types"] + "include": ["src/**/*.ts", "tests/**/*.ts"], + "exclude": ["dist", "node_modules"] } diff --git a/Tokenization/backend/wrapper/tsconfig.test.json b/Tokenization/backend/wrapper/tsconfig.test.json deleted file mode 100644 index 32ee3a99a..000000000 --- a/Tokenization/backend/wrapper/tsconfig.test.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "ESNext", - "target": "ES2022", - "moduleResolution": "node", - "verbatimModuleSyntax": true, - "allowImportingTsExtensions": false, - "esModuleInterop": true, - "types": ["jest"] - }, - "include": ["test", "client", "central", "utils", "types"] -} From e79215c506d0095b4aa3dde08aa4b404553489b7 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Mon, 4 Aug 2025 22:12:34 +0200 Subject: [PATCH 036/103] feat: refactor code for command design pattern --- .../wrapper/src/central/CentralSystem.ts | 13 +++++ .../client/Commands/revokeToken.command.ts | 19 ++++++++ .../client/Commands/revokeToken.handler.ts | 30 ++++++++++++ .../src/client/Connection/Connection.ts | 4 +- .../ConnectionManager/CentralConnection.ts | 9 ++-- .../ConnectionManager/ConnectionManager.ts | 39 +++++++++++++-- .../CentralCommandDispatcher.ts | 48 +++++++++++++++++++ .../EventManagement/EventDispatcher.ts | 40 ---------------- .../backend/wrapper/src/client/gRPCWrapper.ts | 21 ++++++++ .../{events.model.ts => commands.model.ts} | 12 ++++- .../wrapper/src/models/message.model.ts | 14 +++--- .../ConnectionManager.test.ts | 33 +++++++++++-- 12 files changed, 219 insertions(+), 63 deletions(-) create mode 100644 Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts create mode 100644 Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts create mode 100644 Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts delete mode 100644 Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/EventDispatcher.ts rename Tokenization/backend/wrapper/src/models/{events.model.ts => commands.model.ts} (76%) diff --git a/Tokenization/backend/wrapper/src/central/CentralSystem.ts b/Tokenization/backend/wrapper/src/central/CentralSystem.ts index 62b61b6ef..035837940 100644 --- a/Tokenization/backend/wrapper/src/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/src/central/CentralSystem.ts @@ -1,3 +1,16 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; import path from "path"; diff --git a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts b/Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts new file mode 100644 index 000000000..dcdec0e34 --- /dev/null +++ b/Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts @@ -0,0 +1,19 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ +import { Command } from "models/commands.model"; + +export class RevokeTokenCommand implements Command { + readonly type = DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN; + constructor(public payload: DuplexMessageModel) {} +} diff --git a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts b/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts new file mode 100644 index 000000000..8278c382d --- /dev/null +++ b/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ +import { CommandHandler } from "models/commands.model"; +import { RevokeTokenCommand } from "./revokeToken.command"; +import { ConnectionManager } from "client/ConnectionManager/ConnectionManager"; + +export class RevokeTokenHandler implements CommandHandler { + constructor(private manager: ConnectionManager) {} + + async handle(command: RevokeTokenCommand): Promise { + const { targetAddress } = command.payload.data.revokeToken || {}; + if (!targetAddress) { + throw new Error("Target address is required to revoke token."); + } + + const conn = this.manager.getConnectionByAddress(targetAddress); + conn?.handleRevokeToken(); + } +} diff --git a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts index c1eebcaec..2cd5b572f 100644 --- a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts +++ b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts @@ -1,5 +1,3 @@ -import { ConnectionStatus } from "../../models/connection.model"; - /** * @license * Copyright 2019-2020 CERN and copyright holders of ALICE O2. @@ -13,6 +11,8 @@ import { ConnectionStatus } from "../../models/connection.model"; * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ +import { ConnectionStatus } from "../../models/connection.model"; + /** * @description This class represents a connection to a target client and manages sending messages to it. */ diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts index fee5a82b5..70ed788d3 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts @@ -13,7 +13,7 @@ */ import * as grpc from "@grpc/grpc-js"; import { LogManager } from "@aliceo2/web-ui"; -import type { MessageHandler } from "../../models/events.model"; +import { CentralCommandDispatcher } from "./EventManagement/CentralCommandDispatcher"; /** * @description This class manages the duplex stream with the CentralSystem gRPC service. @@ -23,7 +23,10 @@ export class CentralConnection { private logger = LogManager.getLogger("CentralConnection"); private stream?: grpc.ClientDuplexStream; - constructor(private client: any, private handler: MessageHandler) {} + constructor( + private client: any, + private dispatcher: CentralCommandDispatcher + ) {} /** * @description Initializes the duplex stream and sets up event handlers. @@ -34,7 +37,7 @@ export class CentralConnection { this.stream = this.client.ClientStream(); this.stream!.on("data", (payload) => { - this.handler.handle(payload); + this.dispatcher.dispatch(payload); }); this.stream!.on("end", () => { diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts index 3089a549c..57d751d8c 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts @@ -14,9 +14,11 @@ import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; import { CentralConnection } from "./CentralConnection"; -import { EventDispatcher } from "./EventManagement/EventDispatcher"; +import { CentralCommandDispatcher } from "./EventManagement/CentralCommandDispatcher"; import { Connection } from "../Connection/Connection"; import { LogManager } from "@aliceo2/web-ui"; +import { Command, CommandHandler } from "models/commands.model"; +import { DuplexMessageEvent } from "models/message.model"; /** * @description Manages all the connection between clients and central system. @@ -36,6 +38,7 @@ import { LogManager } from "@aliceo2/web-ui"; */ export class ConnectionManager { private logger = LogManager.getLogger("ConnectionManager"); + private centralDispatcher: CentralCommandDispatcher; private centralConnection: CentralConnection; private sendingConnections = new Map(); private receivingConnections = new Map(); @@ -65,24 +68,50 @@ export class ConnectionManager { grpc.credentials.createInsecure() ); - const dispatcher = new EventDispatcher(); - this.centralConnection = new CentralConnection(client, dispatcher); + this.centralDispatcher = new CentralCommandDispatcher(); + + this.centralConnection = new CentralConnection( + client, + this.centralDispatcher + ); this.sendingConnections.set("a", new Connection("1", "a")); this.sendingConnections.set("b", new Connection("2", "b")); } + registerCommandHandlers( + commandHandlers: { + event: DuplexMessageEvent; + handler: CommandHandler; + }[] + ): void { + commandHandlers.forEach(({ event, handler }) => { + this.centralDispatcher.register(event, handler); + }); + } + /** * @description Starts the connection to the central system. */ - connectToCentralSystem() { + connectToCentralSystem(): void { this.centralConnection.start(); } /** * @description Disconnects from the central system. */ - disconnectFromCentralSystem() { + disconnectFromCentralSystem(): void { this.centralConnection.disconnect(); } + + /** + * @description Gets the connection instance by address. + * @returns{Connection} connection instance. + */ + getConnectionByAddress(address: string): Connection | undefined { + return ( + this.sendingConnections.get(address) || + this.receivingConnections.get(address) + ); + } } diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts new file mode 100644 index 000000000..f6eb7ccf6 --- /dev/null +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts @@ -0,0 +1,48 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { LogManager } from "@aliceo2/web-ui"; +import { Command, CommandHandler } from "models/commands.model"; +import { DuplexMessageEvent } from "models/message.model"; + +export class CentralCommandDispatcher { + private handlers = new Map(); + private logger = LogManager.getLogger("CentralCommandDispatcher"); + + register( + type: DuplexMessageEvent, + handler: CommandHandler + ): void { + this.handlers.set(type, handler); + } + + async dispatch(command: Command): Promise { + const handler = this.handlers.get(command.type); + if (!handler) { + this.logger.warnMessage( + `No handler registered for command type: ${command.type}` + ); + return; + } + + try { + await handler.handle(command); + } catch (error) { + this.logger.errorMessage( + `Error handling command ${command.type}:`, + error + ); + } + } +} diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/EventDispatcher.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/EventDispatcher.ts deleted file mode 100644 index 1b17efb7e..000000000 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/EventDispatcher.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ -import type { MessageHandler } from "../../../models/events.model"; -import { LogManager } from "@aliceo2/web-ui"; - -/** - * @description Dispatches gRPC stream events received from CentralSystem. - */ -export class EventDispatcher implements MessageHandler { - private logger = LogManager.getLogger("ConnectionManager"); - - /** - * @description Handles incoming events from the gRPC stream. - * - * @param event - The event object received from the stream. - */ - handle(event: any): void { - switch (event.event) { - case DuplexMessageEvent.MESSAGE_EVENT_EMPTY: - // handle empty event - break; - case DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN: - this.logger.infoMessage("Token revoked:", event.revokeToken); - // handle token revocation - default: - this.logger.infoMessage("Unknown event type:", event.event); - } - } -} diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index bf514c088..0452daa22 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -1,5 +1,20 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ import path from "path"; import { ConnectionManager } from "./ConnectionManager/ConnectionManager"; +import { RevokeTokenHandler } from "./Commands/revokeToken.handler"; +import { DuplexMessageEvent } from "models/message.model"; /** * @description Wrapper class for managing secure gRPC wrapper. @@ -26,6 +41,12 @@ export class gRPCWrapper { */ constructor(protoPath: string, centralAddress: string = "localhost:50051") { this.ConnectionManager = new ConnectionManager(protoPath, centralAddress); + this.ConnectionManager.registerCommandHandlers([ + { + event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, + handler: new RevokeTokenHandler(this.ConnectionManager), + }, + ]); } /** diff --git a/Tokenization/backend/wrapper/src/models/events.model.ts b/Tokenization/backend/wrapper/src/models/commands.model.ts similarity index 76% rename from Tokenization/backend/wrapper/src/models/events.model.ts rename to Tokenization/backend/wrapper/src/models/commands.model.ts index ff20b71ff..0a595b005 100644 --- a/Tokenization/backend/wrapper/src/models/events.model.ts +++ b/Tokenization/backend/wrapper/src/models/commands.model.ts @@ -12,12 +12,20 @@ * or submit itself to any jurisdiction. */ +import { DuplexMessageEvent } from "./message.model"; + /** * Interface representing a handler for processing events. * * @remarks * The `handle` method receives an event object and performs the necessary processing. */ -export interface MessageHandler { - handle(event: any): void; + +export interface Command { + type: DuplexMessageEvent; + payload: any; +} + +export interface CommandHandler { + handle(command: T): Promise; } diff --git a/Tokenization/backend/wrapper/src/models/message.model.ts b/Tokenization/backend/wrapper/src/models/message.model.ts index 3dc754218..0bfab7d8f 100644 --- a/Tokenization/backend/wrapper/src/models/message.model.ts +++ b/Tokenization/backend/wrapper/src/models/message.model.ts @@ -22,7 +22,7 @@ * @property MESSAGE_EVENT_NEW_TOKEN: Event for replacing with newly generated token. * @property MESSAGE_EVENT_REVOKE_TOKEN: Event for revoking an existing token. */ -enum DuplexMessageEvent { +export enum DuplexMessageEvent { MESSAGE_EVENT_EMPTY = 0, MESSAGE_EVENT_NEW_TOKEN = 1, MESSAGE_EVENT_REVOKE_TOKEN = 2, @@ -37,8 +37,8 @@ enum DuplexMessageEvent { * @property {string} token - The token to be replaced or revoked. * @property {string} targetAddress - The address of connection binded to this token. */ -interface TokenMessage { - token: string; +export interface TokenMessage { + token?: string; targetAddress: string; } @@ -48,8 +48,10 @@ interface TokenMessage { * @property {TokenMessage} data - The data associated with the event, it may be undefined for some events. * @example {event: DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN, data: {token: '', targetAddress: ''}} */ -interface DuplexMessageModel { +export interface DuplexMessageModel { event: DuplexMessageEvent; - newToken?: TokenMessage; - revokeToken?: TokenMessage; + data: { + newToken?: TokenMessage; + revokeToken?: TokenMessage; + }; } diff --git a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts index ea1d2498f..a2b34cde4 100644 --- a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts @@ -1,5 +1,6 @@ import * as grpc from "@grpc/grpc-js"; import { ConnectionManager } from "../../../client/ConnectionManager/ConnectionManager"; +import { DuplexMessageEvent } from "../../../models/message.model"; // Mock duplex stream const mockStream = { @@ -15,12 +16,13 @@ const mockClient = { // Mock CentralSystem constructor const CentralSystemMock = jest.fn(() => mockClient); -// Mock EventDispatcher +// Mock dispatcher +const mockDispatch = jest.fn(); jest.mock( - "../../../client/ConnectionManager/EventManagement/EventDispatcher", + "../../../client/ConnectionManager/EventManagement/CentralCommandDispatcher", () => ({ - EventDispatcher: jest.fn(() => ({ - handle: jest.fn(), + CentralCommandDispatcher: jest.fn(() => ({ + dispatch: mockDispatch, })), }) ); @@ -101,7 +103,7 @@ describe("ConnectionManager", () => { onEnd?.(); // simulate 'end' jest.advanceTimersByTime(2000); - expect(mockClient.ClientStream).toHaveBeenCalledTimes(2); // initial + reconnect + expect(mockClient.ClientStream).toHaveBeenCalledTimes(2); jest.useRealTimers(); }); @@ -118,4 +120,25 @@ describe("ConnectionManager", () => { expect(mockClient.ClientStream).toHaveBeenCalledTimes(2); jest.useRealTimers(); }); + + test("should dispatch event when 'data' is received", () => { + conn.connectToCentralSystem(); + const onData = mockStream.on.mock.calls.find( + ([event]) => event === "data" + )?.[1]; + + const mockMessage = { + event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, + data: { + revokeToken: { + token: "abc123", + targetAddress: "peer-123", + }, + }, + }; + + onData?.(mockMessage); + + expect(mockDispatch).toHaveBeenCalledWith(mockMessage); + }); }); From 90d713d4d31af35a581f6ab038a7c5a20dea0eb9 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Mon, 4 Aug 2025 22:54:48 +0200 Subject: [PATCH 037/103] feat: unit tests for token revokation --- .../client/Commands/revokeToken.command.ts | 6 +- .../client/Commands/revokeToken.handler.ts | 6 +- .../wrapper/src/models/message.model.ts | 6 +- .../test/client/Commands/revokeToken.test.ts | 106 ++++++++++++++++++ .../{ConnectionManager.test.ts => index.ts} | 0 5 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts rename Tokenization/backend/wrapper/src/test/client/ConnectionManager/{ConnectionManager.test.ts => index.ts} (100%) diff --git a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts b/Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts index dcdec0e34..88bea615b 100644 --- a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts +++ b/Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts @@ -11,7 +11,11 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ -import { Command } from "models/commands.model"; +import { Command } from "../../models/commands.model"; +import { + DuplexMessageEvent, + DuplexMessageModel, +} from "../../models/message.model"; export class RevokeTokenCommand implements Command { readonly type = DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN; diff --git a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts b/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts index 8278c382d..299c71c85 100644 --- a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts +++ b/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts @@ -11,15 +11,15 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ -import { CommandHandler } from "models/commands.model"; +import { CommandHandler } from "../../models/commands.model"; import { RevokeTokenCommand } from "./revokeToken.command"; -import { ConnectionManager } from "client/ConnectionManager/ConnectionManager"; +import { ConnectionManager } from "../ConnectionManager/ConnectionManager"; export class RevokeTokenHandler implements CommandHandler { constructor(private manager: ConnectionManager) {} async handle(command: RevokeTokenCommand): Promise { - const { targetAddress } = command.payload.data.revokeToken || {}; + const { targetAddress } = command.payload.revokeToken || {}; if (!targetAddress) { throw new Error("Target address is required to revoke token."); } diff --git a/Tokenization/backend/wrapper/src/models/message.model.ts b/Tokenization/backend/wrapper/src/models/message.model.ts index 0bfab7d8f..07abc0b18 100644 --- a/Tokenization/backend/wrapper/src/models/message.model.ts +++ b/Tokenization/backend/wrapper/src/models/message.model.ts @@ -50,8 +50,6 @@ export interface TokenMessage { */ export interface DuplexMessageModel { event: DuplexMessageEvent; - data: { - newToken?: TokenMessage; - revokeToken?: TokenMessage; - }; + newToken?: TokenMessage; + revokeToken?: TokenMessage; } diff --git a/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts b/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts new file mode 100644 index 000000000..972565210 --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts @@ -0,0 +1,106 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { RevokeTokenCommand } from "../../../client/Commands/revokeToken.command"; +import { RevokeTokenHandler } from "../../../client/Commands/revokeToken.handler"; +import { Connection } from "../../../client/Connection/Connection"; +import { ConnectionManager } from "../../../client/ConnectionManager/ConnectionManager"; +import { DuplexMessageEvent } from "../../../models/message.model"; +import { ConnectionStatus } from "../../../models/connection.model"; + +describe("RevokeToken", () => { + function createEventMessage(targetAddress: string) { + return { + event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, + revokeToken: { + token: "test-token", + targetAddress, + }, + }; + } + + let manager: ConnectionManager; + + beforeEach(() => { + manager = { + sendingConnections: new Map(), + receivingConnections: new Map(), + getConnectionByAddress: jest.fn(function (this: any, address: string) { + return ( + this.sendingConnections.get(address) || + this.receivingConnections.get(address) + ); + }), + } as unknown as ConnectionManager; + }); + + it("should revoke token when connection found in sendingConnections", async () => { + const targetAddress = "peer-123"; + const conn = new Connection("valid-token", targetAddress); + (manager as any).sendingConnections!.set(targetAddress, conn); + + const handler = new RevokeTokenHandler(manager); + const command = new RevokeTokenCommand(createEventMessage(targetAddress)); + + await handler.handle(command); + + expect(conn.getToken()).toBe(""); + expect(conn.getStatus()).toBe(ConnectionStatus.UNAUTHORIZED); + }); + + it("should revoke token when connection found in receivingConnections", async () => { + const targetAddress = "peer-456"; + const conn = new Connection("valid-token", targetAddress); + (manager as any).receivingConnections.set(targetAddress, conn); + + const handler = new RevokeTokenHandler(manager); + const command = new RevokeTokenCommand(createEventMessage(targetAddress)); + + await handler.handle(command); + + expect(conn.getToken()).toBe(""); + expect(conn.getStatus()).toBe(ConnectionStatus.UNAUTHORIZED); + }); + + it("should do nothing when connection not found", async () => { + const targetAddress = "non-existent"; + const handler = new RevokeTokenHandler(manager); + const command = new RevokeTokenCommand(createEventMessage(targetAddress)); + + await expect(handler.handle(command)).resolves.toBeUndefined(); + expect(manager.getConnectionByAddress).toHaveBeenCalledWith(targetAddress); + }); + + it("should throw error when targetAddress is missing", async () => { + const invalidMessage = { + event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, + revokeToken: { token: "test-token" }, + }; + + const handler = new RevokeTokenHandler(manager); + const command = new RevokeTokenCommand(invalidMessage as any); + + await expect(handler.handle(command)).rejects.toThrow( + "Target address is required to revoke token." + ); + }); + + it("should create command with correct type and payload", () => { + const eventMessage = createEventMessage("peer-001"); + const command = new RevokeTokenCommand(eventMessage); + + expect(command.type).toBe(DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN); + expect(command.payload).toEqual(eventMessage); + }); +}); diff --git a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts similarity index 100% rename from Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts rename to Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts From e0794386344df923b699554fa8f39a534e12e23f Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 5 Aug 2025 21:50:14 +0200 Subject: [PATCH 038/103] feat: implement token revokation logic and testing --- Tokenization/backend/wrapper/package.json | 4 +- .../wrapper/src/central/CentralSystem.ts | 105 ++++++++++++++++-- .../client/Commands/revokeToken.command.ts | 9 +- .../client/Commands/revokeToken.handler.ts | 2 +- .../src/client/Connection/Connection.ts | 8 ++ .../ConnectionManager/CentralConnection.ts | 4 +- .../ConnectionManager/ConnectionManager.ts | 12 +- .../CentralCommandDispatcher.ts | 14 ++- .../backend/wrapper/src/client/gRPCWrapper.ts | 38 ++++++- .../wrapper/src/models/commands.model.ts | 2 +- .../wrapper/src/models/message.model.ts | 11 +- .../backend/wrapper/src/proto/wrapper.proto | 3 +- .../test/client/Commands/revokeToken.test.ts | 21 ++-- 13 files changed, 189 insertions(+), 44 deletions(-) diff --git a/Tokenization/backend/wrapper/package.json b/Tokenization/backend/wrapper/package.json index 04922d113..7f472886a 100644 --- a/Tokenization/backend/wrapper/package.json +++ b/Tokenization/backend/wrapper/package.json @@ -5,8 +5,8 @@ "scripts": { "test": "jest", "build": "tsc -p tsconfig.build.json && cp -r src/proto dist", - "start-client": "node dist/client/gRPCWrapper.js", - "start-central": "node dist/central/CentralSystem.js" + "client": "node dist/client/gRPCWrapper.js", + "central": "node dist/central/CentralSystem.js" }, "author": "ALICEO2", "devDependencies": { diff --git a/Tokenization/backend/wrapper/src/central/CentralSystem.ts b/Tokenization/backend/wrapper/src/central/CentralSystem.ts index 035837940..0d122c390 100644 --- a/Tokenization/backend/wrapper/src/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/src/central/CentralSystem.ts @@ -15,6 +15,10 @@ import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; import path from "path"; import { LogManager } from "@aliceo2/web-ui"; +import { + DuplexMessageEvent, + DuplexMessageModel, +} from "../models/message.model"; /** * @description Central System gRPC wrapper that manages client connections and handles gRPC streams with them. @@ -26,6 +30,10 @@ export class CentralSystemWrapper { // class properties private server: grpc.Server; + // clients management + private clients = new Map>(); + private clientIps = new Map(); // Peer -> IP map + /** * Initializes the Wrapper for CentralSystem. * @param port The port number to bind the gRPC server to. @@ -58,33 +66,103 @@ export class CentralSystemWrapper { }); } + /** + * @description Extracts IP address from peer string + * @param peer string e.g. ipv4:127.0.0.1:12345 + * @returns Extracted IP address + */ + private extractIpFromPeer(peer: string): string { + // Context + // IPv4 format: "ipv4:127.0.0.1:12345" + // IPv6 format: "ipv6:[::1]:12345" + + const ipv4Match = peer.match(/^ipv4:(.+?):\d+$/); + if (ipv4Match) return ipv4Match[1]; + + const ipv6Match = peer.match(/^ipv6:\[(.+?)\]:\d+$/); + if (ipv6Match) return ipv6Match[1]; + + // fallback to original peer if pattern doesn't match any + return peer; + } + /** * @description Handles the duplex stream from the client. * @param call The duplex stream call object. */ private clientStreamHandler(call: grpc.ServerDuplexStream): void { + const peer = call.getPeer(); + const clientIp = this.extractIpFromPeer(peer); + this.logger.infoMessage( - `Client ${call.getPeer()} connected to CentralSystem stream stream` + `Client ${clientIp} (${peer}) connected to CentralSystem stream` ); + // Add client to maps + this.clients.set(clientIp, call); + this.clientIps.set(peer, clientIp); + // Listen for data events from the client call.on("data", (payload: any) => { - console.log(`Received from ${call.getPeer()}:`, payload); + this.logger.infoMessage(`Received from ${clientIp}:`, payload); }); // Handle stream end event call.on("end", () => { - this.logger.infoMessage(`Client ${call.getPeer()} ended stream.`); + this.logger.infoMessage(`Client ${clientIp} ended stream.`); + this.cleanupClient(peer); call.end(); }); // Handle stream error event - call.on("error", (err) => - this.logger.infoMessage( - `Stream error from client ${call.getPeer()}:`, - err - ) - ); + call.on("error", (err) => { + this.logger.infoMessage(`Stream error from client ${clientIp}:`, err); + this.cleanupClient(peer); + }); + } + + /** + * @description Cleans up client resources + * @param peer Original peer string + */ + private cleanupClient(peer: string): void { + const clientIp = this.clientIps.get(peer); + if (clientIp) { + this.clients.delete(clientIp); + this.clientIps.delete(peer); + this.logger.infoMessage(`Cleaned up resources of ${clientIp}`); + } + } + + /** + * @description Sends data to a specific client by IP address + * @param ip Client IP address + * @param data Data to send + * @returns Whether the data was successfully sent + */ + public sendEvent(ip: string, data: DuplexMessageModel): boolean { + const client = this.clients.get(ip); + if (!client) { + this.logger.warnMessage(`Client ${ip} not found for sending event`); + return false; + } + + try { + client.write(data); + this.logger.infoMessage(`Sent event to ${ip}:`, data); + return true; + } catch (err) { + this.logger.errorMessage(`Error sending to ${ip}:`, err); + return false; + } + } + + /** + * @description Gets all connected client IPs + * @returns Array of connected client IPs + */ + public getConnectedClients(): string[] { + return Array.from(this.clients.keys()); } /** @@ -111,3 +189,12 @@ const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); const centralSystem = new CentralSystemWrapper(PROTO_PATH, 50051); // Start listening explicitly centralSystem.listen(); + +setTimeout(() => { + centralSystem.sendEvent(centralSystem.getConnectedClients()[0], { + event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, + payload: { + targetAddress: "a", + }, + }); +}, 5000); diff --git a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts b/Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts index 88bea615b..baeff3792 100644 --- a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts +++ b/Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts @@ -12,12 +12,9 @@ * or submit itself to any jurisdiction. */ import { Command } from "../../models/commands.model"; -import { - DuplexMessageEvent, - DuplexMessageModel, -} from "../../models/message.model"; +import { DuplexMessageEvent, TokenMessage } from "../../models/message.model"; export class RevokeTokenCommand implements Command { - readonly type = DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN; - constructor(public payload: DuplexMessageModel) {} + readonly event = DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN; + constructor(public payload: TokenMessage) {} } diff --git a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts b/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts index 299c71c85..08a8b9f89 100644 --- a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts +++ b/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts @@ -19,7 +19,7 @@ export class RevokeTokenHandler implements CommandHandler { constructor(private manager: ConnectionManager) {} async handle(command: RevokeTokenCommand): Promise { - const { targetAddress } = command.payload.revokeToken || {}; + const { targetAddress } = command.payload || {}; if (!targetAddress) { throw new Error("Target address is required to revoke token."); } diff --git a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts index 2cd5b572f..79ebfdcec 100644 --- a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts +++ b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts @@ -56,4 +56,12 @@ export class Connection { public getStatus(): string { return this.status; } + + /** + * @description Returns target address for this Connection object + * @returns Target address + */ + public getTargetAddress(): string { + return this.targetAddress; + } } diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts index 70ed788d3..30cc02b96 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts @@ -14,6 +14,7 @@ import * as grpc from "@grpc/grpc-js"; import { LogManager } from "@aliceo2/web-ui"; import { CentralCommandDispatcher } from "./EventManagement/CentralCommandDispatcher"; +import { DuplexMessageModel } from "../../models/message.model"; /** * @description This class manages the duplex stream with the CentralSystem gRPC service. @@ -36,7 +37,8 @@ export class CentralConnection { this.stream = this.client.ClientStream(); - this.stream!.on("data", (payload) => { + this.stream!.on("data", (payload: DuplexMessageModel) => { + console.log("Received payload:", JSON.stringify(payload)); this.dispatcher.dispatch(payload); }); diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts index 57d751d8c..06712e2c2 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts @@ -18,7 +18,7 @@ import { CentralCommandDispatcher } from "./EventManagement/CentralCommandDispat import { Connection } from "../Connection/Connection"; import { LogManager } from "@aliceo2/web-ui"; import { Command, CommandHandler } from "models/commands.model"; -import { DuplexMessageEvent } from "models/message.model"; +import { DuplexMessageEvent } from "../../models/message.model"; /** * @description Manages all the connection between clients and central system. @@ -114,4 +114,14 @@ export class ConnectionManager { this.receivingConnections.get(address) ); } + + public getAllConnections(): { + sending: Connection[]; + receiving: Connection[]; + } { + return { + sending: [...this.sendingConnections.values()], + receiving: [...this.receivingConnections.values()], + }; + } } diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts index f6eb7ccf6..451f275b8 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts @@ -14,24 +14,26 @@ import { LogManager } from "@aliceo2/web-ui"; import { Command, CommandHandler } from "models/commands.model"; -import { DuplexMessageEvent } from "models/message.model"; +import { DuplexMessageEvent } from "../../../models/message.model"; export class CentralCommandDispatcher { private handlers = new Map(); private logger = LogManager.getLogger("CentralCommandDispatcher"); register( - type: DuplexMessageEvent, + event: DuplexMessageEvent, handler: CommandHandler ): void { - this.handlers.set(type, handler); + console.log(`Registering handler for command type: ${event}`); + this.handlers.set(event, handler); } async dispatch(command: Command): Promise { - const handler = this.handlers.get(command.type); + const handler = this.handlers.get(command.event); + this.logger.debugMessage(`Dispatching command: ${command.event}`); if (!handler) { this.logger.warnMessage( - `No handler registered for command type: ${command.type}` + `No handler registered for command type: ${command.event}` ); return; } @@ -40,7 +42,7 @@ export class CentralCommandDispatcher { await handler.handle(command); } catch (error) { this.logger.errorMessage( - `Error handling command ${command.type}:`, + `Error handling command ${command.event}:`, error ); } diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index 0452daa22..6e1dd3148 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -14,7 +14,8 @@ import path from "path"; import { ConnectionManager } from "./ConnectionManager/ConnectionManager"; import { RevokeTokenHandler } from "./Commands/revokeToken.handler"; -import { DuplexMessageEvent } from "models/message.model"; +import { DuplexMessageEvent } from "../models/message.model"; +import { Connection } from "./Connection/Connection"; /** * @description Wrapper class for managing secure gRPC wrapper. @@ -52,11 +53,44 @@ export class gRPCWrapper { /** * @description Starts the Connection Manager stream connection with Central System */ - public connectToCentralSystem(): void { + public connectToCentralSystem() { this.ConnectionManager.connectToCentralSystem(); } + + /** + * @description Returns all saved connections. + * + * @returns An object containing the sending and receiving connections. + */ + public getAllConnections(): { + sending: Connection[]; + receiving: Connection[]; + } { + return this.ConnectionManager.getAllConnections(); + } + + public getSummary(): string { + const conn = this.ConnectionManager.getAllConnections(); + return ( + `Wrapper Summary: ` + + `\nSending Connections: ${conn.sending.length}` + + `\nReceiving Connections: ${conn.receiving.length}` + + conn.sending + .map((c) => `\n- ${c.getTargetAddress()} (${c.getStatus()})`) + .join("") + + conn.receiving + .map((c) => `\n- ${c.getTargetAddress()} (${c.getStatus()})`) + .join("") + ); + } } const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); const grpc = new gRPCWrapper(PROTO_PATH, "localhost:50051"); grpc.connectToCentralSystem(); +console.log(grpc.getSummary()); + +setTimeout(() => { + console.log("New status after 10 seconds and token revokation:"); + console.log(grpc.getSummary()); +}, 10000); diff --git a/Tokenization/backend/wrapper/src/models/commands.model.ts b/Tokenization/backend/wrapper/src/models/commands.model.ts index 0a595b005..874a301d4 100644 --- a/Tokenization/backend/wrapper/src/models/commands.model.ts +++ b/Tokenization/backend/wrapper/src/models/commands.model.ts @@ -22,7 +22,7 @@ import { DuplexMessageEvent } from "./message.model"; */ export interface Command { - type: DuplexMessageEvent; + event: DuplexMessageEvent; payload: any; } diff --git a/Tokenization/backend/wrapper/src/models/message.model.ts b/Tokenization/backend/wrapper/src/models/message.model.ts index 07abc0b18..d61598439 100644 --- a/Tokenization/backend/wrapper/src/models/message.model.ts +++ b/Tokenization/backend/wrapper/src/models/message.model.ts @@ -23,9 +23,9 @@ * @property MESSAGE_EVENT_REVOKE_TOKEN: Event for revoking an existing token. */ export enum DuplexMessageEvent { - MESSAGE_EVENT_EMPTY = 0, - MESSAGE_EVENT_NEW_TOKEN = 1, - MESSAGE_EVENT_REVOKE_TOKEN = 2, + MESSAGE_EVENT_EMPTY = "MESSAGE_EVENT_EMPTY", + MESSAGE_EVENT_NEW_TOKEN = "MESSAGE_EVENT_NEW_TOKEN", + MESSAGE_EVENT_REVOKE_TOKEN = "MESSAGE_EVENT_REVOKE_TOKEN", } // ====================================== @@ -46,10 +46,9 @@ export interface TokenMessage { * @description Model for duplex stream messages between client and central system. * @property {DuplexMessageEvent} event - The event type of the message. * @property {TokenMessage} data - The data associated with the event, it may be undefined for some events. - * @example {event: DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN, data: {token: '', targetAddress: ''}} + * @example {event: DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN, {token: '', targetAddress: ''}} */ export interface DuplexMessageModel { event: DuplexMessageEvent; - newToken?: TokenMessage; - revokeToken?: TokenMessage; + payload: TokenMessage; } diff --git a/Tokenization/backend/wrapper/src/proto/wrapper.proto b/Tokenization/backend/wrapper/src/proto/wrapper.proto index 9cb03f258..6b2de2832 100644 --- a/Tokenization/backend/wrapper/src/proto/wrapper.proto +++ b/Tokenization/backend/wrapper/src/proto/wrapper.proto @@ -44,8 +44,7 @@ message Payload { // Data related to specific event type oneof data { EmptyMessage emptyMessage = 2; - Token newToken = 3; - Token revokeToken = 4; + Token payload = 3; } } diff --git a/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts b/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts index 972565210..07bd939a6 100644 --- a/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts @@ -18,16 +18,17 @@ import { Connection } from "../../../client/Connection/Connection"; import { ConnectionManager } from "../../../client/ConnectionManager/ConnectionManager"; import { DuplexMessageEvent } from "../../../models/message.model"; import { ConnectionStatus } from "../../../models/connection.model"; +import { Command } from "models/commands.model"; describe("RevokeToken", () => { function createEventMessage(targetAddress: string) { return { event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, - revokeToken: { + payload: { token: "test-token", targetAddress, }, - }; + } as Command; } let manager: ConnectionManager; @@ -51,7 +52,9 @@ describe("RevokeToken", () => { (manager as any).sendingConnections!.set(targetAddress, conn); const handler = new RevokeTokenHandler(manager); - const command = new RevokeTokenCommand(createEventMessage(targetAddress)); + const command = new RevokeTokenCommand( + createEventMessage(targetAddress).payload + ); await handler.handle(command); @@ -65,7 +68,9 @@ describe("RevokeToken", () => { (manager as any).receivingConnections.set(targetAddress, conn); const handler = new RevokeTokenHandler(manager); - const command = new RevokeTokenCommand(createEventMessage(targetAddress)); + const command = new RevokeTokenCommand( + createEventMessage(targetAddress).payload + ); await handler.handle(command); @@ -76,7 +81,9 @@ describe("RevokeToken", () => { it("should do nothing when connection not found", async () => { const targetAddress = "non-existent"; const handler = new RevokeTokenHandler(manager); - const command = new RevokeTokenCommand(createEventMessage(targetAddress)); + const command = new RevokeTokenCommand( + createEventMessage(targetAddress).payload + ); await expect(handler.handle(command)).resolves.toBeUndefined(); expect(manager.getConnectionByAddress).toHaveBeenCalledWith(targetAddress); @@ -98,9 +105,9 @@ describe("RevokeToken", () => { it("should create command with correct type and payload", () => { const eventMessage = createEventMessage("peer-001"); - const command = new RevokeTokenCommand(eventMessage); + const command = new RevokeTokenCommand(eventMessage.payload); - expect(command.type).toBe(DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN); + expect(command.event).toBe(DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN); expect(command.payload).toEqual(eventMessage); }); }); From 5527944cdc9e8db7e7f0312ec70a9cab1921c9fd Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 5 Aug 2025 22:25:14 +0200 Subject: [PATCH 039/103] feat: implement connection direction info inside of payload --- .../wrapper/src/central/CentralSystem.ts | 2 ++ .../client/Commands/revokeToken.handler.ts | 6 +++- .../src/client/Connection/Connection.ts | 7 +++- .../ConnectionManager/ConnectionManager.ts | 33 ++++++++++++++----- .../backend/wrapper/src/client/gRPCWrapper.ts | 10 ++++-- .../wrapper/src/models/message.model.ts | 21 +++++++++++- .../backend/wrapper/src/proto/wrapper.proto | 13 ++++++++ .../test/client/Commands/revokeToken.test.ts | 17 ++++++++-- 8 files changed, 93 insertions(+), 16 deletions(-) diff --git a/Tokenization/backend/wrapper/src/central/CentralSystem.ts b/Tokenization/backend/wrapper/src/central/CentralSystem.ts index 0d122c390..c5a827059 100644 --- a/Tokenization/backend/wrapper/src/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/src/central/CentralSystem.ts @@ -16,6 +16,7 @@ import * as protoLoader from "@grpc/proto-loader"; import path from "path"; import { LogManager } from "@aliceo2/web-ui"; import { + ConnectionDirection, DuplexMessageEvent, DuplexMessageModel, } from "../models/message.model"; @@ -194,6 +195,7 @@ setTimeout(() => { centralSystem.sendEvent(centralSystem.getConnectedClients()[0], { event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, payload: { + connectionDirection: ConnectionDirection.SENDING, targetAddress: "a", }, }); diff --git a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts b/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts index 08a8b9f89..1cf9d00d8 100644 --- a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts +++ b/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts @@ -24,7 +24,11 @@ export class RevokeTokenHandler implements CommandHandler { throw new Error("Target address is required to revoke token."); } - const conn = this.manager.getConnectionByAddress(targetAddress); + const conn = this.manager.getConnectionByAddress( + targetAddress, + command.payload.connectionDirection + ); + conn?.handleRevokeToken(); } } diff --git a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts index 79ebfdcec..2347fa961 100644 --- a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts +++ b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts @@ -11,6 +11,7 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ +import { ConnectionDirection } from "../../models/message.model"; import { ConnectionStatus } from "../../models/connection.model"; /** @@ -21,7 +22,11 @@ export class Connection { private targetAddress: string; private status: ConnectionStatus; - constructor(token: string, targetAddress: string) { + constructor( + token: string, + targetAddress: string, + public direction: ConnectionDirection + ) { this.token = token; this.targetAddress = targetAddress; diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts index 06712e2c2..4f0c11cc0 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts @@ -18,7 +18,10 @@ import { CentralCommandDispatcher } from "./EventManagement/CentralCommandDispat import { Connection } from "../Connection/Connection"; import { LogManager } from "@aliceo2/web-ui"; import { Command, CommandHandler } from "models/commands.model"; -import { DuplexMessageEvent } from "../../models/message.model"; +import { + ConnectionDirection, + DuplexMessageEvent, +} from "../../models/message.model"; /** * @description Manages all the connection between clients and central system. @@ -75,8 +78,14 @@ export class ConnectionManager { this.centralDispatcher ); - this.sendingConnections.set("a", new Connection("1", "a")); - this.sendingConnections.set("b", new Connection("2", "b")); + this.sendingConnections.set( + "a", + new Connection("1", "a", ConnectionDirection.SENDING) + ); + this.sendingConnections.set( + "b", + new Connection("2", "b", ConnectionDirection.SENDING) + ); } registerCommandHandlers( @@ -108,11 +117,19 @@ export class ConnectionManager { * @description Gets the connection instance by address. * @returns{Connection} connection instance. */ - getConnectionByAddress(address: string): Connection | undefined { - return ( - this.sendingConnections.get(address) || - this.receivingConnections.get(address) - ); + getConnectionByAddress( + address: string, + direction: ConnectionDirection + ): Connection | undefined { + switch (direction) { + case ConnectionDirection.SENDING: + return this.sendingConnections.get(address); + case ConnectionDirection.RECEIVING: + return this.receivingConnections.get(address); + default: + this.logger.errorMessage(`Invalid connection direction: ${direction}`); + return undefined; + } } public getAllConnections(): { diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index 6e1dd3148..9673c6ad2 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -76,10 +76,16 @@ export class gRPCWrapper { `\nSending Connections: ${conn.sending.length}` + `\nReceiving Connections: ${conn.receiving.length}` + conn.sending - .map((c) => `\n- ${c.getTargetAddress()} (${c.getStatus()})`) + .map( + (c) => + `\n- ${c.getTargetAddress()} - ${c.direction}\n\t(${c.getStatus()})` + ) .join("") + conn.receiving - .map((c) => `\n- ${c.getTargetAddress()} (${c.getStatus()})`) + .map( + (c) => + `\n- ${c.getTargetAddress()} - ${c.direction}\n\t(${c.getStatus()})` + ) .join("") ); } diff --git a/Tokenization/backend/wrapper/src/models/message.model.ts b/Tokenization/backend/wrapper/src/models/message.model.ts index d61598439..fb64a4002 100644 --- a/Tokenization/backend/wrapper/src/models/message.model.ts +++ b/Tokenization/backend/wrapper/src/models/message.model.ts @@ -28,6 +28,18 @@ export enum DuplexMessageEvent { MESSAGE_EVENT_REVOKE_TOKEN = "MESSAGE_EVENT_REVOKE_TOKEN", } +/** + * @enum Represents the direction of a connection in the system. + * @property SENDING: Indicates a connection where messages are sent to another client. + * @property RECEIVING: Indicates a connection where messages are received from another client. + * @property DUPLEX: Indicates a connection that can both send and receive messages. + */ +export enum ConnectionDirection { + SENDING = "SENDING", + RECEIVING = "RECEIVING", + DUPLEX = "DUPLEX", +} + // ====================================== // INTERFACES // ====================================== @@ -39,14 +51,21 @@ export enum DuplexMessageEvent { */ export interface TokenMessage { token?: string; + connectionDirection: ConnectionDirection; targetAddress: string; } /** * @description Model for duplex stream messages between client and central system. * @property {DuplexMessageEvent} event - The event type of the message. + * @property {ConnectionDirection} connectionDirection - The direction of the connection, optional for some events. * @property {TokenMessage} data - The data associated with the event, it may be undefined for some events. - * @example {event: DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN, {token: '', targetAddress: ''}} + * @example + * { + * event: DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN, + * connectionDirection: ConnectionDirection.SENDING, + * payload: {token: 'abc', targetAddress: 'localhost:50051'} + * } */ export interface DuplexMessageModel { event: DuplexMessageEvent; diff --git a/Tokenization/backend/wrapper/src/proto/wrapper.proto b/Tokenization/backend/wrapper/src/proto/wrapper.proto index 6b2de2832..ad2cac80a 100644 --- a/Tokenization/backend/wrapper/src/proto/wrapper.proto +++ b/Tokenization/backend/wrapper/src/proto/wrapper.proto @@ -35,12 +35,14 @@ message EmptyMessage {} message Token { string token = 1; string targetAddress = 2; + ConnectionDirection connectionDirection = 3; } // Stream message that can contain one of specific messages message Payload { // Message event type MessageEvent event = 1; + // Data related to specific event type oneof data { EmptyMessage emptyMessage = 2; @@ -62,3 +64,14 @@ enum MessageEvent { // Revoke token message type, contains a token to be revoked MESSAGE_EVENT_REVOKE_TOKEN = 2; } + +enum ConnectionDirection { + // Direction from client to server + SENDING = 1; + + // Direction from server to client + RECEIVING = 2; + + // Duplex connection, both sending and receiving + DUPLEX = 3; +} \ No newline at end of file diff --git a/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts b/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts index 07bd939a6..554507eee 100644 --- a/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts @@ -16,7 +16,10 @@ import { RevokeTokenCommand } from "../../../client/Commands/revokeToken.command import { RevokeTokenHandler } from "../../../client/Commands/revokeToken.handler"; import { Connection } from "../../../client/Connection/Connection"; import { ConnectionManager } from "../../../client/ConnectionManager/ConnectionManager"; -import { DuplexMessageEvent } from "../../../models/message.model"; +import { + ConnectionDirection, + DuplexMessageEvent, +} from "../../../models/message.model"; import { ConnectionStatus } from "../../../models/connection.model"; import { Command } from "models/commands.model"; @@ -48,7 +51,11 @@ describe("RevokeToken", () => { it("should revoke token when connection found in sendingConnections", async () => { const targetAddress = "peer-123"; - const conn = new Connection("valid-token", targetAddress); + const conn = new Connection( + "valid-token", + targetAddress, + ConnectionDirection.SENDING + ); (manager as any).sendingConnections!.set(targetAddress, conn); const handler = new RevokeTokenHandler(manager); @@ -64,7 +71,11 @@ describe("RevokeToken", () => { it("should revoke token when connection found in receivingConnections", async () => { const targetAddress = "peer-456"; - const conn = new Connection("valid-token", targetAddress); + const conn = new Connection( + "valid-token", + targetAddress, + ConnectionDirection.RECEIVING + ); (manager as any).receivingConnections.set(targetAddress, conn); const handler = new RevokeTokenHandler(manager); From 090da6ba85d11e81dee9dde401572644a6e35c45 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 7 Aug 2025 10:37:01 +0200 Subject: [PATCH 040/103] fix: fix tests --- .../wrapper/src/test/central/CentralSystem.test.ts | 3 ++- .../wrapper/src/test/client/Commands/revokeToken.test.ts | 9 ++++++--- Tokenization/backend/wrapper/tsconfig.build.json | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts b/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts index 1599e0ccd..093412940 100644 --- a/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts +++ b/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts @@ -104,8 +104,9 @@ describe("CentralSystemWrapper", () => { expect(mockCall.end).toHaveBeenCalled(); expect(logger.infoMessage).toHaveBeenCalledWith( - "Client client123 connected to CentralSystem stream stream" + expect.stringContaining("Client client123") ); + expect(logger.infoMessage).toHaveBeenCalledWith( "Client client123 ended stream." ); diff --git a/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts b/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts index 554507eee..2f783fe16 100644 --- a/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts @@ -28,8 +28,8 @@ describe("RevokeToken", () => { return { event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, payload: { + targetAddress: targetAddress, token: "test-token", - targetAddress, }, } as Command; } @@ -97,7 +97,10 @@ describe("RevokeToken", () => { ); await expect(handler.handle(command)).resolves.toBeUndefined(); - expect(manager.getConnectionByAddress).toHaveBeenCalledWith(targetAddress); + expect(manager.getConnectionByAddress).toHaveBeenCalledWith( + targetAddress, + undefined + ); }); it("should throw error when targetAddress is missing", async () => { @@ -119,6 +122,6 @@ describe("RevokeToken", () => { const command = new RevokeTokenCommand(eventMessage.payload); expect(command.event).toBe(DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN); - expect(command.payload).toEqual(eventMessage); + expect(command).toEqual(eventMessage); }); }); diff --git a/Tokenization/backend/wrapper/tsconfig.build.json b/Tokenization/backend/wrapper/tsconfig.build.json index 681ec5d54..9478b5399 100644 --- a/Tokenization/backend/wrapper/tsconfig.build.json +++ b/Tokenization/backend/wrapper/tsconfig.build.json @@ -8,5 +8,5 @@ "sourceMap": true }, "include": ["src/**/*.ts"], - "exclude": ["tests", "node_modules"] + "exclude": ["src/**/test", "node_modules"] } From fd0e1dd35aaecc32069c6e26652392656343d4da Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 7 Aug 2025 12:33:30 +0200 Subject: [PATCH 041/103] feat: implement command for new token from central system --- .../wrapper/src/central/CentralSystem.ts | 16 +++++++ .../Commands/newToken/newToken.command.ts | 23 ++++++++++ .../Commands/newToken/newToken.handler.ts | 43 +++++++++++++++++++ .../{ => revokeToken}/revokeToken.command.ts | 7 ++- .../{ => revokeToken}/revokeToken.handler.ts | 4 +- .../ConnectionManager/ConnectionManager.ts | 26 +++++++++++ .../backend/wrapper/src/client/gRPCWrapper.ts | 17 ++++++-- .../test/client/Commands/revokeToken.test.ts | 4 +- 8 files changed, 130 insertions(+), 10 deletions(-) create mode 100644 Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.command.ts create mode 100644 Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts rename Tokenization/backend/wrapper/src/client/Commands/{ => revokeToken}/revokeToken.command.ts (83%) rename Tokenization/backend/wrapper/src/client/Commands/{ => revokeToken}/revokeToken.handler.ts (88%) diff --git a/Tokenization/backend/wrapper/src/central/CentralSystem.ts b/Tokenization/backend/wrapper/src/central/CentralSystem.ts index c5a827059..c01e7f17d 100644 --- a/Tokenization/backend/wrapper/src/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/src/central/CentralSystem.ts @@ -199,4 +199,20 @@ setTimeout(() => { targetAddress: "a", }, }); + centralSystem.sendEvent(centralSystem.getConnectedClients()[0], { + event: DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN, + payload: { + connectionDirection: ConnectionDirection.SENDING, + targetAddress: "a", + token: "newToken", + }, + }); + centralSystem.sendEvent(centralSystem.getConnectedClients()[0], { + event: DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN, + payload: { + connectionDirection: ConnectionDirection.SENDING, + targetAddress: "c", + token: "tokenForNewAddress", + }, + }); }, 5000); diff --git a/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.command.ts b/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.command.ts new file mode 100644 index 000000000..98c6a5dc4 --- /dev/null +++ b/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.command.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ +import { Command } from "../../../models/commands.model"; +import { + DuplexMessageEvent, + TokenMessage, +} from "../../../models/message.model"; + +export class NewTokenCommand implements Command { + readonly event = DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN; + constructor(public payload: TokenMessage) {} +} diff --git a/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts b/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts new file mode 100644 index 000000000..b8471c407 --- /dev/null +++ b/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts @@ -0,0 +1,43 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ +import { CommandHandler } from "../../../models/commands.model"; +import { NewTokenCommand } from "./newToken.command"; +import { ConnectionManager } from "../../ConnectionManager/ConnectionManager"; +import { ConnectionDirection } from "../../../models/message.model"; + +export class NewTokenHandler implements CommandHandler { + constructor(private manager: ConnectionManager) {} + + async handle(command: NewTokenCommand): Promise { + const { targetAddress, connectionDirection, token } = command.payload || {}; + if (!targetAddress || !token || !connectionDirection) { + throw new Error( + "Insufficient arguments. Expected: targetAddress, connectionDirection, token." + ); + } + + const directions = + connectionDirection === ConnectionDirection.DUPLEX + ? [ConnectionDirection.SENDING, ConnectionDirection.RECEIVING] + : [connectionDirection]; + + for (const dir of directions) { + let conn = this.manager.getConnectionByAddress(targetAddress, dir); + if (!conn) { + conn = this.manager.createNewConnection(targetAddress, dir, token); + } + conn.handleNewToken(token); + } + } +} diff --git a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts b/Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.command.ts similarity index 83% rename from Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts rename to Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.command.ts index baeff3792..f92c99022 100644 --- a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts +++ b/Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.command.ts @@ -11,8 +11,11 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ -import { Command } from "../../models/commands.model"; -import { DuplexMessageEvent, TokenMessage } from "../../models/message.model"; +import { Command } from "../../../models/commands.model"; +import { + DuplexMessageEvent, + TokenMessage, +} from "../../../models/message.model"; export class RevokeTokenCommand implements Command { readonly event = DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN; diff --git a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts b/Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.handler.ts similarity index 88% rename from Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts rename to Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.handler.ts index 1cf9d00d8..2ca77a54f 100644 --- a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts +++ b/Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.handler.ts @@ -11,9 +11,9 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ -import { CommandHandler } from "../../models/commands.model"; +import { CommandHandler } from "../../../models/commands.model"; import { RevokeTokenCommand } from "./revokeToken.command"; -import { ConnectionManager } from "../ConnectionManager/ConnectionManager"; +import { ConnectionManager } from "../../ConnectionManager/ConnectionManager"; export class RevokeTokenHandler implements CommandHandler { constructor(private manager: ConnectionManager) {} diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts index 4f0c11cc0..c3d5bb45f 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts @@ -113,6 +113,28 @@ export class ConnectionManager { this.centralConnection.disconnect(); } + /** + * Creates new connection + * @param address Target (external) address of the connection + * @param direction Direction of connection + * @param token Optional token for connection + */ + createNewConnection( + address: string, + direction: ConnectionDirection, + token?: string + ) { + const conn = new Connection(token || "", address, direction); + + if (direction === ConnectionDirection.RECEIVING) { + this.receivingConnections.set(address, conn); + } else { + this.sendingConnections.set(address, conn); + } + + return conn; + } + /** * @description Gets the connection instance by address. * @returns{Connection} connection instance. @@ -132,6 +154,10 @@ export class ConnectionManager { } } + /** + * Returns object with all connections + * @returns Object of all connections + */ public getAllConnections(): { sending: Connection[]; receiving: Connection[]; diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index 9673c6ad2..94655ef91 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -13,9 +13,10 @@ */ import path from "path"; import { ConnectionManager } from "./ConnectionManager/ConnectionManager"; -import { RevokeTokenHandler } from "./Commands/revokeToken.handler"; +import { RevokeTokenHandler } from "./Commands/revokeToken/revokeToken.handler"; import { DuplexMessageEvent } from "../models/message.model"; import { Connection } from "./Connection/Connection"; +import { NewTokenHandler } from "./Commands/newToken/newToken.handler"; /** * @description Wrapper class for managing secure gRPC wrapper. @@ -47,6 +48,10 @@ export class gRPCWrapper { event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, handler: new RevokeTokenHandler(this.ConnectionManager), }, + { + event: DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN, + handler: new NewTokenHandler(this.ConnectionManager), + }, ]); } @@ -78,13 +83,17 @@ export class gRPCWrapper { conn.sending .map( (c) => - `\n- ${c.getTargetAddress()} - ${c.direction}\n\t(${c.getStatus()})` + `\n- ${c.getTargetAddress()} \nDirection - ${ + c.direction + }\n\tStatus: (${c.getStatus()})\n\tToken: (${c.getToken()})` ) .join("") + conn.receiving .map( (c) => - `\n- ${c.getTargetAddress()} - ${c.direction}\n\t(${c.getStatus()})` + `\n- ${c.getTargetAddress()} \nDirection - ${ + c.direction + }\n\tStatus: (${c.getStatus()})\n\tToken: (${c.getToken()})` ) .join("") ); @@ -97,6 +106,6 @@ grpc.connectToCentralSystem(); console.log(grpc.getSummary()); setTimeout(() => { - console.log("New status after 10 seconds and token revokation:"); + console.log("New status after 10 seconds, token revokation and new token:"); console.log(grpc.getSummary()); }, 10000); diff --git a/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts b/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts index 2f783fe16..2602c5173 100644 --- a/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts @@ -12,8 +12,8 @@ * or submit itself to any jurisdiction. */ -import { RevokeTokenCommand } from "../../../client/Commands/revokeToken.command"; -import { RevokeTokenHandler } from "../../../client/Commands/revokeToken.handler"; +import { RevokeTokenCommand } from "../../../client/Commands/revokeToken/revokeToken.command"; +import { RevokeTokenHandler } from "../../../client/Commands/revokeToken/revokeToken.handler"; import { Connection } from "../../../client/Connection/Connection"; import { ConnectionManager } from "../../../client/ConnectionManager/ConnectionManager"; import { From 5965abeb45836f40491b55458e4cfb276650e777 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 7 Aug 2025 14:54:24 +0200 Subject: [PATCH 042/103] feat: add missing comments, banners and tests --- Tokenization/backend/wrapper/package.json | 3 +- .../backend/wrapper/scripts/banner.js | 68 ++++++++ .../wrapper/src/central/CentralSystem.ts | 1 + .../Commands/newToken/newToken.command.ts | 4 + .../Commands/newToken/newToken.handler.ts | 14 ++ .../revokeToken/revokeToken.command.ts | 4 + .../revokeToken/revokeToken.handler.ts | 17 ++ .../src/client/Connection/Connection.ts | 11 ++ .../ConnectionManager/CentralConnection.ts | 3 +- .../ConnectionManager/ConnectionManager.ts | 8 +- .../CentralCommandDispatcher.ts | 17 ++ .../backend/wrapper/src/client/gRPCWrapper.ts | 6 +- .../wrapper/src/models/commands.model.ts | 12 +- .../wrapper/src/models/connection.model.ts | 13 ++ .../src/test/central/CentralSystem.test.ts | 14 ++ .../src/test/client/Commands/newToken.test.ts | 154 ++++++++++++++++++ .../test/client/ConnectionManager/index.ts | 14 ++ .../wrapper/src/utils/types/webui.d.ts | 14 ++ 18 files changed, 368 insertions(+), 9 deletions(-) create mode 100644 Tokenization/backend/wrapper/scripts/banner.js create mode 100644 Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts diff --git a/Tokenization/backend/wrapper/package.json b/Tokenization/backend/wrapper/package.json index 7f472886a..0c420b84c 100644 --- a/Tokenization/backend/wrapper/package.json +++ b/Tokenization/backend/wrapper/package.json @@ -6,7 +6,8 @@ "test": "jest", "build": "tsc -p tsconfig.build.json && cp -r src/proto dist", "client": "node dist/client/gRPCWrapper.js", - "central": "node dist/central/CentralSystem.js" + "central": "node dist/central/CentralSystem.js", + "process-banners": "node scripts/banner.js" }, "author": "ALICEO2", "devDependencies": { diff --git a/Tokenization/backend/wrapper/scripts/banner.js b/Tokenization/backend/wrapper/scripts/banner.js new file mode 100644 index 000000000..bbba4a30f --- /dev/null +++ b/Tokenization/backend/wrapper/scripts/banner.js @@ -0,0 +1,68 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +const fs = require("fs"); +const path = require("path"); + +const banner = `/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ +`; + +const processFile = (filePath) => { + try { + const content = fs.readFileSync(filePath, "utf8"); + + if (content.trim().startsWith(banner.trim())) { + return; + } + + const newContent = banner + "\n" + content; + fs.writeFileSync(filePath, newContent, "utf8"); + console.log(`Added banner to: ${filePath}`); + } catch (err) { + console.error(`Error with file ${filePath}:`, err); + } +}; + +const excludedDirs = ["node_modules", "dist"]; +const walkDir = (dir) => { + const files = fs.readdirSync(dir, { withFileTypes: true }); + + for (const file of files) { + const fullPath = path.join(dir, file.name); + + if (file.isDirectory() && !excludedDirs.includes(fullPath)) { + walkDir(fullPath); + } else if (file.isFile()) { + if (/\.(js|ts|jsx|tsx|mjs|cjs)$/.test(file.name)) { + processFile(fullPath); + } + } + } +}; + +const startDir = "./src/"; +walkDir(startDir); +console.log("Banners processed."); diff --git a/Tokenization/backend/wrapper/src/central/CentralSystem.ts b/Tokenization/backend/wrapper/src/central/CentralSystem.ts index c01e7f17d..58d938eed 100644 --- a/Tokenization/backend/wrapper/src/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/src/central/CentralSystem.ts @@ -11,6 +11,7 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ + import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; import path from "path"; diff --git a/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.command.ts b/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.command.ts index 98c6a5dc4..df8889d0d 100644 --- a/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.command.ts +++ b/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.command.ts @@ -11,12 +11,16 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ + import { Command } from "../../../models/commands.model"; import { DuplexMessageEvent, TokenMessage, } from "../../../models/message.model"; +/** + * @description Command used to trigger new token for a specific connection. Handles structure logic. + */ export class NewTokenCommand implements Command { readonly event = DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN; constructor(public payload: TokenMessage) {} diff --git a/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts b/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts index b8471c407..cac108af3 100644 --- a/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts +++ b/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts @@ -11,14 +11,28 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ + import { CommandHandler } from "../../../models/commands.model"; import { NewTokenCommand } from "./newToken.command"; import { ConnectionManager } from "../../ConnectionManager/ConnectionManager"; import { ConnectionDirection } from "../../../models/message.model"; +/** + * @description Handles the NewTokenCommand by updating or creating a connection with a new authentication token. + */ export class NewTokenHandler implements CommandHandler { + /** + * @param manager - Instance of ConnectionManager used to access and manage connections. + */ constructor(private manager: ConnectionManager) {} + /** + * @description Processes the NewTokenCommand by assigning a new token to the specified connection. + * If the connection does not exist, it is created. + * + * @param command - The new token event command. + * @throws Will throw an error if any of the required payload fields are missing. + */ async handle(command: NewTokenCommand): Promise { const { targetAddress, connectionDirection, token } = command.payload || {}; if (!targetAddress || !token || !connectionDirection) { diff --git a/Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.command.ts b/Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.command.ts index f92c99022..656bd6496 100644 --- a/Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.command.ts +++ b/Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.command.ts @@ -11,12 +11,16 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ + import { Command } from "../../../models/commands.model"; import { DuplexMessageEvent, TokenMessage, } from "../../../models/message.model"; +/** + * @description Command used to trigger token revocation for a specific connection. Handles structure logic. + */ export class RevokeTokenCommand implements Command { readonly event = DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN; constructor(public payload: TokenMessage) {} diff --git a/Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.handler.ts b/Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.handler.ts index 2ca77a54f..af923fc36 100644 --- a/Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.handler.ts +++ b/Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.handler.ts @@ -11,13 +11,30 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ + import { CommandHandler } from "../../../models/commands.model"; import { RevokeTokenCommand } from "./revokeToken.command"; import { ConnectionManager } from "../../ConnectionManager/ConnectionManager"; +/** + * RevokeTokenHandler is responsible for handling the RevokeTokenCommand. + * It retrieves the target connection using the provided address and direction, + * and calls `handleRevokeToken()` on that connection if it exists. + */ export class RevokeTokenHandler implements CommandHandler { + /** + * Creates a new instance of RevokeTokenHandler. + * + * @param manager - The ConnectionManager used to retrieve active connections. + */ constructor(private manager: ConnectionManager) {} + /** + * Handles the RevokeTokenCommand by revoking the token on the target connection. + * + * @param command - The RevokeTokenCommand containing the target address and direction. + * @throws Will throw an error if the target address is missing in the command payload. + */ async handle(command: RevokeTokenCommand): Promise { const { targetAddress } = command.payload || {}; if (!targetAddress) { diff --git a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts index 2347fa961..c0e4aa71f 100644 --- a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts +++ b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts @@ -11,6 +11,7 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ + import { ConnectionDirection } from "../../models/message.model"; import { ConnectionStatus } from "../../models/connection.model"; @@ -22,6 +23,13 @@ export class Connection { private targetAddress: string; private status: ConnectionStatus; + /** + * @description Creates a new Connection instance with the given token, target address, and connection direction. + * + * @param token - The authentication token for the connection. + * @param targetAddress - The unique address of the target client. + * @param direction - The direction of the connection (e.g., sending or receiving). + */ constructor( token: string, targetAddress: string, @@ -41,6 +49,9 @@ export class Connection { this.token = token; } + /** + * @description Revoke current token and set status of unauthorized connection + */ public handleRevokeToken(): void { this.token = ""; this.status = ConnectionStatus.UNAUTHORIZED; diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts index 30cc02b96..f41a586fd 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts @@ -11,6 +11,7 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ + import * as grpc from "@grpc/grpc-js"; import { LogManager } from "@aliceo2/web-ui"; import { CentralCommandDispatcher } from "./EventManagement/CentralCommandDispatcher"; @@ -38,7 +39,7 @@ export class CentralConnection { this.stream = this.client.ClientStream(); this.stream!.on("data", (payload: DuplexMessageModel) => { - console.log("Received payload:", JSON.stringify(payload)); + this.logger.debugMessage(`Received payload: ${JSON.stringify(payload)}`); this.dispatcher.dispatch(payload); }); diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts index c3d5bb45f..34e73cd74 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts @@ -11,6 +11,7 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ + import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; import { CentralConnection } from "./CentralConnection"; @@ -36,6 +37,7 @@ import { * * @remarks * - `centralConnection`: Handles the duplex stream with the central gRPC server. + * - `centralDispatcher`: Dispatcher for central system events * - `sendingConnections`: Map of active outbound connections. * - `receivingConnections`: Map of active inbound connections. */ @@ -71,8 +73,8 @@ export class ConnectionManager { grpc.credentials.createInsecure() ); + // event dispatcher for central system events this.centralDispatcher = new CentralCommandDispatcher(); - this.centralConnection = new CentralConnection( client, this.centralDispatcher @@ -88,6 +90,10 @@ export class ConnectionManager { ); } + /** + * Registers new Command Handler for specific central event + * @param commandHandlers Array of event names and handler instances + */ registerCommandHandlers( commandHandlers: { event: DuplexMessageEvent; diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts index 451f275b8..013f98132 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts @@ -16,10 +16,21 @@ import { LogManager } from "@aliceo2/web-ui"; import { Command, CommandHandler } from "models/commands.model"; import { DuplexMessageEvent } from "../../../models/message.model"; +/** + * CentralCommandDispatcher is responsible for registering and dispatching command handlers + * based on the command's event type. It acts as the central hub for routing incoming + * command messages coming from central system to the appropriate handler functions. + */ export class CentralCommandDispatcher { private handlers = new Map(); private logger = LogManager.getLogger("CentralCommandDispatcher"); + /** + * Registers a command handler for a specific command event type. + * + * @param event - The event type of the command to be handled. + * @param handler - The handler that should process commands of the given event type. + */ register( event: DuplexMessageEvent, handler: CommandHandler @@ -28,6 +39,12 @@ export class CentralCommandDispatcher { this.handlers.set(event, handler); } + /** + * Dispatches a command to the appropriate registered handler based on its event type. + * Logs warnings if no handler is found, and catches/logs errors during handler execution. + * + * @param command - The command object containing an event and its associated payload. + */ async dispatch(command: Command): Promise { const handler = this.handlers.get(command.event); this.logger.debugMessage(`Dispatching command: ${command.event}`); diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index 94655ef91..48000d61f 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -11,6 +11,7 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ + import path from "path"; import { ConnectionManager } from "./ConnectionManager/ConnectionManager"; import { RevokeTokenHandler } from "./Commands/revokeToken/revokeToken.handler"; @@ -28,7 +29,7 @@ import { NewTokenHandler } from "./Commands/newToken/newToken.handler"; * * @example * ```typescript - * const grpcWrapper = new gRPCWrapper(); + * const grpcWrapper = new gRPCWrapper(PROTO_PATH, CENTRAL_SYSTEM_ADDRESS); * // Use grpcWrapper to interact with gRPC services * ``` */ @@ -74,6 +75,9 @@ export class gRPCWrapper { return this.ConnectionManager.getAllConnections(); } + /** + * @returns Returns string with summary of all connection + */ public getSummary(): string { const conn = this.ConnectionManager.getAllConnections(); return ( diff --git a/Tokenization/backend/wrapper/src/models/commands.model.ts b/Tokenization/backend/wrapper/src/models/commands.model.ts index 874a301d4..1bba14108 100644 --- a/Tokenization/backend/wrapper/src/models/commands.model.ts +++ b/Tokenization/backend/wrapper/src/models/commands.model.ts @@ -15,17 +15,19 @@ import { DuplexMessageEvent } from "./message.model"; /** - * Interface representing a handler for processing events. - * - * @remarks - * The `handle` method receives an event object and performs the necessary processing. + * Interface representing a Command for specific event. */ - export interface Command { event: DuplexMessageEvent; payload: any; } +/** + * Interface representing a handler for processing events. + * + * @remarks + * The `handle` method receives an event object and performs the necessary processing. + */ export interface CommandHandler { handle(command: T): Promise; } diff --git a/Tokenization/backend/wrapper/src/models/connection.model.ts b/Tokenization/backend/wrapper/src/models/connection.model.ts index 474a2964c..b57432958 100644 --- a/Tokenization/backend/wrapper/src/models/connection.model.ts +++ b/Tokenization/backend/wrapper/src/models/connection.model.ts @@ -1,3 +1,16 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ export enum ConnectionStatus { // The connection is in the process of being established CONNECTING = "CONNECTING", diff --git a/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts b/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts index 093412940..3b85e4c6e 100644 --- a/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts +++ b/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts @@ -1,3 +1,17 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + const mockAddService = jest.fn(); const mockBindAsync = jest.fn(); const mockServerInstance = { diff --git a/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts b/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts new file mode 100644 index 000000000..91f109204 --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts @@ -0,0 +1,154 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { NewTokenCommand } from "../../../client/Commands/newToken/newToken.command"; +import { NewTokenHandler } from "../../../client/Commands/newToken/newToken.handler"; +import { Connection } from "../../../client/Connection/Connection"; +import { ConnectionManager } from "../../../client/ConnectionManager/ConnectionManager"; +import { Command } from "models/commands.model"; +import { + ConnectionDirection, + DuplexMessageEvent, +} from "../../../models/message.model"; + +/** + * Helper to create a new token command with given address, direction, and token. + */ +function createEventMessage( + targetAddress: string, + connectionDirection: ConnectionDirection +): Command { + return { + event: DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN, + payload: { + targetAddress, + connectionDirection, + token: "test-token", + }, + } as Command; +} + +describe("NewTokenHandler", () => { + let manager: ConnectionManager; + + beforeEach(() => { + manager = { + sendingConnections: new Map(), + receivingConnections: new Map(), + getConnectionByAddress: jest.fn(function ( + this: any, + address: string, + dir: ConnectionDirection + ) { + if (dir === ConnectionDirection.SENDING) { + return this.sendingConnections.get(address); + } else if (dir === ConnectionDirection.RECEIVING) { + return this.receivingConnections.get(address); + } + return undefined; + }), + createNewConnection: jest.fn(function ( + this: any, + address: string, + dir: ConnectionDirection, + token: string + ) { + const conn = new Connection(token, address, dir); + if (dir === ConnectionDirection.SENDING) { + this.sendingConnections.set(address, conn); + } else { + this.receivingConnections.set(address, conn); + } + return conn; + }), + } as unknown as ConnectionManager; + }); + + it("should update token on existing SENDING connection", async () => { + const targetAddress = "peer-123"; + const conn = new Connection( + "old-token", + targetAddress, + ConnectionDirection.SENDING + ); + (manager as any).sendingConnections.set(targetAddress, conn); + + const handler = new NewTokenHandler(manager); + const command = new NewTokenCommand( + createEventMessage(targetAddress, ConnectionDirection.SENDING).payload + ); + + await handler.handle(command); + + expect(conn.getToken()).toBe("test-token"); + }); + + it("should create new RECEIVING connection if not found", async () => { + const targetAddress = "peer-456"; + + const handler = new NewTokenHandler(manager); + const command = new NewTokenCommand( + createEventMessage(targetAddress, ConnectionDirection.RECEIVING).payload + ); + + await handler.handle(command); + + const conn = (manager as any).receivingConnections.get(targetAddress); + expect(conn).toBeDefined(); + expect(conn.getToken()).toBe("test-token"); + }); + + it("should handle DUPLEX direction by updating/creating both connections", async () => { + const targetAddress = "peer-789"; + + const handler = new NewTokenHandler(manager); + const command = new NewTokenCommand( + createEventMessage(targetAddress, ConnectionDirection.DUPLEX).payload + ); + + await handler.handle(command); + + const sendingConn = (manager as any).sendingConnections.get(targetAddress); + const receivingConn = (manager as any).receivingConnections.get( + targetAddress + ); + + expect(sendingConn).toBeDefined(); + expect(receivingConn).toBeDefined(); + expect(sendingConn.getToken()).toBe("test-token"); + expect(receivingConn.getToken()).toBe("test-token"); + }); + + it("should throw error when payload is missing required fields", async () => { + const invalidCommand = new NewTokenCommand({} as any); + + const handler = new NewTokenHandler(manager); + await expect(handler.handle(invalidCommand)).rejects.toThrow( + "Insufficient arguments. Expected: targetAddress, connectionDirection, token." + ); + }); + + it("should create command with correct event and payload", () => { + const payload = { + targetAddress: "peer-000", + connectionDirection: ConnectionDirection.SENDING, + token: "sample-token", + }; + + const command = new NewTokenCommand(payload); + + expect(command.event).toBe(DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN); + expect(command.payload).toEqual(payload); + }); +}); diff --git a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts index a2b34cde4..f60739154 100644 --- a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts +++ b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts @@ -1,3 +1,17 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + import * as grpc from "@grpc/grpc-js"; import { ConnectionManager } from "../../../client/ConnectionManager/ConnectionManager"; import { DuplexMessageEvent } from "../../../models/message.model"; diff --git a/Tokenization/backend/wrapper/src/utils/types/webui.d.ts b/Tokenization/backend/wrapper/src/utils/types/webui.d.ts index 1f2aeee51..1bf1cc574 100644 --- a/Tokenization/backend/wrapper/src/utils/types/webui.d.ts +++ b/Tokenization/backend/wrapper/src/utils/types/webui.d.ts @@ -1,3 +1,17 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + declare module "@aliceo2/web-ui" { export const LogManager: { getLogger: (name: string) => { From 456276204c90512079a9232df291f94c63a12ebf Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 7 Aug 2025 14:59:28 +0200 Subject: [PATCH 043/103] fix: fixed banner processing. removed console logs --- Tokenization/backend/wrapper/scripts/banner.js | 11 ++++++++--- .../EventManagement/CentralCommandDispatcher.ts | 2 +- Tokenization/backend/wrapper/src/proto/wrapper.proto | 3 ++- .../backend/wrapper/src/utils/types/webui.d.ts | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Tokenization/backend/wrapper/scripts/banner.js b/Tokenization/backend/wrapper/scripts/banner.js index bbba4a30f..a8a9be9e2 100644 --- a/Tokenization/backend/wrapper/scripts/banner.js +++ b/Tokenization/backend/wrapper/scripts/banner.js @@ -27,14 +27,19 @@ const banner = `/** * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ `; const processFile = (filePath) => { try { const content = fs.readFileSync(filePath, "utf8"); - if (content.trim().startsWith(banner.trim())) { + if ( + content.includes(`@license`) && + content.includes( + `Copyright 2019-2020 CERN and copyright holders of ALICE O2.` + ) + ) { return; } @@ -56,7 +61,7 @@ const walkDir = (dir) => { if (file.isDirectory() && !excludedDirs.includes(fullPath)) { walkDir(fullPath); } else if (file.isFile()) { - if (/\.(js|ts|jsx|tsx|mjs|cjs)$/.test(file.name)) { + if (/\.(js|ts|jsx|tsx|mjs|cjs|proto)$/.test(file.name)) { processFile(fullPath); } } diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts index 013f98132..3a433edbc 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts @@ -35,7 +35,7 @@ export class CentralCommandDispatcher { event: DuplexMessageEvent, handler: CommandHandler ): void { - console.log(`Registering handler for command type: ${event}`); + this.logger.infoMessage(`Registering handler for command type: ${event}`); this.handlers.set(event, handler); } diff --git a/Tokenization/backend/wrapper/src/proto/wrapper.proto b/Tokenization/backend/wrapper/src/proto/wrapper.proto index ad2cac80a..6688164da 100644 --- a/Tokenization/backend/wrapper/src/proto/wrapper.proto +++ b/Tokenization/backend/wrapper/src/proto/wrapper.proto @@ -10,7 +10,8 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ + syntax = "proto3"; package webui.tokenization; diff --git a/Tokenization/backend/wrapper/src/utils/types/webui.d.ts b/Tokenization/backend/wrapper/src/utils/types/webui.d.ts index 1bf1cc574..9432663e8 100644 --- a/Tokenization/backend/wrapper/src/utils/types/webui.d.ts +++ b/Tokenization/backend/wrapper/src/utils/types/webui.d.ts @@ -10,7 +10,7 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ declare module "@aliceo2/web-ui" { export const LogManager: { From 1fd05795fa99b7a4fa988f37ad330af5a41e563b Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Sat, 16 Aug 2025 13:32:11 +0200 Subject: [PATCH 044/103] fix: fix logging --- Tokenization/backend/wrapper/central/CentralSystem.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tokenization/backend/wrapper/central/CentralSystem.ts b/Tokenization/backend/wrapper/central/CentralSystem.ts index 350984751..f726203fb 100644 --- a/Tokenization/backend/wrapper/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/central/CentralSystem.ts @@ -67,7 +67,7 @@ export class CentralSystemWrapper { // Handle stream error event call.on("error", (err) => - this.logger.infoMessage( + this.logger.errorMessage( `Stream error from client ${call.getPeer()}:`, err ) @@ -84,7 +84,7 @@ export class CentralSystemWrapper { grpc.ServerCredentials.createInsecure(), (err, _port) => { if (err) { - this.logger.infoMessage("Server bind error:", err); + this.logger.errorMessage("Server bind error:", err); return; } this.logger.infoMessage(`CentralSytem started listening on ${addr}`); From 99530a8e5e48bd2fc1bc8478cbed1986e4fa79ee Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Sat, 16 Aug 2025 15:31:18 +0200 Subject: [PATCH 045/103] feat: change parameters to config objects --- .../backend/wrapper/central/CentralSystem.ts | 17 +++++++++-- .../backend/wrapper/client/gRPCWrapper.ts | 13 +++++++-- .../backend/wrapper/models/config.model.ts | 29 +++++++++++++++++++ 3 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 Tokenization/backend/wrapper/models/config.model.ts diff --git a/Tokenization/backend/wrapper/central/CentralSystem.ts b/Tokenization/backend/wrapper/central/CentralSystem.ts index f726203fb..060944614 100644 --- a/Tokenization/backend/wrapper/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/central/CentralSystem.ts @@ -2,6 +2,7 @@ import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; import path from "path"; import { LogManager } from "@aliceo2/web-ui"; +import { CentralSystemConfig } from "../models/config.model"; /** * @description Central System gRPC wrapper that manages client connections and handles gRPC streams with them. @@ -12,12 +13,18 @@ export class CentralSystemWrapper { // class properties private server: grpc.Server; + private protoPath: string; + private host: string; + private port: number; /** * Initializes the Wrapper for CentralSystem. * @param port The port number to bind the gRPC server to. */ - constructor(private protoPath: string, private port: number) { + constructor(config: CentralSystemConfig) { + this.protoPath = config.protoPath; + this.host = config.host || "0.0.0.0"; + this.port = config.port || 50051; this.server = new grpc.Server(); this.setupService(); } @@ -78,7 +85,7 @@ export class CentralSystemWrapper { * @desciprion Starts the gRPC server and binds it to the specified in class port. */ public listen() { - const addr = `localhost:${this.port}`; + const addr = `${this.host}:${this.port}`; this.server.bindAsync( addr, grpc.ServerCredentials.createInsecure(), @@ -95,6 +102,10 @@ export class CentralSystemWrapper { // Instantiate the CentralSystemWrapper on port 50051, but don't start automatically const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); -const centralSystem = new CentralSystemWrapper(PROTO_PATH, 50051); +const centralSystem = new CentralSystemWrapper({ + protoPath: PROTO_PATH, + host: "localhost", + port: 50051, +}); // Start listening explicitly centralSystem.listen(); diff --git a/Tokenization/backend/wrapper/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/client/gRPCWrapper.ts index d77b8fa33..a70a4765d 100644 --- a/Tokenization/backend/wrapper/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/client/gRPCWrapper.ts @@ -1,5 +1,6 @@ import path from "path"; import { ConnectionManager } from "./ConnectionManager/ConnectionManager.ts"; +import { gRPCWrapperConfig } from "../models/config.model.ts"; /** * @description Wrapper class for managing secure gRPC wrapper. @@ -24,8 +25,11 @@ export class gRPCWrapper { * @param protoPath - The file path to the gRPC proto definition. * @param centralAddress - The address of the central gRPC server (default: "localhost:50051"). */ - constructor(protoPath: string, centralAddress: string = "localhost:50051") { - this.ConnectionManager = new ConnectionManager(protoPath, centralAddress); + constructor(config: gRPCWrapperConfig) { + this.ConnectionManager = new ConnectionManager( + config.protoPath, + config.centralAddress || "localhost" + ); } /** @@ -37,5 +41,8 @@ export class gRPCWrapper { } const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); -const grpc = new gRPCWrapper(PROTO_PATH, "localhost:50051"); +const grpc = new gRPCWrapper({ + protoPath: PROTO_PATH, + centralAddress: "localhost:50051", +}); grpc.connectToCentralSystem(); diff --git a/Tokenization/backend/wrapper/models/config.model.ts b/Tokenization/backend/wrapper/models/config.model.ts new file mode 100644 index 000000000..8583bd987 --- /dev/null +++ b/Tokenization/backend/wrapper/models/config.model.ts @@ -0,0 +1,29 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +export interface CentralSystemConfig { + /** Path to the proto file defining the services. */ + protoPath: string; + /** Host/IP to bind the gRPC server on. Defaults to "0.0.0.0" which is docker-friendly. */ + host?: string; + /** Port to bind. Defaults to 50051. */ + port?: number; +} + +export interface gRPCWrapperConfig { + /** Path to the proto file defining the services. */ + protoPath: string; + /** Address of the CentralSystem server. Defaults to "localhost". */ + centralAddress: string; +} From 5efb5ca064726fe9d9f950603036e35c858362b9 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Sat, 16 Aug 2025 15:32:44 +0200 Subject: [PATCH 046/103] fix: fix deafult values in config --- Tokenization/backend/wrapper/client/gRPCWrapper.ts | 2 +- Tokenization/backend/wrapper/models/config.model.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tokenization/backend/wrapper/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/client/gRPCWrapper.ts index a70a4765d..b3aed93d7 100644 --- a/Tokenization/backend/wrapper/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/client/gRPCWrapper.ts @@ -28,7 +28,7 @@ export class gRPCWrapper { constructor(config: gRPCWrapperConfig) { this.ConnectionManager = new ConnectionManager( config.protoPath, - config.centralAddress || "localhost" + config.centralAddress ); } diff --git a/Tokenization/backend/wrapper/models/config.model.ts b/Tokenization/backend/wrapper/models/config.model.ts index 8583bd987..5f4c82a0f 100644 --- a/Tokenization/backend/wrapper/models/config.model.ts +++ b/Tokenization/backend/wrapper/models/config.model.ts @@ -24,6 +24,6 @@ export interface CentralSystemConfig { export interface gRPCWrapperConfig { /** Path to the proto file defining the services. */ protoPath: string; - /** Address of the CentralSystem server. Defaults to "localhost". */ + /** Address of the CentralSystem server. */ centralAddress: string; } From ec36841f5410b334548690d0fbd66823edf1e7be Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Sat, 16 Aug 2025 16:17:14 +0200 Subject: [PATCH 047/103] feat: add github actions for wrapper tests --- .github/workflows/grpc-wrapper.yml | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/grpc-wrapper.yml diff --git a/.github/workflows/grpc-wrapper.yml b/.github/workflows/grpc-wrapper.yml new file mode 100644 index 000000000..27efe6dfa --- /dev/null +++ b/.github/workflows/grpc-wrapper.yml @@ -0,0 +1,40 @@ +name: Grpc Wrapper + +on: + push: + branches: ["**"] + paths: + - "Tokenization/backend/wrapper/test/**" + - ".github/workflows/grpc-wrapper.yml" + pull_request: + branches: ["**"] + paths: + - "Tokenization/backend/wrapper/test/**" + - ".github/workflows/grpc-wrapper.yml" + +concurrency: + group: wrapper-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ubuntu-latest + + defaults: + run: + working-directory: Tokenization/backend/wrapper + + steps: + - uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "22.x" + cache: npm + cache-dependency-path: Tokenization/backend/wrapper/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run Jest + run: npm run test From 836ff51e7d116a0685e563bf776b23aebfebf7a4 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Sat, 16 Aug 2025 16:21:12 +0200 Subject: [PATCH 048/103] fix: paths --- .github/workflows/grpc-wrapper.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/grpc-wrapper.yml b/.github/workflows/grpc-wrapper.yml index 27efe6dfa..217034103 100644 --- a/.github/workflows/grpc-wrapper.yml +++ b/.github/workflows/grpc-wrapper.yml @@ -22,7 +22,7 @@ jobs: defaults: run: - working-directory: Tokenization/backend/wrapper + working-directory: "Tokenization/backend/wrapper" steps: - uses: actions/checkout@v4 @@ -31,7 +31,7 @@ jobs: with: node-version: "22.x" cache: npm - cache-dependency-path: Tokenization/backend/wrapper/package-lock.json + cache-dependency-path: "Tokenization/backend/wrapper/package-lock.json" - name: Install dependencies run: npm ci From c61ac97f8ccfea6ee8ff79db2a27127e60842d03 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Sat, 16 Aug 2025 16:25:57 +0200 Subject: [PATCH 049/103] fix: pathing --- .github/workflows/grpc-wrapper.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/grpc-wrapper.yml b/.github/workflows/grpc-wrapper.yml index 217034103..8b27019e2 100644 --- a/.github/workflows/grpc-wrapper.yml +++ b/.github/workflows/grpc-wrapper.yml @@ -1,15 +1,10 @@ name: Grpc Wrapper on: - push: - branches: ["**"] - paths: - - "Tokenization/backend/wrapper/test/**" - - ".github/workflows/grpc-wrapper.yml" pull_request: branches: ["**"] paths: - - "Tokenization/backend/wrapper/test/**" + - "Tokenization/backend/wrapper/**" - ".github/workflows/grpc-wrapper.yml" concurrency: @@ -22,7 +17,7 @@ jobs: defaults: run: - working-directory: "Tokenization/backend/wrapper" + working-directory: Tokenization/backend/wrapper steps: - uses: actions/checkout@v4 @@ -30,8 +25,6 @@ jobs: uses: actions/setup-node@v4 with: node-version: "22.x" - cache: npm - cache-dependency-path: "Tokenization/backend/wrapper/package-lock.json" - name: Install dependencies run: npm ci From ec8ea1a4fa7216ee04646ecb89b92614a10bad5b Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Sat, 16 Aug 2025 16:38:50 +0200 Subject: [PATCH 050/103] fix: fix tests --- .../src/test/central/CentralSystem.test.ts | 14 +++++++++----- .../src/test/client/ConnectionManager/index.ts | 18 +++++++++++------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts b/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts index 3b85e4c6e..82ad251ca 100644 --- a/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts +++ b/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts @@ -23,11 +23,15 @@ const logger = { infoMessage: jest.fn(), }; -jest.mock("@aliceo2/web-ui", () => ({ - LogManager: { - getLogger: () => logger, - }, -})); +jest.mock( + "@aliceo2/web-ui", + () => ({ + LogManager: { + getLogger: () => logger, + }, + }), + { virtual: true } +); jest.mock("@grpc/proto-loader", () => ({ loadSync: jest.fn(() => { diff --git a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts index f60739154..1da1fb06f 100644 --- a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts +++ b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts @@ -42,13 +42,17 @@ jest.mock( ); // Mock logger -jest.mock("@aliceo2/web-ui", () => ({ - LogManager: { - getLogger: () => ({ - infoMessage: jest.fn(), - }), - }, -})); +jest.mock( + "@aliceo2/web-ui", + () => ({ + LogManager: { + getLogger: () => ({ + infoMessage: jest.fn(), + }), + }, + }), + { virtual: true } +); // Mock gRPC proto loader and client jest.mock("@grpc/proto-loader", () => ({ From 6ac9f712412e77caf0b264567c23b35535a80b91 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Wed, 27 Aug 2025 14:41:37 +0200 Subject: [PATCH 051/103] feat: implement simple connection peer to peers --- Tokenization/backend/wrapper/.gitignore | 3 +- .../backend/wrapper/package-lock.json | 850 +++++++++++++++++- Tokenization/backend/wrapper/package.json | 4 +- .../wrapper/src/central/CentralSystem.ts | 39 +- .../Commands/newToken/newToken.handler.ts | 6 +- .../src/client/Connection/Connection.ts | 86 +- .../ConnectionManager/ConnectionManager.ts | 136 ++- .../backend/wrapper/src/client/gRPCWrapper.ts | 67 +- .../wrapper/src/models/connection.model.ts | 32 + .../backend/wrapper/src/proto/wrapper.proto | 32 + 10 files changed, 1187 insertions(+), 68 deletions(-) diff --git a/Tokenization/backend/wrapper/.gitignore b/Tokenization/backend/wrapper/.gitignore index 763301fc0..d90c70c7a 100644 --- a/Tokenization/backend/wrapper/.gitignore +++ b/Tokenization/backend/wrapper/.gitignore @@ -1,2 +1,3 @@ dist/ -node_modules/ \ No newline at end of file +node_modules/ +run_tests/ \ No newline at end of file diff --git a/Tokenization/backend/wrapper/package-lock.json b/Tokenization/backend/wrapper/package-lock.json index 1a24c0038..cd3e9595a 100644 --- a/Tokenization/backend/wrapper/package-lock.json +++ b/Tokenization/backend/wrapper/package-lock.json @@ -10,10 +10,12 @@ "dependencies": { "@grpc/grpc-js": "^1.13.4", "@grpc/proto-loader": "^0.7.15", + "express": "^5.1.0", "ts-node": "^10.9.2", "typescript": "^5.8.3" }, "devDependencies": { + "@types/express": "^5.0.3", "@types/jest": "^29.5.14", "jest": "^29.7.0", "ts-jest": "^29.4.0", @@ -1151,6 +1153,52 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", + "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", + "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -1161,6 +1209,13 @@ "@types/node": "*" } }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -1199,6 +1254,13 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.0.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.0.tgz", @@ -1208,6 +1270,43 @@ "undici-types": "~7.8.0" } }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -1232,6 +1331,19 @@ "dev": true, "license": "MIT" }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -1479,6 +1591,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1566,6 +1698,44 @@ "dev": true, "license": "MIT" }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1749,6 +1919,27 @@ "dev": true, "license": "MIT" }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1756,6 +1947,24 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -1803,7 +2012,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1842,6 +2050,15 @@ "node": ">=0.10.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -1884,6 +2101,26 @@ "node": ">=8" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -1926,6 +2163,15 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -1936,6 +2182,36 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1945,6 +2221,12 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -1959,6 +2241,15 @@ "node": ">=4" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -2009,6 +2300,48 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -2099,6 +2432,23 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -2113,6 +2463,24 @@ "node": ">=8" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2139,7 +2507,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2164,6 +2531,30 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -2174,6 +2565,19 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -2266,6 +2670,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2283,11 +2699,22 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -2303,6 +2730,31 @@ "dev": true, "license": "MIT" }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -2313,6 +2765,18 @@ "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==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2369,9 +2833,17 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -2460,6 +2932,12 @@ "node": ">=0.12.0" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -3347,6 +3825,36 @@ "tmpl": "1.0.5" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -3378,6 +3886,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.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", @@ -3405,7 +3934,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/mylas": { @@ -3429,6 +3957,15 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -3466,11 +4003,34 @@ "node": ">=8" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -3566,6 +4126,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3603,6 +4172,15 @@ "dev": true, "license": "MIT" }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -3735,6 +4313,19 @@ "node": ">=12.0.0" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -3752,6 +4343,21 @@ ], "license": "MIT" }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-lit": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", @@ -3783,6 +4389,30 @@ ], "license": "MIT" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -3887,6 +4517,22 @@ "node": ">=0.10.0" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3911,6 +4557,32 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -3921,6 +4593,49 @@ "semver": "bin/semver.js" } }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3944,6 +4659,78 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -4019,6 +4806,15 @@ "node": ">=8" } }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -4153,6 +4949,15 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/ts-jest": { "version": "29.4.0", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.0.tgz", @@ -4320,6 +5125,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -4339,6 +5158,15 @@ "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "license": "MIT" }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -4391,6 +5219,15 @@ "node": ">=10.12.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -4438,7 +5275,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { diff --git a/Tokenization/backend/wrapper/package.json b/Tokenization/backend/wrapper/package.json index 0c420b84c..e099eeaab 100644 --- a/Tokenization/backend/wrapper/package.json +++ b/Tokenization/backend/wrapper/package.json @@ -5,12 +5,11 @@ "scripts": { "test": "jest", "build": "tsc -p tsconfig.build.json && cp -r src/proto dist", - "client": "node dist/client/gRPCWrapper.js", - "central": "node dist/central/CentralSystem.js", "process-banners": "node scripts/banner.js" }, "author": "ALICEO2", "devDependencies": { + "@types/express": "^5.0.3", "@types/jest": "^29.5.14", "jest": "^29.7.0", "ts-jest": "^29.4.0", @@ -19,6 +18,7 @@ "dependencies": { "@grpc/grpc-js": "^1.13.4", "@grpc/proto-loader": "^0.7.15", + "express": "^5.1.0", "ts-node": "^10.9.2", "typescript": "^5.8.3" } diff --git a/Tokenization/backend/wrapper/src/central/CentralSystem.ts b/Tokenization/backend/wrapper/src/central/CentralSystem.ts index 58d938eed..5c5da23d4 100644 --- a/Tokenization/backend/wrapper/src/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/src/central/CentralSystem.ts @@ -14,13 +14,8 @@ import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; -import path from "path"; import { LogManager } from "@aliceo2/web-ui"; -import { - ConnectionDirection, - DuplexMessageEvent, - DuplexMessageModel, -} from "../models/message.model"; +import { DuplexMessageModel } from "../models/message.model"; /** * @description Central System gRPC wrapper that manages client connections and handles gRPC streams with them. @@ -185,35 +180,3 @@ export class CentralSystemWrapper { ); } } - -// Instantiate the CentralSystemWrapper on port 50051, but don't start automatically -const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); -const centralSystem = new CentralSystemWrapper(PROTO_PATH, 50051); -// Start listening explicitly -centralSystem.listen(); - -setTimeout(() => { - centralSystem.sendEvent(centralSystem.getConnectedClients()[0], { - event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, - payload: { - connectionDirection: ConnectionDirection.SENDING, - targetAddress: "a", - }, - }); - centralSystem.sendEvent(centralSystem.getConnectedClients()[0], { - event: DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN, - payload: { - connectionDirection: ConnectionDirection.SENDING, - targetAddress: "a", - token: "newToken", - }, - }); - centralSystem.sendEvent(centralSystem.getConnectedClients()[0], { - event: DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN, - payload: { - connectionDirection: ConnectionDirection.SENDING, - targetAddress: "c", - token: "tokenForNewAddress", - }, - }); -}, 5000); diff --git a/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts b/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts index cac108af3..feade4424 100644 --- a/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts +++ b/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts @@ -49,7 +49,11 @@ export class NewTokenHandler implements CommandHandler { for (const dir of directions) { let conn = this.manager.getConnectionByAddress(targetAddress, dir); if (!conn) { - conn = this.manager.createNewConnection(targetAddress, dir, token); + conn = await this.manager.createNewConnection( + targetAddress, + dir, + token + ); } conn.handleNewToken(token); } diff --git a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts index c0e4aa71f..54f0e075f 100644 --- a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts +++ b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts @@ -13,15 +13,24 @@ */ import { ConnectionDirection } from "../../models/message.model"; -import { ConnectionStatus } from "../../models/connection.model"; +import { + ConnectionHeaders, + ConnectionStatus, + FetchOptions, + FetchResponse, +} from "../../models/connection.model"; +import * as grpc from "@grpc/grpc-js"; /** * @description This class represents a connection to a target client and manages sending messages to it. */ export class Connection { private token: string; - private targetAddress: string; private status: ConnectionStatus; + private peerClient?: any; // a client grpc connection instance + + public targetAddress: string; + public direction: ConnectionDirection; /** * @description Creates a new Connection instance with the given token, target address, and connection direction. @@ -33,10 +42,17 @@ export class Connection { constructor( token: string, targetAddress: string, - public direction: ConnectionDirection + direction: ConnectionDirection, + peerCtor: any ) { this.token = token; this.targetAddress = targetAddress; + this.direction = direction; + + this.peerClient = new peerCtor( + targetAddress, + grpc.credentials.createInsecure() + ); this.status = ConnectionStatus.CONNECTED; } @@ -73,6 +89,14 @@ export class Connection { return this.status; } + /** + * @description Updates the status of the connection. + * @param status New status + */ + public updateStatus(status: ConnectionStatus): void { + this.status = status; + } + /** * @description Returns target address for this Connection object * @returns Target address @@ -80,4 +104,60 @@ export class Connection { public getTargetAddress(): string { return this.targetAddress; } + + /** + * @description Attaches gRPC client to that connection + */ + public attachGrpcClient(client: any): void { + this.peerClient = client; + } + + /** + * @description "HTTP-like" fetch via gRPC protocol + * @returns Promise with peer's response + */ + public fetch(options: FetchOptions = {}): Promise { + if (!this.peerClient) { + return Promise.reject( + new Error(`Peer client not attached for ${this.getTargetAddress()}`) + ); + } + + // build a request object + const method = (options.method || "POST").toUpperCase(); + const path = options.path || "/"; + const headers: ConnectionHeaders = { ...(options.headers || {}) }; + + let bodyBuf: Buffer = Buffer.alloc(0); + const b = options.body; + if (b != null) { + if (Buffer.isBuffer(b)) bodyBuf = b; + else if (b instanceof Uint8Array) bodyBuf = Buffer.from(b); + else if (typeof b === "string") bodyBuf = Buffer.from(b, "utf8"); + else + return Promise.reject( + new Error("Body must be a string/Buffer/Uint8Array") + ); + } + + const req = { method, path, headers, body: bodyBuf }; + + // return promise with response + return new Promise((resolve, reject) => { + this.peerClient.Fetch(req, (err: any, resp: any) => { + if (err) return reject(err); + + const resBody = resp?.body ? Buffer.from(resp.body) : Buffer.alloc(0); + const fetchResponse: FetchResponse = { + status: Number(resp?.status ?? 200), + headers: resp?.headers || {}, + body: resBody, + text: async () => resBody.toString("utf8"), + json: async () => JSON.parse(resBody.toString("utf8")), + }; + + resolve(fetchResponse); + }); + }); + } } diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts index 34e73cd74..807ae4088 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts @@ -23,6 +23,7 @@ import { ConnectionDirection, DuplexMessageEvent, } from "../../models/message.model"; +import { ConnectionStatus } from "../../models/connection.model"; /** * @description Manages all the connection between clients and central system. @@ -43,10 +44,16 @@ import { */ export class ConnectionManager { private logger = LogManager.getLogger("ConnectionManager"); + private wrapper: any; // gRPC wrapper file + private centralDispatcher: CentralCommandDispatcher; private centralConnection: CentralConnection; private sendingConnections = new Map(); + private receivingConnections = new Map(); + private peerCtor: any; // p2p gRPC constructor + private peerServer?: grpc.Server; + private baseAPIPath: string = "localhost:40041/api/"; /** * @description Initializes a new instance of the ConnectionManager class. @@ -66,9 +73,10 @@ export class ConnectionManager { }); const proto = grpc.loadPackageDefinition(packageDef) as any; - const wrapper = proto.webui.tokenization; + this.wrapper = proto.webui.tokenization; + this.peerCtor = this.wrapper.Peer2Peer; - const client = new wrapper.CentralSystem( + const centralClient = new this.wrapper.CentralSystem( centralAddress, grpc.credentials.createInsecure() ); @@ -76,17 +84,17 @@ export class ConnectionManager { // event dispatcher for central system events this.centralDispatcher = new CentralCommandDispatcher(); this.centralConnection = new CentralConnection( - client, + centralClient, this.centralDispatcher ); this.sendingConnections.set( "a", - new Connection("1", "a", ConnectionDirection.SENDING) + new Connection("1", "a", ConnectionDirection.SENDING, this.peerCtor) ); this.sendingConnections.set( "b", - new Connection("2", "b", ConnectionDirection.SENDING) + new Connection("2", "b", ConnectionDirection.SENDING, this.peerCtor) ); } @@ -125,18 +133,40 @@ export class ConnectionManager { * @param direction Direction of connection * @param token Optional token for connection */ - createNewConnection( + public async createNewConnection( address: string, direction: ConnectionDirection, token?: string ) { - const conn = new Connection(token || "", address, direction); + let conn: Connection | undefined; + + // Checks if connection already exists + conn = + direction === ConnectionDirection.RECEIVING + ? this.receivingConnections.get(address) + : this.sendingConnections.get(address); + + // Return existing connection if found + if (conn) { + if (token) { + conn.handleNewToken(token); + } + return conn; + } + + // Create new connection + conn = new Connection(token || "", address, direction, this.peerCtor); + conn.updateStatus(ConnectionStatus.CONNECTING); if (direction === ConnectionDirection.RECEIVING) { this.receivingConnections.set(address, conn); } else { this.sendingConnections.set(address, conn); } + conn.updateStatus(ConnectionStatus.CONNECTED); + this.logger.infoMessage( + `Connection with ${address} has been estabilished. Status: ${conn.getStatus()}` + ); return conn; } @@ -173,4 +203,96 @@ export class ConnectionManager { receiving: [...this.receivingConnections.values()], }; } + + /** Starts a listener server for p2p connections */ + public async listenForPeers( + port: number, + baseAPIPath?: string + ): Promise { + if (baseAPIPath) this.baseAPIPath = baseAPIPath; + + if (this.peerServer) { + this.peerServer.forceShutdown(); + this.peerServer = undefined; + } + + this.peerServer = new grpc.Server(); + this.peerServer.addService(this.wrapper.Peer2Peer.service, { + Fetch: async ( + call: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData + ) => { + try { + const clientAddress = call.getPeer(); + this.logger.infoMessage(`Incoming request from ${clientAddress}`); + + let conn: Connection | undefined = + this.receivingConnections.get(clientAddress); + + if (!conn) { + conn = new Connection( + "", + clientAddress, + ConnectionDirection.RECEIVING, + this.peerCtor + ); + conn.updateStatus(ConnectionStatus.CONNECTED); + this.receivingConnections.set(clientAddress, conn); + this.logger.infoMessage( + `New incoming connection registered for: ${clientAddress}` + ); + } + + // create request to forward to local API endpoint + const method = String(call.request?.method || "POST").toUpperCase(); + const url = this.baseAPIPath + (call.request?.path || ""); + const headers: { [key: string]: string } = call.request?.headers; + const body = call.request?.body + ? Buffer.from(call.request.body).toString("utf-8") + : undefined; + + this.logger.infoMessage( + `Received payload from ${clientAddress}: \n${url}\n${JSON.stringify( + headers + )}\n${JSON.stringify(body)}\n` + ); + + const httpResp = await fetch(url, { + method, + headers: headers, + body, + }); + + const respHeaders: Record = {}; + httpResp.headers.forEach((v, k) => (respHeaders[k] = v)); + const resBody = Buffer.from(await httpResp.arrayBuffer()); + + callback(null, { + status: httpResp.status, + headers: respHeaders, + body: resBody, + }); + } catch (e: any) { + this.logger.errorMessage( + `Error forwarding request: ${e ?? "Uknown error"}` + ); + + callback({ + code: grpc.status.INTERNAL, + message: e?.message ?? "forward error", + } as any); + } + }, + }); + + await new Promise((resolve, reject) => { + this.peerServer!.bindAsync( + `localhost:${port}`, + grpc.ServerCredentials.createInsecure(), + (err) => (err ? reject(err) : resolve()) + ); + }); + + this.logger.infoMessage(`Peer server listening on localhost:${port}`); + } } diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index 48000d61f..05eca013e 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -15,7 +15,10 @@ import path from "path"; import { ConnectionManager } from "./ConnectionManager/ConnectionManager"; import { RevokeTokenHandler } from "./Commands/revokeToken/revokeToken.handler"; -import { DuplexMessageEvent } from "../models/message.model"; +import { + ConnectionDirection, + DuplexMessageEvent, +} from "../models/message.model"; import { Connection } from "./Connection/Connection"; import { NewTokenHandler } from "./Commands/newToken/newToken.handler"; @@ -63,6 +66,30 @@ export class gRPCWrapper { this.ConnectionManager.connectToCentralSystem(); } + /** + * @description Starts the Connection Manager stream connection with Central System + */ + public async connectToClient( + address: string, + token?: string + ): Promise { + return this.ConnectionManager.createNewConnection( + address, + ConnectionDirection.SENDING, + token || "" + ); + } + + /** + * @description Starts the Connection Manager stream connection with Central System + */ + public async listenForPeers( + port: number, + baseAPIPath?: string + ): Promise { + return this.ConnectionManager.listenForPeers(port, baseAPIPath); + } + /** * @description Returns all saved connections. * @@ -104,12 +131,34 @@ export class gRPCWrapper { } } -const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); -const grpc = new gRPCWrapper(PROTO_PATH, "localhost:50051"); -grpc.connectToCentralSystem(); -console.log(grpc.getSummary()); +// const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); +// const grpc = new gRPCWrapper(PROTO_PATH, "localhost:50051"); +// grpc.connectToCentralSystem(); +// console.log(grpc.getSummary()); + +// setTimeout(() => { +// console.log("New status after 10 seconds, token revokation and new token:"); +// console.log(grpc.getSummary()); +// }, 10000); + +// grpc.connectToCentralSystem(); + +// const conn1: Connection = grpc.connectToClient("localhost:40001"); -setTimeout(() => { - console.log("New status after 10 seconds, token revokation and new token:"); - console.log(grpc.getSummary()); -}, 10000); +// // wrapping request +// conn1 +// .fetch({ +// method: "POST", +// headers: { +// "Content-Type": "application/json", +// Authorization: "bearer someToken", +// }, +// body: JSON.stringify({ +// name: "Jan Kowalski", +// email: "jan.kowalski@example.com", +// age: 28, +// }), +// }) +// .then((response) => response.json()) +// .then((data) => console.log("Response:", data)) +// .catch((error) => console.error("Error:", error)); diff --git a/Tokenization/backend/wrapper/src/models/connection.model.ts b/Tokenization/backend/wrapper/src/models/connection.model.ts index b57432958..f18c24fa4 100644 --- a/Tokenization/backend/wrapper/src/models/connection.model.ts +++ b/Tokenization/backend/wrapper/src/models/connection.model.ts @@ -28,3 +28,35 @@ export enum ConnectionStatus { // The connection is refreshing its authentication token TOKEN_REFRESH = "TOKEN_REFRESH", } + +export type ConnectionHeaders = Record; + +export type FetchOptions = { + method?: string; + path?: string; + headers?: ConnectionHeaders; + body?: string | Buffer | Uint8Array | null; +}; + +export type FetchResponse = { + status: number; + headers: ConnectionHeaders; + body: Buffer; + text: () => Promise; + json: () => Promise; +}; + +export type HttpLikeRequest = { + method: string; + path: string; + headers: Headers; + body: Buffer; + correlation_id?: string; + sequence_number?: number; +}; + +export type HttpLikeResponse = { + status: number; + headers: Headers; + body: Buffer; +}; diff --git a/Tokenization/backend/wrapper/src/proto/wrapper.proto b/Tokenization/backend/wrapper/src/proto/wrapper.proto index 6688164da..abe296b59 100644 --- a/Tokenization/backend/wrapper/src/proto/wrapper.proto +++ b/Tokenization/backend/wrapper/src/proto/wrapper.proto @@ -25,6 +25,12 @@ service CentralSystem { rpc ClientStream(stream Payload) returns (stream Payload); } +// Peer2Peer service handling HTTP-like requests between wrapper clients +service Peer2Peer { + rpc Fetch (HttpLikeRequest) returns (HttpLikeResponse); +} + + // ====================================== // MESSAGES // ====================================== @@ -51,6 +57,32 @@ message Payload { } } +// http method enum +enum HttpMethod { + HTTP_METHOD_UNSPECIFIED = 0; + GET = 1; + POST = 2; + PUT = 3; + PATCH = 4; + DELETE = 5; + HEAD = 6; + OPTIONS = 7; +} + +message HttpLikeRequest { + HttpMethod method = 1; // GET/POST/... + string path = 2; // request path e.g. "/orders/add" + map headers = 3; // "content-type": "application/json" + bytes body = 4; // body (e.g. JSON) +} + +message HttpLikeResponse { + int32 status = 1; + map headers = 2; + bytes body = 3; +} + + // ====================================== // ENUMS // ====================================== From 5b759d80ada2058960a1cd0039e871eb59da1ca0 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 28 Aug 2025 21:46:07 +0200 Subject: [PATCH 052/103] fix: refactor ConnectionManager and remove unnecessary code. --- Tokenization/backend/wrapper/.gitignore | 3 + ...ntralSystem.ts => CentralSystemWrapper.ts} | 15 +--- .../ConnectionManager/ConnectionManager.ts | 2 +- .../backend/wrapper/client/gRPCWrapper.ts | 7 -- ...m.test.ts => CentralSystemWrapper.test.ts} | 7 +- .../test/utils/serialization.utils.test.ts | 71 ------------------- .../wrapper/utils/serialization.utils.ts | 68 ------------------ 7 files changed, 11 insertions(+), 162 deletions(-) create mode 100644 Tokenization/backend/wrapper/.gitignore rename Tokenization/backend/wrapper/central/{CentralSystem.ts => CentralSystemWrapper.ts} (86%) rename Tokenization/backend/wrapper/test/central/{CentralSystem.test.ts => CentralSystemWrapper.test.ts} (96%) delete mode 100644 Tokenization/backend/wrapper/test/utils/serialization.utils.test.ts delete mode 100644 Tokenization/backend/wrapper/utils/serialization.utils.ts diff --git a/Tokenization/backend/wrapper/.gitignore b/Tokenization/backend/wrapper/.gitignore new file mode 100644 index 000000000..d90c70c7a --- /dev/null +++ b/Tokenization/backend/wrapper/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +run_tests/ \ No newline at end of file diff --git a/Tokenization/backend/wrapper/central/CentralSystem.ts b/Tokenization/backend/wrapper/central/CentralSystemWrapper.ts similarity index 86% rename from Tokenization/backend/wrapper/central/CentralSystem.ts rename to Tokenization/backend/wrapper/central/CentralSystemWrapper.ts index 060944614..353450633 100644 --- a/Tokenization/backend/wrapper/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/central/CentralSystemWrapper.ts @@ -75,8 +75,7 @@ export class CentralSystemWrapper { // Handle stream error event call.on("error", (err) => this.logger.errorMessage( - `Stream error from client ${call.getPeer()}:`, - err + `Stream error from client ${call.getPeer()}: ${err}` ) ); } @@ -91,7 +90,7 @@ export class CentralSystemWrapper { grpc.ServerCredentials.createInsecure(), (err, _port) => { if (err) { - this.logger.errorMessage("Server bind error:", err); + this.logger.errorMessage(`Server bind error: ${err}`); return; } this.logger.infoMessage(`CentralSytem started listening on ${addr}`); @@ -99,13 +98,3 @@ export class CentralSystemWrapper { ); } } - -// Instantiate the CentralSystemWrapper on port 50051, but don't start automatically -const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); -const centralSystem = new CentralSystemWrapper({ - protoPath: PROTO_PATH, - host: "localhost", - port: 50051, -}); -// Start listening explicitly -centralSystem.listen(); diff --git a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts index ed6ddb48b..d37586c9c 100644 --- a/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/client/ConnectionManager/ConnectionManager.ts @@ -122,8 +122,8 @@ export class ConnectionManager { if (this.stream) { this.stream.end(); this.stream = undefined; + this.logger.infoMessage(`Disconnected from CentralSystem service`); } this.reconnectAttempts = 0; - this.logger.infoMessage(`Disconnected from CentralSystem service`); } } diff --git a/Tokenization/backend/wrapper/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/client/gRPCWrapper.ts index b3aed93d7..7a4b8e796 100644 --- a/Tokenization/backend/wrapper/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/client/gRPCWrapper.ts @@ -39,10 +39,3 @@ export class gRPCWrapper { this.ConnectionManager.connectToCentralSystem(); } } - -const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); -const grpc = new gRPCWrapper({ - protoPath: PROTO_PATH, - centralAddress: "localhost:50051", -}); -grpc.connectToCentralSystem(); diff --git a/Tokenization/backend/wrapper/test/central/CentralSystem.test.ts b/Tokenization/backend/wrapper/test/central/CentralSystemWrapper.test.ts similarity index 96% rename from Tokenization/backend/wrapper/test/central/CentralSystem.test.ts rename to Tokenization/backend/wrapper/test/central/CentralSystemWrapper.test.ts index 1599e0ccd..f40da83f6 100644 --- a/Tokenization/backend/wrapper/test/central/CentralSystem.test.ts +++ b/Tokenization/backend/wrapper/test/central/CentralSystemWrapper.test.ts @@ -41,7 +41,7 @@ jest.mock("@grpc/grpc-js", () => { }; }); -import { CentralSystemWrapper } from "../../central/CentralSystem"; +import { CentralSystemWrapper } from "../../central/CentralSystemWrapper"; import * as grpc from "@grpc/grpc-js"; describe("CentralSystemWrapper", () => { @@ -49,7 +49,10 @@ describe("CentralSystemWrapper", () => { beforeEach(() => { jest.clearAllMocks(); - wrapper = new CentralSystemWrapper("dummy.proto", 12345); + wrapper = new CentralSystemWrapper({ + protoPath: "dummy.proto", + port: 12345, + }); }); test("should set up gRPC service and add it to the server", () => { diff --git a/Tokenization/backend/wrapper/test/utils/serialization.utils.test.ts b/Tokenization/backend/wrapper/test/utils/serialization.utils.test.ts deleted file mode 100644 index 93d5c7d7c..000000000 --- a/Tokenization/backend/wrapper/test/utils/serialization.utils.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -import { - deserializeRequest, - serializeRequest, -} from "../../utils/serialization.utils"; -import { describe, expect, test } from "@jest/globals"; - -describe("serializeRequest", () => { - test("serializes URL and options correctly", () => { - const url = "/api/test"; - const options = { - method: "POST", - headers: { "Content-Type": "application/json" }, - }; - - const buffer = serializeRequest(url, options); - const view = new Uint8Array(buffer); - - const typeLength = view[0]; - const typeBytes = view.slice(1, 1 + typeLength); - const jsonBytes = view.slice(1 + typeLength); - - const contentType = new TextDecoder().decode(typeBytes); - const json = JSON.parse(new TextDecoder().decode(jsonBytes)); - - expect(contentType).toBe("application/json"); - expect(json.url).toBe(url); - expect(json.options.method).toBe(options.method); - }); -}); - -describe("deserializeRequest", () => { - test("deserializes payload into correct request object", () => { - const url = "/api/test"; - const options = { method: "GET" }; - - const buffer = serializeRequest(url, options); - const result = deserializeRequest(buffer); - - expect(result.url).toBe(url); - expect(result.options.method).toBe("GET"); - }); - - test("throws error on unsupported content type", () => { - const encoder = new TextEncoder(); - const badType = encoder.encode("text/plain"); - const json = encoder.encode(JSON.stringify({ url: "/x" })); - - const buffer = new Uint8Array(1 + badType.length + json.length); - buffer[0] = badType.length; - buffer.set(badType, 1); - buffer.set(json, 1 + badType.length); - - expect(() => { - deserializeRequest(buffer.buffer); - }).toThrow("Unsupported content type: text/plain"); - }); -}); diff --git a/Tokenization/backend/wrapper/utils/serialization.utils.ts b/Tokenization/backend/wrapper/utils/serialization.utils.ts deleted file mode 100644 index 57ca8bcd2..000000000 --- a/Tokenization/backend/wrapper/utils/serialization.utils.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -/** - * @description Serializes Json formatted request into binary payload with specific endpoint - * @param url - The endpoint URL to which the request is made - * @param options - Request options, such as headers or body - * @return {ArrayBuffer} - The serialized binary payload containing the URL and options - */ -export const serializeRequest = (url: string, options: any): ArrayBuffer => { - const encoder = new TextEncoder(); - const contentTypeBytes = encoder.encode("application/json"); - - // build JSON data - const jsonData = { - url: url, - options: options, - }; - - const jsonString = JSON.stringify(jsonData); - const jsonBytes = encoder.encode(jsonString); - - // Buffer following structure: - // 1 byte -> type length - // N bytes -> types - // rest -> JSON data - const buffer = new Uint8Array(1 + contentTypeBytes.length + jsonBytes.length); - - buffer[0] = contentTypeBytes.length; - buffer.set(contentTypeBytes, 1); - buffer.set(jsonBytes, 1 + contentTypeBytes.length); - - return buffer.buffer; -}; - -/** - * @description Deserializes binary payload to Json formated request - * @param payload - The binary payload to deserialize - * @return {any} - The deserialized request object containing the URL and options - */ -export const deserializeRequest = (payload: ArrayBuffer): any => { - const view = new Uint8Array(payload); - const decoder = new TextDecoder(); - - const contentTypeLength = view[0]; - const contentTypeBytes = view.slice(1, 1 + contentTypeLength); - const contentType = decoder.decode(contentTypeBytes); - - const dataBytes = view.slice(1 + contentTypeLength); - - // deserialization of JSON content - if (contentType === "application/json") { - return JSON.parse(decoder.decode(dataBytes)); - } else { - throw new Error(`Unsupported content type: ${contentType}`); - } -}; From d43912a823a9824b0f9d9ad2f93420e9e36a60dd Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 28 Aug 2025 21:54:24 +0200 Subject: [PATCH 053/103] fix: remove unused imports --- Tokenization/backend/wrapper/central/CentralSystemWrapper.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/Tokenization/backend/wrapper/central/CentralSystemWrapper.ts b/Tokenization/backend/wrapper/central/CentralSystemWrapper.ts index 353450633..2e99499f2 100644 --- a/Tokenization/backend/wrapper/central/CentralSystemWrapper.ts +++ b/Tokenization/backend/wrapper/central/CentralSystemWrapper.ts @@ -1,6 +1,5 @@ import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; -import path from "path"; import { LogManager } from "@aliceo2/web-ui"; import { CentralSystemConfig } from "../models/config.model"; From 6ba86f253a33d44073ef5f20569c85f106520a06 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Sun, 31 Aug 2025 12:14:16 +0200 Subject: [PATCH 054/103] fix: remove unnecessary utils --- .../test/utils/serialization.utils.test.ts | 71 ------------------- .../wrapper/src/utils/serialization.utils.ts | 68 ------------------ 2 files changed, 139 deletions(-) delete mode 100644 Tokenization/backend/wrapper/src/test/utils/serialization.utils.test.ts delete mode 100644 Tokenization/backend/wrapper/src/utils/serialization.utils.ts diff --git a/Tokenization/backend/wrapper/src/test/utils/serialization.utils.test.ts b/Tokenization/backend/wrapper/src/test/utils/serialization.utils.test.ts deleted file mode 100644 index 93d5c7d7c..000000000 --- a/Tokenization/backend/wrapper/src/test/utils/serialization.utils.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -import { - deserializeRequest, - serializeRequest, -} from "../../utils/serialization.utils"; -import { describe, expect, test } from "@jest/globals"; - -describe("serializeRequest", () => { - test("serializes URL and options correctly", () => { - const url = "/api/test"; - const options = { - method: "POST", - headers: { "Content-Type": "application/json" }, - }; - - const buffer = serializeRequest(url, options); - const view = new Uint8Array(buffer); - - const typeLength = view[0]; - const typeBytes = view.slice(1, 1 + typeLength); - const jsonBytes = view.slice(1 + typeLength); - - const contentType = new TextDecoder().decode(typeBytes); - const json = JSON.parse(new TextDecoder().decode(jsonBytes)); - - expect(contentType).toBe("application/json"); - expect(json.url).toBe(url); - expect(json.options.method).toBe(options.method); - }); -}); - -describe("deserializeRequest", () => { - test("deserializes payload into correct request object", () => { - const url = "/api/test"; - const options = { method: "GET" }; - - const buffer = serializeRequest(url, options); - const result = deserializeRequest(buffer); - - expect(result.url).toBe(url); - expect(result.options.method).toBe("GET"); - }); - - test("throws error on unsupported content type", () => { - const encoder = new TextEncoder(); - const badType = encoder.encode("text/plain"); - const json = encoder.encode(JSON.stringify({ url: "/x" })); - - const buffer = new Uint8Array(1 + badType.length + json.length); - buffer[0] = badType.length; - buffer.set(badType, 1); - buffer.set(json, 1 + badType.length); - - expect(() => { - deserializeRequest(buffer.buffer); - }).toThrow("Unsupported content type: text/plain"); - }); -}); diff --git a/Tokenization/backend/wrapper/src/utils/serialization.utils.ts b/Tokenization/backend/wrapper/src/utils/serialization.utils.ts deleted file mode 100644 index 57ca8bcd2..000000000 --- a/Tokenization/backend/wrapper/src/utils/serialization.utils.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -/** - * @description Serializes Json formatted request into binary payload with specific endpoint - * @param url - The endpoint URL to which the request is made - * @param options - Request options, such as headers or body - * @return {ArrayBuffer} - The serialized binary payload containing the URL and options - */ -export const serializeRequest = (url: string, options: any): ArrayBuffer => { - const encoder = new TextEncoder(); - const contentTypeBytes = encoder.encode("application/json"); - - // build JSON data - const jsonData = { - url: url, - options: options, - }; - - const jsonString = JSON.stringify(jsonData); - const jsonBytes = encoder.encode(jsonString); - - // Buffer following structure: - // 1 byte -> type length - // N bytes -> types - // rest -> JSON data - const buffer = new Uint8Array(1 + contentTypeBytes.length + jsonBytes.length); - - buffer[0] = contentTypeBytes.length; - buffer.set(contentTypeBytes, 1); - buffer.set(jsonBytes, 1 + contentTypeBytes.length); - - return buffer.buffer; -}; - -/** - * @description Deserializes binary payload to Json formated request - * @param payload - The binary payload to deserialize - * @return {any} - The deserialized request object containing the URL and options - */ -export const deserializeRequest = (payload: ArrayBuffer): any => { - const view = new Uint8Array(payload); - const decoder = new TextDecoder(); - - const contentTypeLength = view[0]; - const contentTypeBytes = view.slice(1, 1 + contentTypeLength); - const contentType = decoder.decode(contentTypeBytes); - - const dataBytes = view.slice(1 + contentTypeLength); - - // deserialization of JSON content - if (contentType === "application/json") { - return JSON.parse(decoder.decode(dataBytes)); - } else { - throw new Error(`Unsupported content type: ${contentType}`); - } -}; From 2569c5c8fa2dc16c0cde4c650e4bf05e98df7ae0 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Sun, 31 Aug 2025 12:15:49 +0200 Subject: [PATCH 055/103] fix: remove unused variable --- Tokenization/backend/wrapper/src/client/gRPCWrapper.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index 05eca013e..9b7835474 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -12,7 +12,6 @@ * or submit itself to any jurisdiction. */ -import path from "path"; import { ConnectionManager } from "./ConnectionManager/ConnectionManager"; import { RevokeTokenHandler } from "./Commands/revokeToken/revokeToken.handler"; import { From 21fb63e48d99902df30faf75622756f26e1f98fc Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Sun, 31 Aug 2025 12:24:08 +0200 Subject: [PATCH 056/103] fix: refactor unit tests --- .../src/test/client/Commands/newToken.test.ts | 28 ++++++++++++++++-- .../test/client/Commands/revokeToken.test.ts | 29 +++++++++++++++++-- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts b/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts index 91f109204..829dff294 100644 --- a/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts @@ -21,6 +21,9 @@ import { ConnectionDirection, DuplexMessageEvent, } from "../../../models/message.model"; +import * as grpc from "@grpc/grpc-js"; +import * as protoLoader from "@grpc/proto-loader"; +import path from "path"; /** * Helper to create a new token command with given address, direction, and token. @@ -42,6 +45,26 @@ function createEventMessage( describe("NewTokenHandler", () => { let manager: ConnectionManager; + const protoPath = path.join( + __dirname, + "..", + "..", + "..", + "proto", + "wrapper.proto" + ); + const packageDef = protoLoader.loadSync(protoPath, { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + }); + + const proto = grpc.loadPackageDefinition(packageDef) as any; + const wrapper = proto.webui.tokenization; + const peerCtor = wrapper.Peer2Peer; + beforeEach(() => { manager = { sendingConnections: new Map(), @@ -64,7 +87,7 @@ describe("NewTokenHandler", () => { dir: ConnectionDirection, token: string ) { - const conn = new Connection(token, address, dir); + const conn = new Connection(token, address, dir, peerCtor); if (dir === ConnectionDirection.SENDING) { this.sendingConnections.set(address, conn); } else { @@ -80,7 +103,8 @@ describe("NewTokenHandler", () => { const conn = new Connection( "old-token", targetAddress, - ConnectionDirection.SENDING + ConnectionDirection.SENDING, + peerCtor ); (manager as any).sendingConnections.set(targetAddress, conn); diff --git a/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts b/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts index 2602c5173..2eb821e90 100644 --- a/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts @@ -22,8 +22,31 @@ import { } from "../../../models/message.model"; import { ConnectionStatus } from "../../../models/connection.model"; import { Command } from "models/commands.model"; +import * as grpc from "@grpc/grpc-js"; +import * as protoLoader from "@grpc/proto-loader"; +import path from "path"; describe("RevokeToken", () => { + const protoPath = path.join( + __dirname, + "..", + "..", + "..", + "proto", + "wrapper.proto" + ); + const packageDef = protoLoader.loadSync(protoPath, { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + }); + + const proto = grpc.loadPackageDefinition(packageDef) as any; + const wrapper = proto.webui.tokenization; + const peerCtor = wrapper.Peer2Peer; + function createEventMessage(targetAddress: string) { return { event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, @@ -54,7 +77,8 @@ describe("RevokeToken", () => { const conn = new Connection( "valid-token", targetAddress, - ConnectionDirection.SENDING + ConnectionDirection.SENDING, + peerCtor ); (manager as any).sendingConnections!.set(targetAddress, conn); @@ -74,7 +98,8 @@ describe("RevokeToken", () => { const conn = new Connection( "valid-token", targetAddress, - ConnectionDirection.RECEIVING + ConnectionDirection.RECEIVING, + peerCtor ); (manager as any).receivingConnections.set(targetAddress, conn); From 957a7befa3bdcdf50e5b60865d435d3af115c732 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Sun, 31 Aug 2025 14:25:51 +0200 Subject: [PATCH 057/103] feat: update unit tests for p2p connections --- .../ConnectionManager.test.ts | 318 ++++++++++++++++++ .../test/client/ConnectionManager/index.ts | 162 --------- 2 files changed, 318 insertions(+), 162 deletions(-) create mode 100644 Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts delete mode 100644 Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts diff --git a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts new file mode 100644 index 000000000..5397e99fd --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts @@ -0,0 +1,318 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import * as grpc from "@grpc/grpc-js"; +import { ConnectionManager } from "../../../client/ConnectionManager/ConnectionManager"; +import { + ConnectionDirection, + DuplexMessageEvent, +} from "../../../models/message.model"; + +// Mock duplex stream +const mockStream = { + on: jest.fn(), + end: jest.fn(), +}; + +// Mock gRPC client +const mockClient = { + ClientStream: jest.fn(() => mockStream), +}; + +// Mock CentralSystem constructor +const CentralSystemMock = jest.fn(() => mockClient); + +// Mock dispatcher +const mockDispatch = jest.fn(); +jest.mock( + "../../../client/ConnectionManager/EventManagement/CentralCommandDispatcher", + () => ({ + CentralCommandDispatcher: jest.fn(() => ({ + dispatch: mockDispatch, + register: jest.fn(), + })), + }) +); + +// Mock logger +jest.mock( + "@aliceo2/web-ui", + () => ({ + LogManager: { + getLogger: () => ({ + infoMessage: jest.fn(), + debugMessage: jest.fn(), + errorMessage: jest.fn(), + }), + }, + }), + { virtual: true } +); + +// Mock gRPC proto loader and client +jest.mock("@grpc/proto-loader", () => ({ + loadSync: jest.fn(() => { + return {}; + }), +})); + +let capturedServerImpl: any | null = null; + +jest.mock("@grpc/grpc-js", () => { + const original = jest.requireActual("@grpc/grpc-js"); + const Peer2PeerMock: any = jest.fn(() => ({ + Fetch: jest.fn(), + })); + // simulation of the service definition + Peer2PeerMock.service = { + Fetch: { + path: "/webui.tokenization.Peer2Peer/Fetch", + requestStream: false, + responseStream: false, + requestSerialize: (x: any) => x, + requestDeserialize: (x: any) => x, + responseSerialize: (x: any) => x, + responseDeserialize: (x: any) => x, + }, + }; + + // Mock server + const mockServer = { + addService: jest.fn((_svc: any, impl: any) => { + capturedServerImpl = impl; + }), + bindAsync: jest.fn((_addr: any, _creds: any, cb: any) => cb(null)), + forceShutdown: jest.fn(), + }; + + const mockServerCtor = jest.fn(() => mockServer); + + return { + ...original, + credentials: { + createInsecure: jest.fn(), + }, + ServerCredentials: { + createInsecure: jest.fn(() => ({})), + }, + status: { + ...original.status, + INTERNAL: 13, + }, + loadPackageDefinition: jest.fn(() => ({ + webui: { + tokenization: { + CentralSystem: CentralSystemMock, + Peer2Peer: Peer2PeerMock, + }, + }, + })), + Server: mockServerCtor, + }; +}); + +describe("ConnectionManager", () => { + let conn: ConnectionManager; + + beforeEach(() => { + jest.clearAllMocks(); + capturedServerImpl = null; + global.fetch = jest.fn(); + conn = new ConnectionManager("dummy.proto", "localhost:12345"); + }); + + afterAll(() => { + // @ts-ignore + delete global.fetch; + }); + + test("should initialize client with correct address", () => { + expect(conn).toBeDefined(); + expect(grpc.loadPackageDefinition).toHaveBeenCalled(); + expect(CentralSystemMock).toHaveBeenCalledWith( + "localhost:12345", + undefined + ); + }); + + test("connectToCentralSystem() should set up stream listeners", () => { + conn.connectToCentralSystem(); + + expect(mockClient.ClientStream).toHaveBeenCalled(); + expect(mockStream.on).toHaveBeenCalledWith("data", expect.any(Function)); + expect(mockStream.on).toHaveBeenCalledWith("end", expect.any(Function)); + expect(mockStream.on).toHaveBeenCalledWith("error", expect.any(Function)); + }); + + test("disconnectFromCentralSystem() should end stream", () => { + conn.connectToCentralSystem(); + conn.disconnectFromCentralSystem(); + + expect(mockStream.end).toHaveBeenCalled(); + }); + + test("should reconnect on stream 'end'", () => { + jest.useFakeTimers(); + conn.connectToCentralSystem(); + const onEnd = mockStream.on.mock.calls.find( + ([event]) => event === "end" + )?.[1]; + + onEnd?.(); // simulate 'end' + jest.advanceTimersByTime(2000); + + expect(mockClient.ClientStream).toHaveBeenCalledTimes(2); + jest.useRealTimers(); + }); + + test("should reconnect on stream 'error'", () => { + jest.useFakeTimers(); + conn.connectToCentralSystem(); + const onError = mockStream.on.mock.calls.find( + ([event]) => event === "error" + )?.[1]; + + onError?.(new Error("Simulated error")); + jest.advanceTimersByTime(2000); + + expect(mockClient.ClientStream).toHaveBeenCalledTimes(2); + jest.useRealTimers(); + }); + + test("should dispatch event when 'data' is received", () => { + conn.connectToCentralSystem(); + const onData = mockStream.on.mock.calls.find( + ([event]) => event === "data" + )?.[1]; + + const mockMessage = { + event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, + data: { + revokeToken: { + token: "abc123", + targetAddress: "peer-123", + }, + }, + }; + + onData?.(mockMessage); + + expect(mockDispatch).toHaveBeenCalledWith(mockMessage); + }); + + test("listenForPeers() should start server and register service", async () => { + await conn.listenForPeers(50055, "http://localhost:40041/api/"); + + const serverCtor = (grpc.Server as any).mock; + expect(serverCtor).toBeDefined(); + expect(serverCtor.calls.length).toBeGreaterThan(0); + + const serverInstance = serverCtor.results[0].value; + expect(serverInstance.addService).toHaveBeenCalled(); + expect(serverInstance.bindAsync).toHaveBeenCalledWith( + "localhost:50055", + expect.anything(), + expect.any(Function) + ); + + // implementacja metody Fetch została przechwycona + expect(capturedServerImpl).toBeTruthy(); + expect(typeof capturedServerImpl.Fetch).toBe("function"); + }); + + test("p2p Fetch should register incoming receiving connection and forward request", async () => { + await conn.listenForPeers(50056, "http://localhost:40041/api/"); + + // przygotuj dane wywołania + const call = { + getPeer: () => "client-42", + request: { + method: "POST", + path: "echo", + headers: { "content-type": "application/json" }, + body: Buffer.from(JSON.stringify({ ping: true })), + }, + } as any; + + const callback = jest.fn(); + + // @ts-ignore - mock global.fetch response + global.fetch.mockResolvedValue({ + status: 202, + headers: { + forEach: (fn: (v: string, k: string) => void) => { + fn("application/json", "content-type"); + fn("test", "x-extra"); + }, + }, + arrayBuffer: async () => Buffer.from(JSON.stringify({ ok: 1 })), + }); + + const before = conn.getAllConnections().receiving.length; + await capturedServerImpl.Fetch(call, callback); + + expect(global.fetch).toHaveBeenCalledWith( + "http://localhost:40041/api/echo", + { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ping: true }), + } + ); + + // callback with response from forwarded fetch + expect(callback).toHaveBeenCalledWith(null, { + status: 202, + headers: { "content-type": "application/json", "x-extra": "test" }, + body: expect.any(Buffer), + }); + + // connection receiving should be registered + const after = conn.getAllConnections().receiving.length; + expect(after).toBeGreaterThan(before); + + const rec = conn.getConnectionByAddress( + "client-42", + ConnectionDirection.RECEIVING + ); + expect(rec).toBeDefined(); + }); + + test("p2p Fetch should return INTERNAL on forward error", async () => { + await conn.listenForPeers(50057, "http://localhost:40041/api/"); + + const call = { + getPeer: () => "client-error", + request: { + method: "GET", + path: "fail", + headers: {}, + }, + } as any; + + const callback = jest.fn(); + + // @ts-ignore + global.fetch.mockRejectedValue(new Error("err")); + + await capturedServerImpl.Fetch(call, callback); + + expect(callback).toHaveBeenCalledWith( + expect.objectContaining({ + code: grpc.status.INTERNAL, + message: "err", + }) + ); + }); +}); diff --git a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts deleted file mode 100644 index 1da1fb06f..000000000 --- a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -import * as grpc from "@grpc/grpc-js"; -import { ConnectionManager } from "../../../client/ConnectionManager/ConnectionManager"; -import { DuplexMessageEvent } from "../../../models/message.model"; - -// Mock duplex stream -const mockStream = { - on: jest.fn(), - end: jest.fn(), -}; - -// Mock gRPC client -const mockClient = { - ClientStream: jest.fn(() => mockStream), -}; - -// Mock CentralSystem constructor -const CentralSystemMock = jest.fn(() => mockClient); - -// Mock dispatcher -const mockDispatch = jest.fn(); -jest.mock( - "../../../client/ConnectionManager/EventManagement/CentralCommandDispatcher", - () => ({ - CentralCommandDispatcher: jest.fn(() => ({ - dispatch: mockDispatch, - })), - }) -); - -// Mock logger -jest.mock( - "@aliceo2/web-ui", - () => ({ - LogManager: { - getLogger: () => ({ - infoMessage: jest.fn(), - }), - }, - }), - { virtual: true } -); - -// Mock gRPC proto loader and client -jest.mock("@grpc/proto-loader", () => ({ - loadSync: jest.fn(() => { - return {}; - }), -})); - -jest.mock("@grpc/grpc-js", () => { - const original = jest.requireActual("@grpc/grpc-js"); - return { - ...original, - credentials: { - createInsecure: jest.fn(), - }, - loadPackageDefinition: jest.fn(() => ({ - webui: { - tokenization: { - CentralSystem: CentralSystemMock, - }, - }, - })), - }; -}); - -describe("ConnectionManager", () => { - let conn: ConnectionManager; - - beforeEach(() => { - jest.clearAllMocks(); - conn = new ConnectionManager("dummy.proto", "localhost:12345"); - }); - - test("should initialize client with correct address", () => { - expect(conn).toBeDefined(); - expect(grpc.loadPackageDefinition).toHaveBeenCalled(); - expect(CentralSystemMock).toHaveBeenCalledWith( - "localhost:12345", - undefined - ); - }); - - test("connectToCentralSystem() should set up stream listeners", () => { - conn.connectToCentralSystem(); - - expect(mockClient.ClientStream).toHaveBeenCalled(); - expect(mockStream.on).toHaveBeenCalledWith("data", expect.any(Function)); - expect(mockStream.on).toHaveBeenCalledWith("end", expect.any(Function)); - expect(mockStream.on).toHaveBeenCalledWith("error", expect.any(Function)); - }); - - test("disconnectFromCentralSystem() should end stream", () => { - conn.connectToCentralSystem(); - conn.disconnectFromCentralSystem(); - - expect(mockStream.end).toHaveBeenCalled(); - }); - - test("should reconnect on stream 'end'", () => { - jest.useFakeTimers(); - conn.connectToCentralSystem(); - const onEnd = mockStream.on.mock.calls.find( - ([event]) => event === "end" - )?.[1]; - - onEnd?.(); // simulate 'end' - jest.advanceTimersByTime(2000); - - expect(mockClient.ClientStream).toHaveBeenCalledTimes(2); - jest.useRealTimers(); - }); - - test("should reconnect on stream 'error'", () => { - jest.useFakeTimers(); - conn.connectToCentralSystem(); - const onError = mockStream.on.mock.calls.find( - ([event]) => event === "error" - )?.[1]; - - onError?.(new Error("Simulated error")); - jest.advanceTimersByTime(2000); - - expect(mockClient.ClientStream).toHaveBeenCalledTimes(2); - jest.useRealTimers(); - }); - - test("should dispatch event when 'data' is received", () => { - conn.connectToCentralSystem(); - const onData = mockStream.on.mock.calls.find( - ([event]) => event === "data" - )?.[1]; - - const mockMessage = { - event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, - data: { - revokeToken: { - token: "abc123", - targetAddress: "peer-123", - }, - }, - }; - - onData?.(mockMessage); - - expect(mockDispatch).toHaveBeenCalledWith(mockMessage); - }); -}); From a7b81a25e004035aacfe9dd8a75f418842da1d2a Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki <42175519+OmegaCreations@users.noreply.github.com> Date: Tue, 2 Sep 2025 19:05:23 +0200 Subject: [PATCH 058/103] Potential fix for code scanning alert no. 236: Unused variable, import, function or class Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- Tokenization/backend/wrapper/client/gRPCWrapper.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/Tokenization/backend/wrapper/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/client/gRPCWrapper.ts index 7a4b8e796..fb74b45dc 100644 --- a/Tokenization/backend/wrapper/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/client/gRPCWrapper.ts @@ -1,4 +1,3 @@ -import path from "path"; import { ConnectionManager } from "./ConnectionManager/ConnectionManager.ts"; import { gRPCWrapperConfig } from "../models/config.model.ts"; From b8edd56a4b7939394133bec6981dc418d6ff4e2c Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 2 Sep 2025 20:55:20 +0200 Subject: [PATCH 059/103] feat: implement reconnection scheduler --- .../ConnectionManager/CentralConnection.ts | 22 ++--- .../src/utils/ReconnectionScheduler.ts | 95 +++++++++++++++++++ .../wrapper/src/utils/types/webui.d.ts | 14 +-- 3 files changed, 113 insertions(+), 18 deletions(-) create mode 100644 Tokenization/backend/wrapper/src/utils/ReconnectionScheduler.ts diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts index f41a586fd..32ad6ad37 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts @@ -16,6 +16,7 @@ import * as grpc from "@grpc/grpc-js"; import { LogManager } from "@aliceo2/web-ui"; import { CentralCommandDispatcher } from "./EventManagement/CentralCommandDispatcher"; import { DuplexMessageModel } from "../../models/message.model"; +import { ReconnectionScheduler } from "utils/reconnectionScheduler"; /** * @description This class manages the duplex stream with the CentralSystem gRPC service. @@ -24,6 +25,12 @@ import { DuplexMessageModel } from "../../models/message.model"; export class CentralConnection { private logger = LogManager.getLogger("CentralConnection"); private stream?: grpc.ClientDuplexStream; + private reconnectionScheduler: ReconnectionScheduler = + new ReconnectionScheduler( + () => this.connect(), + { initialDelay: 1000, maxDelay: 30000 }, + this.logger + ); constructor( private client: any, @@ -40,13 +47,14 @@ export class CentralConnection { this.stream!.on("data", (payload: DuplexMessageModel) => { this.logger.debugMessage(`Received payload: ${JSON.stringify(payload)}`); + this.reconnectionScheduler.reset(); this.dispatcher.dispatch(payload); }); this.stream!.on("end", () => { this.logger.infoMessage(`Stream ended, attempting to reconnect...`); this.stream = undefined; - this.scheduleReconnect(); + this.reconnectionScheduler.schedule(); }); this.stream!.on("error", (err: any) => { @@ -56,20 +64,10 @@ export class CentralConnection { " attempting to reconnect..." ); this.stream = undefined; - this.scheduleReconnect(); + this.reconnectionScheduler.schedule(); }); } - /** - * @description Schedules a reconnect with exponential backoff. - */ - private scheduleReconnect() { - setTimeout(() => { - this.logger.infoMessage(`Trying to reconnect...`); - this.connect(); - }, 2000); - } - /** * @description Starts the connection to the central system. */ diff --git a/Tokenization/backend/wrapper/src/utils/ReconnectionScheduler.ts b/Tokenization/backend/wrapper/src/utils/ReconnectionScheduler.ts new file mode 100644 index 000000000..c1a99f923 --- /dev/null +++ b/Tokenization/backend/wrapper/src/utils/ReconnectionScheduler.ts @@ -0,0 +1,95 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +export interface ReconnectionOptions { + initialDelay?: number; // Initial delay in ms + maxDelay?: number; // Maximum delay in ms +} + +/** + * @description Schedules reconnection attempts with exponential backoff. + */ +export class ReconnectionScheduler { + private reconnectCallback: any; + private initialDelay: number; + private maxDelay: number; + private currentDelay: number; + private attemptCount: number; + private timeoutId: any; + private logger: Logger; + private isResetting: boolean = false; + + /** + * @param reconnectCallback Function to call for reconnection attempt + * @param options Configuration options for reconnection scheduling + */ + constructor( + reconnectCallback: any, + options: ReconnectionOptions = {}, + logger: Logger + ) { + this.reconnectCallback = reconnectCallback; + this.initialDelay = options.initialDelay || 1000; + this.maxDelay = options.maxDelay || 30000; + + this.currentDelay = this.initialDelay; + this.attemptCount = 0; + this.timeoutId = null; + + this.logger = logger; + } + + /** + * @description Schedules the next reconnection attempt using exponential backoff. + */ + schedule() { + this.isResetting = false; + this.attemptCount++; + + // exponential backoff calculation + let delay = this.initialDelay * Math.pow(2, this.attemptCount); + + this.currentDelay = Math.min(this.maxDelay, delay); + + this.logger.infoMessage( + `Recconection attempt #${ + this.attemptCount + }: Sleep for ${this.currentDelay.toFixed(0)} ms.` + ); + + // plan the reconnection attempt + this.timeoutId = setTimeout(() => { + this.reconnectCallback(); + }, this.currentDelay); + } + + /** + * @description Resets the scheduler to its initial state. + */ + reset() { + if (this.isResetting) return; + this.isResetting = true; + + clearTimeout(this.timeoutId); + this.attemptCount = 0; + this.currentDelay = this.initialDelay; + } + + /** + * @description stops the reconnection attempts. + */ + stop() { + clearTimeout(this.timeoutId); + } +} diff --git a/Tokenization/backend/wrapper/src/utils/types/webui.d.ts b/Tokenization/backend/wrapper/src/utils/types/webui.d.ts index 9432663e8..41fb94bb8 100644 --- a/Tokenization/backend/wrapper/src/utils/types/webui.d.ts +++ b/Tokenization/backend/wrapper/src/utils/types/webui.d.ts @@ -14,11 +14,13 @@ declare module "@aliceo2/web-ui" { export const LogManager: { - getLogger: (name: string) => { - infoMessage: (...args: any[]) => void; - errorMessage: (...args: any[]) => void; - warnMessage: (...args: any[]) => void; - debugMessage: (...args: any[]) => void; - }; + getLogger: (name: string) => Logger; }; } + +declare interface Logger { + infoMessage: (...args: any[]) => void; + errorMessage: (...args: any[]) => void; + warnMessage: (...args: any[]) => void; + debugMessage: (...args: any[]) => void; +} From 863438b6cee11fa4dd24fb834d38835dcc0c4006 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Wed, 3 Sep 2025 14:20:11 +0200 Subject: [PATCH 060/103] fix: fix scheduler --- .../ConnectionManager/CentralConnection.ts | 2 +- .../backend/wrapper/src/client/gRPCWrapper.ts | 32 ------------------- .../src/utils/ReconnectionScheduler.ts | 5 +++ 3 files changed, 6 insertions(+), 33 deletions(-) diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts index 32ad6ad37..3aefaf9fb 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts @@ -16,7 +16,7 @@ import * as grpc from "@grpc/grpc-js"; import { LogManager } from "@aliceo2/web-ui"; import { CentralCommandDispatcher } from "./EventManagement/CentralCommandDispatcher"; import { DuplexMessageModel } from "../../models/message.model"; -import { ReconnectionScheduler } from "utils/reconnectionScheduler"; +import { ReconnectionScheduler } from "../../utils/reconnectionScheduler"; /** * @description This class manages the duplex stream with the CentralSystem gRPC service. diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index 9b7835474..797f39816 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -129,35 +129,3 @@ export class gRPCWrapper { ); } } - -// const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); -// const grpc = new gRPCWrapper(PROTO_PATH, "localhost:50051"); -// grpc.connectToCentralSystem(); -// console.log(grpc.getSummary()); - -// setTimeout(() => { -// console.log("New status after 10 seconds, token revokation and new token:"); -// console.log(grpc.getSummary()); -// }, 10000); - -// grpc.connectToCentralSystem(); - -// const conn1: Connection = grpc.connectToClient("localhost:40001"); - -// // wrapping request -// conn1 -// .fetch({ -// method: "POST", -// headers: { -// "Content-Type": "application/json", -// Authorization: "bearer someToken", -// }, -// body: JSON.stringify({ -// name: "Jan Kowalski", -// email: "jan.kowalski@example.com", -// age: 28, -// }), -// }) -// .then((response) => response.json()) -// .then((data) => console.log("Response:", data)) -// .catch((error) => console.error("Error:", error)); diff --git a/Tokenization/backend/wrapper/src/utils/ReconnectionScheduler.ts b/Tokenization/backend/wrapper/src/utils/ReconnectionScheduler.ts index c1a99f923..d4f0f2504 100644 --- a/Tokenization/backend/wrapper/src/utils/ReconnectionScheduler.ts +++ b/Tokenization/backend/wrapper/src/utils/ReconnectionScheduler.ts @@ -28,7 +28,9 @@ export class ReconnectionScheduler { private attemptCount: number; private timeoutId: any; private logger: Logger; + private isResetting: boolean = false; + private isScheduling: boolean = false; /** * @param reconnectCallback Function to call for reconnection attempt @@ -54,6 +56,8 @@ export class ReconnectionScheduler { * @description Schedules the next reconnection attempt using exponential backoff. */ schedule() { + if (this.isScheduling) return; + this.isScheduling = true; this.isResetting = false; this.attemptCount++; @@ -70,6 +74,7 @@ export class ReconnectionScheduler { // plan the reconnection attempt this.timeoutId = setTimeout(() => { + this.isScheduling = false; this.reconnectCallback(); }, this.currentDelay); } From 542e0f6dd1c2e777cb86bd127e844a4e9dbbd013 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Wed, 3 Sep 2025 14:54:23 +0200 Subject: [PATCH 061/103] fix: fix imports --- .../utils/{ReconnectionScheduler.ts => reconnectionScheduler.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Tokenization/backend/wrapper/src/utils/{ReconnectionScheduler.ts => reconnectionScheduler.ts} (100%) diff --git a/Tokenization/backend/wrapper/src/utils/ReconnectionScheduler.ts b/Tokenization/backend/wrapper/src/utils/reconnectionScheduler.ts similarity index 100% rename from Tokenization/backend/wrapper/src/utils/ReconnectionScheduler.ts rename to Tokenization/backend/wrapper/src/utils/reconnectionScheduler.ts From b866463d500953c7241e7eddf0b0b35cef837220 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 25 Sep 2025 22:42:34 +0200 Subject: [PATCH 062/103] feat: implement secure connection and fix unit tests --- ...ntralSystem.ts => CentralSystemWrapper.ts} | 34 +++++--- .../src/client/Connection/Connection.ts | 24 ++++-- .../ConnectionManager/ConnectionManager.ts | 50 +++++------- .../backend/wrapper/src/client/gRPCWrapper.ts | 48 +++++++----- .../wrapper/src/models/config.model.ts | 46 +++++++++++ .../src/test/central/CentralSystem.test.ts | 12 ++- .../test/central/CentralSystemWrapper.test.ts | 12 ++- .../src/test/client/Commands/newToken.test.ts | 13 +++- .../test/client/Commands/revokeToken.test.ts | 7 +- .../ConnectionManager.test.ts | 40 ++++++++-- .../backend/wrapper/src/test/testCerts/ca.crt | 12 +++ .../central.system.svc.local.crt | 14 ++++ .../central.system.svc.local.key | 5 ++ .../clientListener/client-b.svc.local.crt | 13 ++++ .../clientListener/client-b.svc.local.key | 5 ++ .../clientSender/client-a.svc.local.crt | 13 ++++ .../clientSender/client-a.svc.local.key | 5 ++ .../wrapper/src/test/testCerts/testCerts.ts | 78 +++++++++++++++++++ 18 files changed, 346 insertions(+), 85 deletions(-) rename Tokenization/backend/wrapper/src/central/{CentralSystem.ts => CentralSystemWrapper.ts} (87%) create mode 100644 Tokenization/backend/wrapper/src/models/config.model.ts create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/ca.crt create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central.system.svc.local.crt create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central.system.svc.local.key create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.svc.local.crt create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.svc.local.key create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.svc.local.crt create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.svc.local.key create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/testCerts.ts diff --git a/Tokenization/backend/wrapper/src/central/CentralSystem.ts b/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts similarity index 87% rename from Tokenization/backend/wrapper/src/central/CentralSystem.ts rename to Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts index 4244317ac..a7a8b5eb4 100644 --- a/Tokenization/backend/wrapper/src/central/CentralSystem.ts +++ b/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts @@ -17,6 +17,7 @@ import * as protoLoader from "@grpc/proto-loader"; import { LogManager } from "@aliceo2/web-ui"; import { DuplexMessageModel } from "../models/message.model"; import * as fs from "fs"; +import { CentralSystemConfig } from "models/config.model"; /** * @description Central System gRPC wrapper that manages client connections and handles gRPC streams with them. @@ -27,6 +28,11 @@ export class CentralSystemWrapper { // class properties private server: grpc.Server; + private protoPath: string; + private port: number; + + // certificates paths + private serverCerts: CentralSystemConfig["serverCerts"]; // clients management private clients = new Map>(); @@ -36,13 +42,21 @@ export class CentralSystemWrapper { * Initializes the Wrapper for CentralSystem. * @param port The port number to bind the gRPC server to. */ - constructor( - private protoPath: string, - private port: number, - private readonly caCertPath: string, - private readonly centralCertPath: string, - private readonly centralKeyPath: string - ) { + constructor(config: CentralSystemConfig) { + if ( + !config.protoPath || + !config.serverCerts || + !config.serverCerts.caCertPath || + !config.serverCerts.certPath || + !config.serverCerts.keyPath + ) { + throw new Error("Invalid CentralSystemConfig provided"); + } + + this.protoPath = config.protoPath; + this.serverCerts = config.serverCerts; + this.port = config.port || 50051; + this.server = new grpc.Server(); this.setupService(); } @@ -176,9 +190,9 @@ export class CentralSystemWrapper { const addr = `localhost:${this.port}`; // create mTLS secure gRPC server - const caCert = fs.readFileSync(this.caCertPath); - const centralKey = fs.readFileSync(this.centralKeyPath); - const centralCert = fs.readFileSync(this.centralCertPath); + const caCert = fs.readFileSync(this.serverCerts.caCertPath); + const centralKey = fs.readFileSync(this.serverCerts.keyPath); + const centralCert = fs.readFileSync(this.serverCerts.certPath); const sslCreds = grpc.ServerCredentials.createSsl( caCert, diff --git a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts index b322f51dd..b4ca2e597 100644 --- a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts +++ b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts @@ -49,19 +49,31 @@ export class Connection { targetAddress: string, direction: ConnectionDirection, peerCtor: any, - private readonly caCert: NonSharedBuffer, - private readonly clientCert: NonSharedBuffer, - private readonly clientKey: NonSharedBuffer + private readonly connectionCerts: { + caCert: NonSharedBuffer; + clientCert: NonSharedBuffer; + clientKey: NonSharedBuffer; + } ) { this.token = token; this.targetAddress = targetAddress; this.direction = direction; + if ( + !connectionCerts.caCert || + !connectionCerts.clientCert || + !connectionCerts.clientKey + ) { + throw new Error( + "Connection certificates are required to create a Connection." + ); + } + // create grpc credentials const sslCreds = grpc.credentials.createSsl( - this.caCert, - this.clientKey, - this.clientCert + this.connectionCerts.caCert, + this.connectionCerts.clientKey, + this.connectionCerts.clientCert ); this.peerClient = new peerCtor(targetAddress, sslCreds); diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts index 4db68af63..4f8ffc756 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts @@ -116,27 +116,19 @@ export class ConnectionManager { this.sendingConnections.set( "a", - new Connection( - "1", - "a", - ConnectionDirection.SENDING, - this.peerCtor, - this.caCert, - this.clientKey, - this.clientCert - ) + new Connection("1", "a", ConnectionDirection.SENDING, this.peerCtor, { + caCert: this.caCert, + clientCert: this.clientCert, + clientKey: this.clientKey, + }) ); this.sendingConnections.set( "b", - new Connection( - "2", - "b", - ConnectionDirection.SENDING, - this.peerCtor, - this.caCert, - this.clientKey, - this.clientCert - ) + new Connection("2", "b", ConnectionDirection.SENDING, this.peerCtor, { + caCert: this.caCert, + clientCert: this.clientCert, + clientKey: this.clientKey, + }) ); } @@ -197,15 +189,11 @@ export class ConnectionManager { } // Create new connection - conn = new Connection( - token || "", - address, - direction, - this.peerCtor, - this.caCert, - this.clientKey, - this.clientCert - ); + conn = new Connection(token || "", address, direction, this.peerCtor, { + caCert: this.caCert, + clientCert: this.clientCert, + clientKey: this.clientKey, + }); conn.updateStatus(ConnectionStatus.CONNECTING); if (direction === ConnectionDirection.RECEIVING) { @@ -287,9 +275,11 @@ export class ConnectionManager { clientAddress, ConnectionDirection.RECEIVING, this.peerCtor, - this.caCert, - this.clientKey, - this.clientCert + { + caCert: this.caCert, + clientCert: this.clientCert, + clientKey: this.clientKey, + } ); conn.updateStatus(ConnectionStatus.CONNECTED); this.receivingConnections.set(clientAddress, conn); diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index e339465b9..5be3e7ee3 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -20,8 +20,9 @@ import { } from "../models/message.model"; import { Connection } from "./Connection/Connection"; import { NewTokenHandler } from "./Commands/newToken/newToken.handler"; -import { gRPCWrapperConfig } from "../models/config.model.ts"; +import { gRPCWrapperConfig } from "../models/config.model"; import * as fs from "fs"; +import { LogManager } from "@aliceo2/web-ui"; /** * @description Wrapper class for managing secure gRPC wrapper. @@ -41,6 +42,7 @@ export class gRPCWrapper { private ConnectionManager: ConnectionManager; private listenerKey?: NonSharedBuffer; private listenerCert?: NonSharedBuffer; + private logger = LogManager.getLogger("gRPCWrapper"); /** * @description Initializes an instance of gRPCWrapper class. @@ -48,27 +50,32 @@ export class gRPCWrapper { * @param protoPath - The file path to the gRPC proto definition. * @param centralAddress - The address of the central gRPC server (default: "localhost:50051"). */ - constructor( - protoPath: string, - centralAddress: string = "localhost:50051", - private readonly clientCerts: { - keyPath: string; - certPath: string; - caPath: string; - }, - private listenerCertPaths?: { keyPath: string; certPath: string } - ) { - if (listenerCertPaths?.keyPath && listenerCertPaths?.certPath) { - this.listenerKey = fs.readFileSync(listenerCertPaths.keyPath); - this.listenerCert = fs.readFileSync(listenerCertPaths.certPath); + constructor(config: gRPCWrapperConfig) { + if ( + !config.protoPath || + !config.centralAddress || + !config.clientCerts || + !config.clientCerts.caCertPath || + !config.clientCerts.certPath || + !config.clientCerts.keyPath + ) { + throw new Error("Invalid gRPCWrapper configuration provided."); + } + + if ( + config.listenerCertPaths?.keyPath && + config.listenerCertPaths?.certPath + ) { + this.listenerKey = fs.readFileSync(config.listenerCertPaths.keyPath); + this.listenerCert = fs.readFileSync(config.listenerCertPaths.certPath); } this.ConnectionManager = new ConnectionManager( - protoPath, - centralAddress, - this.clientCerts.caPath, - this.clientCerts.certPath, - this.clientCerts.keyPath + config.protoPath, + config.centralAddress, + config.clientCerts.caCertPath, + config.clientCerts.certPath, + config.clientCerts.keyPath ); this.ConnectionManager.registerCommandHandlers([ { @@ -117,9 +124,10 @@ export class gRPCWrapper { } if (!this.listenerKey || !this.listenerCert) { - throw new Error( + this.logger.errorMessage( "Listener certificates are required to start P2P listener. Please provide valid paths." ); + return; } return this.ConnectionManager.listenForPeers( diff --git a/Tokenization/backend/wrapper/src/models/config.model.ts b/Tokenization/backend/wrapper/src/models/config.model.ts new file mode 100644 index 000000000..6d6b18555 --- /dev/null +++ b/Tokenization/backend/wrapper/src/models/config.model.ts @@ -0,0 +1,46 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +export interface CentralSystemConfig { + /** Path to the proto file defining the services. */ + protoPath: string; + /** Host/IP to bind the gRPC server on. Defaults to "0.0.0.0" which is docker-friendly. */ + host?: string; + /** Port to bind. Defaults to 50051. */ + port?: number; + + /** Central TLS certificates paths. */ + serverCerts: { + caCertPath: string; + certPath: string; + keyPath: string; + }; +} + +export interface gRPCWrapperConfig { + /** Path to the proto file defining the services. */ + protoPath: string; + /** Address of the CentralSystem server. */ + centralAddress: string; + + /** Client TLS certificates paths. */ + clientCerts: { + caCertPath: string; + keyPath: string; + certPath: string; + }; + + /** Optional listener TLS certificates paths. If provided, the gRPCWrapper will be able to accept incoming connections. */ + listenerCertPaths?: { keyPath: string; certPath: string }; +} diff --git a/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts b/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts index 8fe1c38d0..ef54852ae 100644 --- a/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts +++ b/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts @@ -21,6 +21,7 @@ const mockServerInstance = { const logger = { infoMessage: jest.fn(), + errorMessage: jest.fn(), }; jest.mock( @@ -45,7 +46,7 @@ jest.mock("@grpc/grpc-js", () => { ...original, Server: jest.fn(() => mockServerInstance), ServerCredentials: { - createInsecure: jest.fn(() => "mock-credentials"), + createSsl: jest.fn(() => "mock-credentials"), }, loadPackageDefinition: jest.fn(() => ({ webui: { @@ -59,24 +60,27 @@ jest.mock("@grpc/grpc-js", () => { }; }); +import { getTestCentralCertPaths } from "../testCerts/testCerts"; import { CentralSystemWrapper } from "../../central/CentralSystemWrapper"; import * as grpc from "@grpc/grpc-js"; describe("CentralSystemWrapper", () => { let wrapper: CentralSystemWrapper; + const testCentralCertPaths = getTestCentralCertPaths(); beforeEach(() => { jest.clearAllMocks(); wrapper = new CentralSystemWrapper({ protoPath: "dummy.proto", port: 12345, + serverCerts: testCentralCertPaths, }); }); test("should set up gRPC service and add it to the server", () => { expect(grpc.Server).toHaveBeenCalled(); expect(grpc.loadPackageDefinition).toHaveBeenCalled(); - expect(grpc.ServerCredentials.createInsecure).not.toHaveBeenCalled(); + expect(grpc.ServerCredentials.createSsl).not.toHaveBeenCalled(); expect(wrapper).toBeDefined(); }); @@ -98,7 +102,7 @@ describe("CentralSystemWrapper", () => { wrapper.listen(); - expect(logger.infoMessage).toHaveBeenCalledWith( + expect(logger.errorMessage).toHaveBeenCalledWith( "Server bind error:", error ); @@ -131,7 +135,7 @@ describe("CentralSystemWrapper", () => { expect(logger.infoMessage).toHaveBeenCalledWith( "Client client123 ended stream." ); - expect(logger.infoMessage).toHaveBeenCalledWith( + expect(logger.errorMessage).toHaveBeenCalledWith( "Stream error from client client123:", expect.any(Error) ); diff --git a/Tokenization/backend/wrapper/src/test/central/CentralSystemWrapper.test.ts b/Tokenization/backend/wrapper/src/test/central/CentralSystemWrapper.test.ts index 8fe1c38d0..d8954bcfb 100644 --- a/Tokenization/backend/wrapper/src/test/central/CentralSystemWrapper.test.ts +++ b/Tokenization/backend/wrapper/src/test/central/CentralSystemWrapper.test.ts @@ -21,6 +21,7 @@ const mockServerInstance = { const logger = { infoMessage: jest.fn(), + errorMessage: jest.fn(), }; jest.mock( @@ -45,7 +46,7 @@ jest.mock("@grpc/grpc-js", () => { ...original, Server: jest.fn(() => mockServerInstance), ServerCredentials: { - createInsecure: jest.fn(() => "mock-credentials"), + createSsl: jest.fn(() => "mock-credentials"), }, loadPackageDefinition: jest.fn(() => ({ webui: { @@ -61,22 +62,25 @@ jest.mock("@grpc/grpc-js", () => { import { CentralSystemWrapper } from "../../central/CentralSystemWrapper"; import * as grpc from "@grpc/grpc-js"; +import { getTestCentralCertPaths } from "../testCerts/testCerts"; describe("CentralSystemWrapper", () => { let wrapper: CentralSystemWrapper; + const testCentralCertPaths = getTestCentralCertPaths(); beforeEach(() => { jest.clearAllMocks(); wrapper = new CentralSystemWrapper({ protoPath: "dummy.proto", port: 12345, + serverCerts: testCentralCertPaths, }); }); test("should set up gRPC service and add it to the server", () => { expect(grpc.Server).toHaveBeenCalled(); expect(grpc.loadPackageDefinition).toHaveBeenCalled(); - expect(grpc.ServerCredentials.createInsecure).not.toHaveBeenCalled(); + expect(grpc.ServerCredentials.createSsl).not.toHaveBeenCalled(); expect(wrapper).toBeDefined(); }); @@ -98,7 +102,7 @@ describe("CentralSystemWrapper", () => { wrapper.listen(); - expect(logger.infoMessage).toHaveBeenCalledWith( + expect(logger.errorMessage).toHaveBeenCalledWith( "Server bind error:", error ); @@ -131,7 +135,7 @@ describe("CentralSystemWrapper", () => { expect(logger.infoMessage).toHaveBeenCalledWith( "Client client123 ended stream." ); - expect(logger.infoMessage).toHaveBeenCalledWith( + expect(logger.errorMessage).toHaveBeenCalledWith( "Stream error from client client123:", expect.any(Error) ); diff --git a/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts b/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts index 829dff294..37e948cf5 100644 --- a/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts @@ -24,6 +24,7 @@ import { import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; import path from "path"; +import { getTestCerts } from "../../testCerts/testCerts"; /** * Helper to create a new token command with given address, direction, and token. @@ -87,7 +88,13 @@ describe("NewTokenHandler", () => { dir: ConnectionDirection, token: string ) { - const conn = new Connection(token, address, dir, peerCtor); + const conn = new Connection( + token, + address, + dir, + peerCtor, + getTestCerts() + ); if (dir === ConnectionDirection.SENDING) { this.sendingConnections.set(address, conn); } else { @@ -104,8 +111,10 @@ describe("NewTokenHandler", () => { "old-token", targetAddress, ConnectionDirection.SENDING, - peerCtor + peerCtor, + getTestCerts() ); + (manager as any).sendingConnections.set(targetAddress, conn); const handler = new NewTokenHandler(manager); diff --git a/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts b/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts index 2eb821e90..85eddcc8a 100644 --- a/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts @@ -25,6 +25,7 @@ import { Command } from "models/commands.model"; import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; import path from "path"; +import { getTestCerts } from "../../testCerts/testCerts"; describe("RevokeToken", () => { const protoPath = path.join( @@ -78,7 +79,8 @@ describe("RevokeToken", () => { "valid-token", targetAddress, ConnectionDirection.SENDING, - peerCtor + peerCtor, + getTestCerts() ); (manager as any).sendingConnections!.set(targetAddress, conn); @@ -99,7 +101,8 @@ describe("RevokeToken", () => { "valid-token", targetAddress, ConnectionDirection.RECEIVING, - peerCtor + peerCtor, + getTestCerts() ); (manager as any).receivingConnections.set(targetAddress, conn); diff --git a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts index 5397e99fd..503b22a50 100644 --- a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts @@ -18,6 +18,10 @@ import { ConnectionDirection, DuplexMessageEvent, } from "../../../models/message.model"; +import { + getTestCentralCertPaths, + getTestCerts, +} from "../../testCerts/testCerts"; // Mock duplex stream const mockStream = { @@ -101,10 +105,10 @@ jest.mock("@grpc/grpc-js", () => { return { ...original, credentials: { - createInsecure: jest.fn(), + createSsl: jest.fn(() => "mock-credentials"), }, ServerCredentials: { - createInsecure: jest.fn(() => ({})), + createSsl: jest.fn(() => "mock-credentials"), }, status: { ...original.status, @@ -124,12 +128,19 @@ jest.mock("@grpc/grpc-js", () => { describe("ConnectionManager", () => { let conn: ConnectionManager; + const { caCertPath, certPath, keyPath } = getTestCentralCertPaths(); beforeEach(() => { jest.clearAllMocks(); capturedServerImpl = null; global.fetch = jest.fn(); - conn = new ConnectionManager("dummy.proto", "localhost:12345"); + conn = new ConnectionManager( + "dummy.proto", + "localhost:12345", + caCertPath, + certPath, + keyPath + ); }); afterAll(() => { @@ -142,7 +153,7 @@ describe("ConnectionManager", () => { expect(grpc.loadPackageDefinition).toHaveBeenCalled(); expect(CentralSystemMock).toHaveBeenCalledWith( "localhost:12345", - undefined + "mock-credentials" ); }); @@ -212,7 +223,12 @@ describe("ConnectionManager", () => { }); test("listenForPeers() should start server and register service", async () => { - await conn.listenForPeers(50055, "http://localhost:40041/api/"); + await conn.listenForPeers( + 50055, + getTestCerts().clientCert, + getTestCerts().caCert, + "http://localhost:40041/api/" + ); const serverCtor = (grpc.Server as any).mock; expect(serverCtor).toBeDefined(); @@ -232,7 +248,12 @@ describe("ConnectionManager", () => { }); test("p2p Fetch should register incoming receiving connection and forward request", async () => { - await conn.listenForPeers(50056, "http://localhost:40041/api/"); + await conn.listenForPeers( + 50056, + getTestCerts().clientCert, + getTestCerts().caCert, + "http://localhost:40041/api/" + ); // przygotuj dane wywołania const call = { @@ -290,7 +311,12 @@ describe("ConnectionManager", () => { }); test("p2p Fetch should return INTERNAL on forward error", async () => { - await conn.listenForPeers(50057, "http://localhost:40041/api/"); + await conn.listenForPeers( + 50057, + getTestCerts().clientCert, + getTestCerts().caCert, + "http://localhost:40041/api/" + ); const call = { getPeer: () => "client-error", diff --git a/Tokenization/backend/wrapper/src/test/testCerts/ca.crt b/Tokenization/backend/wrapper/src/test/testCerts/ca.crt new file mode 100644 index 000000000..f1ac950cf --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/ca.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBrjCCAVWgAwIBAgIUDrk3s+DN4r4YrgKixgOD1bLk2HMwCgYIKoZIzj0EAwIw +RTEdMBsGA1UEAwwURXhhbXBsZSBUZXN0IFJvb3QgQ0ExFzAVBgNVBAoMDkV4YW1w +bGVDbyBUZXN0MQswCQYDVQQGEwJQTDAeFw0yNTA4MjYxNTIzNDVaFw0zNTA4MjQx +NTI4NDVaMEUxHTAbBgNVBAMMFEV4YW1wbGUgVGVzdCBSb290IENBMRcwFQYDVQQK +DA5FeGFtcGxlQ28gVGVzdDELMAkGA1UEBhMCUEwwWTATBgcqhkjOPQIBBggqhkjO +PQMBBwNCAASSEFtjeisuWn0K1xjr4gD9RS/kTMMBDGMfRzw6dT5Xc8e4FfpSSfQ8 +mZs+3UOf4/tDIzz40GP5ayYIgXZGq0yhoyMwITAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBhjAKBggqhkjOPQQDAgNHADBEAiAgdtqDwf7ecyj5mfw1hASm +nu1wICbvS+vBTwL8c7gJdwIgdtZer+Vw3g/e+yK55rMwj3lzHdoZXgxZg7nY/wlW +Lr4= +-----END CERTIFICATE----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central.system.svc.local.crt b/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central.system.svc.local.crt new file mode 100644 index 000000000..79a6e5b2f --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central.system.svc.local.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICDzCCAbSgAwIBAgIUMT7kDfFrbxHKvOzcUH467BHIZKAwCgYIKoZIzj0EAwIw +RTEdMBsGA1UEAwwURXhhbXBsZSBUZXN0IFJvb3QgQ0ExFzAVBgNVBAoMDkV4YW1w +bGVDbyBUZXN0MQswCQYDVQQGEwJQTDAeFw0yNTA4MjYxNTIzNDVaFw0yNjA4MjYx +NTI4NDVaMEkxITAfBgNVBAMMGGNlbnRyYWwuc3lzdGVtLnN2Yy5sb2NhbDEXMBUG +A1UECgwORXhhbXBsZUNvIFRlc3QxCzAJBgNVBAYTAlBMMFkwEwYHKoZIzj0CAQYI +KoZIzj0DAQcDQgAEJBVRkwiBKUn8osDqgeaXAlbGB8oYd0Awre++dGL70zCYHOJ6 +Av/o31sTfB9wZUpL36mvzGPbNX9KjRAxQbBzHKN+MHwwDAYDVR0TAQH/BAIwADA9 +BgNVHREENjA0ghhjZW50cmFsLnN5c3RlbS5zdmMubG9jYWyCB2NlbnRyYWyCCWxv +Y2FsaG9zdIcEfwAAATAOBgNVHQ8BAf8EBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUH +AwEGCCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCLOWlisiuOzD6FI46PrXrl +YYnZgaB2pAropRK6XZln6gIhANq2AEqyFrwuzuw70yBIoIiv3Z/bIyuVmGfoopzs +uYo4 +-----END CERTIFICATE----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central.system.svc.local.key b/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central.system.svc.local.key new file mode 100644 index 000000000..121c305a8 --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central.system.svc.local.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEICiXVtz9kxU7b/gyOQpEC4z9ECNYhyKz6K63+pErBRBPoAoGCCqGSM49 +AwEHoUQDQgAEJBVRkwiBKUn8osDqgeaXAlbGB8oYd0Awre++dGL70zCYHOJ6Av/o +31sTfB9wZUpL36mvzGPbNX9KjRAxQbBzHA== +-----END EC PRIVATE KEY----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.svc.local.crt b/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.svc.local.crt new file mode 100644 index 000000000..d32418936 --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.svc.local.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB8jCCAZigAwIBAgIUFWvpRzaOwyQTl013JHSZr/0ZXwQwCgYIKoZIzj0EAwIw +RTEdMBsGA1UEAwwURXhhbXBsZSBUZXN0IFJvb3QgQ0ExFzAVBgNVBAoMDkV4YW1w +bGVDbyBUZXN0MQswCQYDVQQGEwJQTDAeFw0yNTA4MjYxNTIzNDVaFw0yNjA4MjYx +NTI4NDVaMEMxGzAZBgNVBAMMEmNsaWVudC1iLnN2Yy5sb2NhbDEXMBUGA1UECgwO +RXhhbXBsZUNvIFRlc3QxCzAJBgNVBAYTAlBMMFkwEwYHKoZIzj0CAQYIKoZIzj0D +AQcDQgAE2XWeWQcFI9FSD0AhcGbtq4xBCKslLGbBJxmUwTw7jS8XuWbo2R/C2W8c +CoNoqacuXOABty601v7vymh0fVvDZKNoMGYwDAYDVR0TAQH/BAIwADAnBgNVHREE +IDAeghJjbGllbnQtYi5zdmMubG9jYWyCCGNsaWVudC1iMA4GA1UdDwEB/wQEAwID +qDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwCgYIKoZIzj0EAwIDSAAw +RQIgNyD6+Fmm+0IKtCZnaRJsfbMpep55K/zJCekJsh0u+agCIQDLxllwAs2xXtCT +2Tf3Zr1gQ3qM0V20KiXhq8aBkMiKXw== +-----END CERTIFICATE----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.svc.local.key b/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.svc.local.key new file mode 100644 index 000000000..68dff890a --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.svc.local.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIP7Nw0K+xSwzXOpN6SL1tZsQo++26LwqWQsi33RKONiRoAoGCCqGSM49 +AwEHoUQDQgAE2XWeWQcFI9FSD0AhcGbtq4xBCKslLGbBJxmUwTw7jS8XuWbo2R/C +2W8cCoNoqacuXOABty601v7vymh0fVvDZA== +-----END EC PRIVATE KEY----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.svc.local.crt b/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.svc.local.crt new file mode 100644 index 000000000..a6d5025b6 --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.svc.local.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB8jCCAZigAwIBAgIUa4ibeAgx6Ga3UklyqWn8uEtEcQcwCgYIKoZIzj0EAwIw +RTEdMBsGA1UEAwwURXhhbXBsZSBUZXN0IFJvb3QgQ0ExFzAVBgNVBAoMDkV4YW1w +bGVDbyBUZXN0MQswCQYDVQQGEwJQTDAeFw0yNTA4MjYxNTIzNDVaFw0yNjA4MjYx +NTI4NDVaMEMxGzAZBgNVBAMMEmNsaWVudC1hLnN2Yy5sb2NhbDEXMBUGA1UECgwO +RXhhbXBsZUNvIFRlc3QxCzAJBgNVBAYTAlBMMFkwEwYHKoZIzj0CAQYIKoZIzj0D +AQcDQgAEU9voQyzju4VEGHJd/5KWROHG1ZqjP8ClUT8qxQ4fo7vQTQAitXYxr4sh +jfI4LvX63Yd/FzbTiFD3vlIJHdMrnqNoMGYwDAYDVR0TAQH/BAIwADAnBgNVHREE +IDAeghJjbGllbnQtYS5zdmMubG9jYWyCCGNsaWVudC1hMA4GA1UdDwEB/wQEAwID +qDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwCgYIKoZIzj0EAwIDSAAw +RQIgCqma8A91M48a9t4f/b38nCEgG2LQ0TRLiWqMw7G+3vwCIQDcMDbM9Beuh1ap +JudMwo61iUWxeF+TLgapxxO/R4jWqA== +-----END CERTIFICATE----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.svc.local.key b/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.svc.local.key new file mode 100644 index 000000000..9531bc087 --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.svc.local.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIL9ACEBWYWkPUs6t9ZbrWETL1kpsPe/pxKQIqnqHlWyroAoGCCqGSM49 +AwEHoUQDQgAEU9voQyzju4VEGHJd/5KWROHG1ZqjP8ClUT8qxQ4fo7vQTQAitXYx +r4shjfI4LvX63Yd/FzbTiFD3vlIJHdMrng== +-----END EC PRIVATE KEY----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/testCerts.ts b/Tokenization/backend/wrapper/src/test/testCerts/testCerts.ts new file mode 100644 index 000000000..c37742655 --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/testCerts.ts @@ -0,0 +1,78 @@ +import { CentralSystemConfig, gRPCWrapperConfig } from "models/config.model"; +import path from "path"; +import * as fs from "fs"; + +export const getTestCentralCertPaths = + (): CentralSystemConfig["serverCerts"] => { + const CA_CERT_PATH = path.join(__dirname, "./ca.crt"); + const SERVER_CERT_PATH = path.join( + __dirname, + "./centralSystem/central.system.svc.local.crt" + ); + const SERVER_KEY_PATH = path.join( + __dirname, + "./centralSystem/central.system.svc.local.key" + ); + + return { + caCertPath: CA_CERT_PATH, + certPath: SERVER_CERT_PATH, + keyPath: SERVER_KEY_PATH, + }; + }; + +export const getTestClientListenerCertPaths = + (): gRPCWrapperConfig["clientCerts"] => { + const CA_CERT_PATH = path.join(__dirname, "./ca.crt"); + const CLIENT_CERT_PATH = path.join( + __dirname, + "./clientListener/client-b.svc.local.crt" + ); + const CLIENT_KEY_PATH = path.join( + __dirname, + "./clientListener/client-b.svc.local.key" + ); + + return { + caCertPath: CA_CERT_PATH, + certPath: CLIENT_CERT_PATH, + keyPath: CLIENT_KEY_PATH, + }; + }; + +export const getTestClientSenderCertPaths = + (): gRPCWrapperConfig["clientCerts"] => { + const CA_CERT_PATH = path.join(__dirname, "./ca.crt"); + const CLIENT_CERT_PATH = path.join( + __dirname, + "./clientSender/client-a.svc.local.crt" + ); + const CLIENT_KEY_PATH = path.join( + __dirname, + "./clientSender/client-a.svc.local.key" + ); + + return { + caCertPath: CA_CERT_PATH, + certPath: CLIENT_CERT_PATH, + keyPath: CLIENT_KEY_PATH, + }; + }; + +export const getTestCerts = () => { + const CA_CERT_PATH = path.join(__dirname, "./ca.crt"); + const SERVER_CERT_PATH = path.join( + __dirname, + "./centralSystem/central.system.svc.local.crt" + ); + const SERVER_KEY_PATH = path.join( + __dirname, + "./centralSystem/central.system.svc.local.key" + ); + + const caCert = fs.readFileSync(CA_CERT_PATH); + const clientCert = fs.readFileSync(SERVER_CERT_PATH); + const clientKey = fs.readFileSync(SERVER_KEY_PATH); + + return { caCert, clientCert, clientKey }; +}; From 8c7bceb81e9efe7471b8e7ca5008db9f9356bf1f Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 25 Sep 2025 23:45:56 +0200 Subject: [PATCH 063/103] feat: add testing certificates --- .../backend/wrapper/src/test/testCerts/ca.crt | 39 ++++++++++++++----- .../centralSystem/central-system.crt | 28 +++++++++++++ .../centralSystem/central-system.key | 28 +++++++++++++ .../central.system.svc.local.crt | 14 ------- .../central.system.svc.local.key | 5 --- .../clientListener/client-b-client.crt | 28 +++++++++++++ .../testCerts/clientListener/client-b.key | 28 +++++++++++++ .../testCerts/clientListener/client-b.pub.pem | 9 +++++ .../clientListener/client-b.svc.local.crt | 13 ------- .../clientListener/client-b.svc.local.key | 5 --- .../clientListenerServer/client-b-server.crt | 27 +++++++++++++ .../clientListenerServer/client-b.key | 28 +++++++++++++ .../clientSender/client-a-client.crt | 28 +++++++++++++ .../clientSender/client-a-server.crt | 27 +++++++++++++ .../test/testCerts/clientSender/client-a.key | 28 +++++++++++++ .../testCerts/clientSender/client-a.pub.pem | 9 +++++ .../clientSender/client-a.svc.local.crt | 13 ------- .../clientSender/client-a.svc.local.key | 5 --- .../wrapper/src/test/testCerts/testCerts.ts | 34 +++++++++++----- 19 files changed, 322 insertions(+), 74 deletions(-) create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central-system.crt create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central-system.key delete mode 100644 Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central.system.svc.local.crt delete mode 100644 Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central.system.svc.local.key create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b-client.crt create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.key create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.pub.pem delete mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.svc.local.crt delete mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.svc.local.key create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientListenerServer/client-b-server.crt create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientListenerServer/client-b.key create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a-client.crt create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a-server.crt create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.key create mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.pub.pem delete mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.svc.local.crt delete mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.svc.local.key diff --git a/Tokenization/backend/wrapper/src/test/testCerts/ca.crt b/Tokenization/backend/wrapper/src/test/testCerts/ca.crt index f1ac950cf..899799b3c 100644 --- a/Tokenization/backend/wrapper/src/test/testCerts/ca.crt +++ b/Tokenization/backend/wrapper/src/test/testCerts/ca.crt @@ -1,12 +1,31 @@ -----BEGIN CERTIFICATE----- -MIIBrjCCAVWgAwIBAgIUDrk3s+DN4r4YrgKixgOD1bLk2HMwCgYIKoZIzj0EAwIw -RTEdMBsGA1UEAwwURXhhbXBsZSBUZXN0IFJvb3QgQ0ExFzAVBgNVBAoMDkV4YW1w -bGVDbyBUZXN0MQswCQYDVQQGEwJQTDAeFw0yNTA4MjYxNTIzNDVaFw0zNTA4MjQx -NTI4NDVaMEUxHTAbBgNVBAMMFEV4YW1wbGUgVGVzdCBSb290IENBMRcwFQYDVQQK -DA5FeGFtcGxlQ28gVGVzdDELMAkGA1UEBhMCUEwwWTATBgcqhkjOPQIBBggqhkjO -PQMBBwNCAASSEFtjeisuWn0K1xjr4gD9RS/kTMMBDGMfRzw6dT5Xc8e4FfpSSfQ8 -mZs+3UOf4/tDIzz40GP5ayYIgXZGq0yhoyMwITAPBgNVHRMBAf8EBTADAQH/MA4G -A1UdDwEB/wQEAwIBhjAKBggqhkjOPQQDAgNHADBEAiAgdtqDwf7ecyj5mfw1hASm -nu1wICbvS+vBTwL8c7gJdwIgdtZer+Vw3g/e+yK55rMwj3lzHdoZXgxZg7nY/wlW -Lr4= +MIIFWjCCA0KgAwIBAgIUA3wFlpIAu9PcCYrsZQwml1VBbBIwDQYJKoZIhvcNAQEL +BQAwMzELMAkGA1UEBhMCUEwxDTALBgNVBAoMBFRlc3QxFTATBgNVBAMMDFRlc3Qg +Um9vdCBDQTAeFw0yNTA5MjUyMDQyMDlaFw0zNTA5MjMyMDQyMDlaMDMxCzAJBgNV +BAYTAlBMMQ0wCwYDVQQKDARUZXN0MRUwEwYDVQQDDAxUZXN0IFJvb3QgQ0EwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDwQYsYLOQyRG5nXHLzTuAXIDJl +nV6eoAOsDItqGCiOVy9T6Y0g+4RcSf99i2DRsOVLGg/jQHpAmo765XVObPiEmE+f +rxgLclXqXNlRQdiaK9LRPIUF0TCcyFJGTVMzHTFt0jHEdiP/2++egoAA/pP1al4y +XwJjWkGgLU7GA/Eou2Gfdc7mxZ4ZtmxkEh7OEqSOVgR+qi1fTfXfTyXF+s1PG4pu +tGNCg/zTh1rlMS9kWcoTF4Bk/RU5nKZ1zOAWOgffd72JiWyoGSYy0Yk2e1fhGgG5 +cWMZCDiOCAdNgwTtVKn8IfTgoZJpXhoegMVH44CDkfb7bkp3ETGfMzKncv4v5C2P +11HjWYOrmQ1bgpy+lR9RR+7Oem9So49UsayqqAYmquOCycnGT9wOX+4qsalnZd/O +J4mHtmUGiK0Lkvfh3X5T7wE7yLuiYJtG4XYwREZkBlusxGyX0lRvWJq3lI93EFqt +p7UWtB+1OjabUpCadypzkbvA19DJ4fhzaPh+A3tfn42RnlVAYAazNRiAy90G4/Mh +MPZzKqe0DTp2i2WG5/NTEivKoSVD27vVKp5Tk4LhgAMmZ/F4uFT0TBGva4q+tEUW +jv6mLKpCtQzgqsjNCSKL5pWnymDme7UN/mGe1ttU8xrI9pyGA5tSb9kO0VkBCcVd +R1mOOjZKx0iR3ctG+QIDAQABo2YwZDASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQU41jbLgGadHiLx0921bLDou0kHQEwHwYDVR0j +BBgwFoAU41jbLgGadHiLx0921bLDou0kHQEwDQYJKoZIhvcNAQELBQADggIBAN+W +xYNn4nGtkEfNaVP46GckMQU8Lq6asXIj/L830JHq+R0gJeiURBqBs5snRqBSx6/W +3Xn+IfFGUttnEPlAkxXsg5R/JnQnggfyQX7Hk3SwedKH0uUqX7NLqAv7tfZJPGU9 +HSuvOzThvSX8N3Vr0zLVJDT94WqXX69HoSp6BZnriVss5RWMvM51QsNirj5379CX +iU7BCQdKBQfGSCQW0Qr4GWZYZHuhpXHcsfrrQ66krdqGLgkOxe3xQgvFQSWEf4OU +d/pqlRIOCnku4g2JR/ph+tuLtxmHdidNBjP27mrtrKx4MsaqimxAYOuHTkni8cLF +01IDq95txBs1fShWE5ritJh5b03ZVmDS0uiVH2IGPBmxz08ysJdUAm6uGJWg3D5X +nJBpJbqzYe6wrZDB48s0yZwo6FX5gfoAG6OR0iWfXMsOrpMOxFz/A739JjxcoFDT +P5qct/z92obgFqp0w/RN/8Dotaw00l5P1IenCE42fLuARelrS8jFKrrjUr2+0Occ +CJ/3us1j7Ln5gYWSlWHTjDRwSyaji3Gi4mnduQUsdkIpI7grh4FGULNOLOZZf3Rj +fKlP9kW5m7MB196MYjQrQXTZM1ZUY5yEeCspsb0UaD78Oq5qXSFfGFZ63BmxPMvi +RzP8neThIVB948nZ0GYMc3SIHBFvwQpFZgkuz2+0 -----END CERTIFICATE----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central-system.crt b/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central-system.crt new file mode 100644 index 000000000..dd67e5dac --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central-system.crt @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIExzCCAq+gAwIBAgIUG2HcUzPbDD8biqumq+ISyohyeYIwDQYJKoZIhvcNAQEL +BQAwMzELMAkGA1UEBhMCUEwxDTALBgNVBAoMBFRlc3QxFTATBgNVBAMMDFRlc3Qg +Um9vdCBDQTAeFw0yNTA5MjUyMDQ5NDRaFw0yNjA5MjUyMDQ5NDRaMEkxCzAJBgNV +BAYTAlBMMRcwFQYDVQQKDA5FeGFtcGxlQ28gVGVzdDEhMB8GA1UEAwwYY2VudHJh +bC5zeXN0ZW0uc3ZjLmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEA6PdQdC5ol4niS1fiXwiJjEhfvPIAhj+LLLaHv1VG71sS9VmnsoEVJMfGqrkO +FkHrHFRFc1UJKi1se1r4NSlPsSPmTOj9KCgp5WSTxrww6X9hniWLCgC1S2fbmrWQ +O07D/qOGpO1GRgqL1KbVdHDZxhLa4MXevRnlgd2VcY1KaXT15BTQcJRJR+I9iIJF +HkUCdrLPjoJvS2G8gRezrRVC+EgrxTfJOQ2rcUunDDhn+f//cTulWjZ/R/Jy9Byy +qFTGPiwwnLkVBQGLhBSRNEYSvzxpgxipVOTLKPZYHNKyITibno3cYiaS+qCI0GG2 +wY7jrh44I6yQ/dCYMyuu7yxT0QIDAQABo4G8MIG5MAkGA1UdEwQCMAAwDgYDVR0P +AQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjA9BgNVHREE +NjA0ghhjZW50cmFsLnN5c3RlbS5zdmMubG9jYWyCB2NlbnRyYWyCCWxvY2FsaG9z +dIcEfwAAATAdBgNVHQ4EFgQUYLT4W+0UJcnlfU4VQMjbSHS5gYkwHwYDVR0jBBgw +FoAU41jbLgGadHiLx0921bLDou0kHQEwDQYJKoZIhvcNAQELBQADggIBALXQVFHu +z1GDyGNUNN5fEonbv4V8MGO8RU6I0O4ccuYqVGBvDUWVSMFtT4Qc60CcXZF2V8p6 +3FP6PAmDK12mMswWuc7lvgAldxOC+PwC4K8fbu9fl+KbP8lsEikVwqiLWXWOGiRh +xlgFhzYTvgEwa3Ta5hqQPsCnY5+/ybF4l7yxZgL0Qp/OWQJgtYd8AeAWpVayhpzw +WojpJ6x56PIZI9vJ00RmMOQib5fl6e4fKKj2ACt8uorG9kL/sWId2BnCJKFjSl8d +4krZr4ocGYK+yK7KgrunqAXy/NPk1hC/oRryaSznC3oh+83P3emjuf//t0FYhSUQ +g8Urku1v9916ulTM8DsF/eSr8z6BMod60fDrpaDnSY+4hcpJuOMfN1pOWcmQVejW +TgX+pwyKpRnvIOlm7NRz+gv31xMEu/McMQQ/oC9qYh7frOsO5pHt1kI7bJ2X69Dj +rz+y7SoW4Ur/pbevfyWu4kBMdo8Dj1zF2GwYFHDzjU0R814fBHEnkN5Jxnk+ahYl +yNwWnjphabPSGRx3nIgCJ600HvgAK1uKgdTCRoiYhkDxcve3m5wEE9UCgLrcp8HL +ushY02iXu9TsPuIA/3bBeLVeI0JxnyxYjP1YuGF2i2fxF/rZxhVXl12xc2oJAytA +6CRc2j0K3JcjgG5jdt3H36LwPnvQ9HPfF200 +-----END CERTIFICATE----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central-system.key b/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central-system.key new file mode 100644 index 000000000..22044072d --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central-system.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDo91B0LmiXieJL +V+JfCImMSF+88gCGP4sstoe/VUbvWxL1WaeygRUkx8aquQ4WQescVEVzVQkqLWx7 +Wvg1KU+xI+ZM6P0oKCnlZJPGvDDpf2GeJYsKALVLZ9uatZA7TsP+o4ak7UZGCovU +ptV0cNnGEtrgxd69GeWB3ZVxjUppdPXkFNBwlElH4j2IgkUeRQJ2ss+Ogm9LYbyB +F7OtFUL4SCvFN8k5DatxS6cMOGf5//9xO6VaNn9H8nL0HLKoVMY+LDCcuRUFAYuE +FJE0RhK/PGmDGKlU5Mso9lgc0rIhOJuejdxiJpL6oIjQYbbBjuOuHjgjrJD90Jgz +K67vLFPRAgMBAAECggEABeW00KwYE7X214drAJLbwIRYgBT0NHHJWSFpwEstV4PL +sBBL8XXZDixMeCflFmUmyXnMpEXDzKCHvXupCtd33/kTrGC9f9W8ccUhBIfhCRgj +ZXh305H/BOClK35rH0U4KusCzov/GmjL718lyiPNL3lstwHrSIguSiJM1SoJdy/l +aCIif6v5l8/DItDSavQxgI97AC0u7lLJadB460XqeJi3vYPzBg6WxEMMdqRhzzOH +1XIsv+IzHabJmt5J3wFsv2lk1v0Irny+CtWtZTM5mTVr4FcgefNx0t8pVSxRXf9F +DjXXbTSrlPVjZVPENrAr6Sl5YyJeK/UABiRl/BLxDQKBgQD3SQ0qZw41JsCFj6W7 +DGyKwFVNvFibzO2Hb6grHwV3iJKHnppFFpanEMhLiZzgdeTGpQFpR4iM6Ne8ewFQ +zu2P91cGjMBP4HqP/RWtGStZ6X/Br2I66sra6BXTsXGNXSr330xzYiwbDrKtr7rv +Q79ySfRRlwWpTeT6RubbqxB3dwKBgQDxLRPlVYaALeJ1rxuds59NeC5bJGHS8+1h +kiXQgKmG1/5saLUyWXWAT5FJqG/xvtiN+vtmf8jL+KhUYpga0kJeuykR7loJRGdr +7h4uMYmzrP7+5P6tNOBqawGCDZzutXMq2TIJAwy1s+tVE4KWWcv0qE8op4on+/f2 +8A+5HWNw9wKBgHGjU3aJ92B7l3uJQMsNcY/txQW9KScn7HwR1sFCNzvwOg4y14gq +Uj8iGjmEWuBXrTOQPm7IHbtLgWCvUjJ1dXx0WLy8z9+lNA2Za22ppF9kS36Rf129 +6kzg3K70207wYr+YEUTw933Tqk7g89HiW0dFLw6TjVl5X2GYVZzbJu0PAoGBAL6b +BLVkIXeeS/L8YJQDSOx+BgzsNQ/2zm4lhhNCDDlQ7XgaTNItF4s/1zBimY5yaU3U +xOmeJkDmFYsTnOjdsaySuIO+X5QhZqdLOrkBV7YUDDfBHXIgbxhL15ZEUfnql8mO +fFfY/CuCtYO4dqWC9Ik4l88mki7FmZSk55hCnLvDAoGBAKEjQS4GVd+9Ku/HVi71 +OG2vfKEyGfTyyOB/3c99BMWNOkQfMNrxuHR8XhTPi2LvFE9nndBlMuCLcRS417iL +Gvd7FazAaO1lRO1tlqOym1z2gx/j2k+2BrIV+vOzg3PEJeXZAihLHTSsfhNIc4xW +ZeR3z5nE4Pz9AOr/YFKi9Xqm +-----END PRIVATE KEY----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central.system.svc.local.crt b/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central.system.svc.local.crt deleted file mode 100644 index 79a6e5b2f..000000000 --- a/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central.system.svc.local.crt +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICDzCCAbSgAwIBAgIUMT7kDfFrbxHKvOzcUH467BHIZKAwCgYIKoZIzj0EAwIw -RTEdMBsGA1UEAwwURXhhbXBsZSBUZXN0IFJvb3QgQ0ExFzAVBgNVBAoMDkV4YW1w -bGVDbyBUZXN0MQswCQYDVQQGEwJQTDAeFw0yNTA4MjYxNTIzNDVaFw0yNjA4MjYx -NTI4NDVaMEkxITAfBgNVBAMMGGNlbnRyYWwuc3lzdGVtLnN2Yy5sb2NhbDEXMBUG -A1UECgwORXhhbXBsZUNvIFRlc3QxCzAJBgNVBAYTAlBMMFkwEwYHKoZIzj0CAQYI -KoZIzj0DAQcDQgAEJBVRkwiBKUn8osDqgeaXAlbGB8oYd0Awre++dGL70zCYHOJ6 -Av/o31sTfB9wZUpL36mvzGPbNX9KjRAxQbBzHKN+MHwwDAYDVR0TAQH/BAIwADA9 -BgNVHREENjA0ghhjZW50cmFsLnN5c3RlbS5zdmMubG9jYWyCB2NlbnRyYWyCCWxv -Y2FsaG9zdIcEfwAAATAOBgNVHQ8BAf8EBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUH -AwEGCCsGAQUFBwMCMAoGCCqGSM49BAMCA0kAMEYCIQCLOWlisiuOzD6FI46PrXrl -YYnZgaB2pAropRK6XZln6gIhANq2AEqyFrwuzuw70yBIoIiv3Z/bIyuVmGfoopzs -uYo4 ------END CERTIFICATE----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central.system.svc.local.key b/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central.system.svc.local.key deleted file mode 100644 index 121c305a8..000000000 --- a/Tokenization/backend/wrapper/src/test/testCerts/centralSystem/central.system.svc.local.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEICiXVtz9kxU7b/gyOQpEC4z9ECNYhyKz6K63+pErBRBPoAoGCCqGSM49 -AwEHoUQDQgAEJBVRkwiBKUn8osDqgeaXAlbGB8oYd0Awre++dGL70zCYHOJ6Av/o -31sTfB9wZUpL36mvzGPbNX9KjRAxQbBzHA== ------END EC PRIVATE KEY----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b-client.crt b/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b-client.crt new file mode 100644 index 000000000..08482178c --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b-client.crt @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEuDCCAqCgAwIBAgIURLvrnhcyzZ0UTbuI1tHGoXXFWc0wDQYJKoZIhvcNAQEL +BQAwMzELMAkGA1UEBhMCUEwxDTALBgNVBAoMBFRlc3QxFTATBgNVBAMMDFRlc3Qg +Um9vdCBDQTAeFw0yNTA5MjUyMDU2MTdaFw0yNjA5MjUyMDU2MTdaMEYxCzAJBgNV +BAYTAlBMMRcwFQYDVQQKDA5FeGFtcGxlQ28gVGVzdDEeMBwGA1UEAwwVY2xpZW50 +LWItY2xpZW50LmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +xxqgLBJFeQgLcRvkPj6S16guSJsJN/MJxGAnmWQF3utr9neUXeID2KUEMNUT2b73 +EO/LD3z+8JvDhfG4J9Y1/ny/CuaAbmHKXH/ot0nIwWjd11yXjhWyXQuNqGHiuniX +2KaDH5+rIPXYU6eUjx+V3VX0iiYEFUizPRUhzmJ8AONSu43OuMdhZo7Frb4qUc4/ +ioVqhiAb3Sdm/nKDWI9OQR6Ux8Mc2OaPY3wQA4r3ZBz9oJU3G7BL85bclmk1PDZh +R6T0/oo09FiTj8zTZ8vooarJH+TAD2EBsTwfIdBT1yuECAueztxaos2q2TUsJz3f +6hQaEnOqiz6nZ8FceXudgQIDAQABo4GwMIGtMAkGA1UdEwQCMAAwDgYDVR0PAQH/ +BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMDsGA1UdEQQ0MDKCFWNsaWVudC1i +LWNsaWVudC5sb2NhbIIIY2xpZW50LWKCCWxvY2FsaG9zdIcEfwAABTAdBgNVHQ4E +FgQUTzAFrkegQMRXfjt4djXC5N4YHnYwHwYDVR0jBBgwFoAU41jbLgGadHiLx092 +1bLDou0kHQEwDQYJKoZIhvcNAQELBQADggIBAB+vXi5ThGYQt7RUx92Vjy2++Yiz +WztajTms/ac2B2JZbW9dl1PYv+t5hrkiXp8Q21eWq7ZPbd+7QbzvQsOxSGEKNBQp +0A4CeI67YkbYjJPzxjpDxiYo8+wmOtGgWRok9P4FLN5dGVo9/3JQHrn+3m20/6DT +FqTqm+mC3FkSjAJzPnd1wOmPxS2xENE/L5x4KHR91xkCeaHXjQky9gQs7vzY4uto +kiuB/elxFq2l4XN4BI6A65261AyelB15qpTKBGICGNO56hIcrGIjoFyYQnKyJrk4 +yylH0LtACnEV0lzS9l7FtRNERQ5xDBRvcqK7X7XCwkKqXfYN04STiVEEqhSoYDF2 +INJeV2FN7CJQueCuiKqG1S+zd7uXJ9a7dx5Qk3boJVBfX2WjK5BbiSpjoq+x7tCG +zwtHpBDaB8K6Ee0XICFcDZlPB214XaGjrx6iavs5ppP6261f20QMI3xtTVPapMjT +bmC2AoARTdaIxoQaSyCE+QImVxkhHYBePpIHAnAZhBPzF46u+APRAOpklTJ6J+uv +VWCwm71ebXUTSZ7alsScqH+zDmYMYpGuar3qcPHnOJDo0XDoSMYl3kLDrK0UoHci +mtjRzuYBlKyEX8LcqY9teVuarnvxaNtYGbF98L3nFSLGnUfzaCMIbA4xtJcZ7DXu +J9l3OBcmHYWG52f2 +-----END CERTIFICATE----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.key b/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.key new file mode 100644 index 000000000..b2fc830da --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDHGqAsEkV5CAtx +G+Q+PpLXqC5Imwk38wnEYCeZZAXe62v2d5Rd4gPYpQQw1RPZvvcQ78sPfP7wm8OF +8bgn1jX+fL8K5oBuYcpcf+i3ScjBaN3XXJeOFbJdC42oYeK6eJfYpoMfn6sg9dhT +p5SPH5XdVfSKJgQVSLM9FSHOYnwA41K7jc64x2FmjsWtvipRzj+KhWqGIBvdJ2b+ +coNYj05BHpTHwxzY5o9jfBADivdkHP2glTcbsEvzltyWaTU8NmFHpPT+ijT0WJOP +zNNny+ihqskf5MAPYQGxPB8h0FPXK4QIC57O3FqizarZNSwnPd/qFBoSc6qLPqdn +wVx5e52BAgMBAAECggEAA0i8JZ3ziWiJj8cO/7vWfjom8UmlYEfg/F09qfkNY7zs +Xfdg+h91QsiOBiQtnKTavGvIJKxCJEPdeMMg739ICreSCyL8MVXpmZb+hq9v4UjS +h+/eDBjthT1gi8t5iuvcTVWJyia/Et8bP13/RFEYDruROgogfR1i33oOwbG8K+OM +iDSIRo9swBaVNqFuWZKqZr1pjY9KUfh4jnSA+x1hueVHREJYt6qvX2KNhHFhyfyo +RRuLSKmz7Xx3sTu7qQD1NRKsD/8lM4oZWGPTit0F6qx+FfzbI0nBn9In8czmrPcm +VqwdgT5aR5I6lfzAj6kMHlmY2n0f4J0Hk6Ha6MgkJQKBgQD6AnjeToUw6PcIbTrJ +18mDfzOCMhQjQHDXipHrJ06K65B+4XX8/oTkRahyeqn4sbKKHP11e6bsDPVNRK0P +9G3wYw7nPNcGtF1pojWvh0HxBTJ/iugDgZT5ngtwbydvAT7+m6De3DicEJo5ZUPP +IAweL+qf9Nh30PtKiOSgJ2GrRwKBgQDL3+eX0sQg+dfYb3zAJmsLwXoY4fAOGc1o +KI4UI26Bq4TPrJv9guaDjSYNE62M7+H7vWudlG+KPxt6jNaxxpIo5J1c7qVl+gx6 +kTDZ053peVLtWJzLrRT9/e238bCYnKfFpKwRzUGT9kEkMvmFqb56lnhKBXS/OPuP +3dAOWHvE9wKBgAJM4YXSHSGdEyDNuHvA84a1NekdwtesMR2alcsfGnbmwfaY5ngE +c36SMYGUJVo3cFga+i4JjDihyeQDHMCH1DchAjMYeTYDlNRy/KF30iCAlr1brtTR +bWh6jspjC27XCRhYoDtMtWyiLnkWuHAAcHwansMIArHfh2BhMBFVK23jAoGAGwrf +GFdfppQdWlsnbAFsj4mhXW2SvvwTL+65MdilTtPmcPmPU2gqlWaClpd2nMww6Ihu +nt9SkD7gsTe/PqN9PaldajdJfyZUw2lA1pPoTVDHfC4V1jpmH26wOob3ira01lWK +cW4NdcfjSh7s1Br45h/RYtgobTjsvV+Jum1oNW8CgYAeY82AEydYzqjZRH66xAjQ +pSxaT6B2YS5JqJFbRAtHA/ndApHbW7ALX5vqoJ6EkJ+aORQQMZUkX32n3Y0WFNE2 +FR9JEcnBjimwmG+JTBRCk1luamsZZpH/sHZohH1bsq9+dGo9xIeeBdi5DPxpdsKM +6NuWljVhDbuJeRiamlo5tg== +-----END PRIVATE KEY----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.pub.pem b/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.pub.pem new file mode 100644 index 000000000..cf74b90e7 --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.pub.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxxqgLBJFeQgLcRvkPj6S +16guSJsJN/MJxGAnmWQF3utr9neUXeID2KUEMNUT2b73EO/LD3z+8JvDhfG4J9Y1 +/ny/CuaAbmHKXH/ot0nIwWjd11yXjhWyXQuNqGHiuniX2KaDH5+rIPXYU6eUjx+V +3VX0iiYEFUizPRUhzmJ8AONSu43OuMdhZo7Frb4qUc4/ioVqhiAb3Sdm/nKDWI9O +QR6Ux8Mc2OaPY3wQA4r3ZBz9oJU3G7BL85bclmk1PDZhR6T0/oo09FiTj8zTZ8vo +oarJH+TAD2EBsTwfIdBT1yuECAueztxaos2q2TUsJz3f6hQaEnOqiz6nZ8FceXud +gQIDAQAB +-----END PUBLIC KEY----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.svc.local.crt b/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.svc.local.crt deleted file mode 100644 index d32418936..000000000 --- a/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.svc.local.crt +++ /dev/null @@ -1,13 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIB8jCCAZigAwIBAgIUFWvpRzaOwyQTl013JHSZr/0ZXwQwCgYIKoZIzj0EAwIw -RTEdMBsGA1UEAwwURXhhbXBsZSBUZXN0IFJvb3QgQ0ExFzAVBgNVBAoMDkV4YW1w -bGVDbyBUZXN0MQswCQYDVQQGEwJQTDAeFw0yNTA4MjYxNTIzNDVaFw0yNjA4MjYx -NTI4NDVaMEMxGzAZBgNVBAMMEmNsaWVudC1iLnN2Yy5sb2NhbDEXMBUGA1UECgwO -RXhhbXBsZUNvIFRlc3QxCzAJBgNVBAYTAlBMMFkwEwYHKoZIzj0CAQYIKoZIzj0D -AQcDQgAE2XWeWQcFI9FSD0AhcGbtq4xBCKslLGbBJxmUwTw7jS8XuWbo2R/C2W8c -CoNoqacuXOABty601v7vymh0fVvDZKNoMGYwDAYDVR0TAQH/BAIwADAnBgNVHREE -IDAeghJjbGllbnQtYi5zdmMubG9jYWyCCGNsaWVudC1iMA4GA1UdDwEB/wQEAwID -qDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwCgYIKoZIzj0EAwIDSAAw -RQIgNyD6+Fmm+0IKtCZnaRJsfbMpep55K/zJCekJsh0u+agCIQDLxllwAs2xXtCT -2Tf3Zr1gQ3qM0V20KiXhq8aBkMiKXw== ------END CERTIFICATE----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.svc.local.key b/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.svc.local.key deleted file mode 100644 index 68dff890a..000000000 --- a/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b.svc.local.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIP7Nw0K+xSwzXOpN6SL1tZsQo++26LwqWQsi33RKONiRoAoGCCqGSM49 -AwEHoUQDQgAE2XWeWQcFI9FSD0AhcGbtq4xBCKslLGbBJxmUwTw7jS8XuWbo2R/C -2W8cCoNoqacuXOABty601v7vymh0fVvDZA== ------END EC PRIVATE KEY----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientListenerServer/client-b-server.crt b/Tokenization/backend/wrapper/src/test/testCerts/clientListenerServer/client-b-server.crt new file mode 100644 index 000000000..949b07297 --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/clientListenerServer/client-b-server.crt @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqjCCApKgAwIBAgIUSKJ0QtJi4yK32QhGXdgKbfADC3owDQYJKoZIhvcNAQEL +BQAwMzELMAkGA1UEBhMCUEwxDTALBgNVBAoMBFRlc3QxFTATBgNVBAMMDFRlc3Qg +Um9vdCBDQTAeFw0yNTA5MjUyMDU2MDhaFw0yNjA5MjUyMDU2MDhaMD8xCzAJBgNV +BAYTAlBMMRcwFQYDVQQKDA5FeGFtcGxlQ28gVGVzdDEXMBUGA1UEAwwOY2xpZW50 +LWIubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHGqAsEkV5 +CAtxG+Q+PpLXqC5Imwk38wnEYCeZZAXe62v2d5Rd4gPYpQQw1RPZvvcQ78sPfP7w +m8OF8bgn1jX+fL8K5oBuYcpcf+i3ScjBaN3XXJeOFbJdC42oYeK6eJfYpoMfn6sg +9dhTp5SPH5XdVfSKJgQVSLM9FSHOYnwA41K7jc64x2FmjsWtvipRzj+KhWqGIBvd +J2b+coNYj05BHpTHwxzY5o9jfBADivdkHP2glTcbsEvzltyWaTU8NmFHpPT+ijT0 +WJOPzNNny+ihqskf5MAPYQGxPB8h0FPXK4QIC57O3FqizarZNSwnPd/qFBoSc6qL +PqdnwVx5e52BAgMBAAGjgakwgaYwCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCBaAw +EwYDVR0lBAwwCgYIKwYBBQUHAwEwNAYDVR0RBC0wK4IOY2xpZW50LWIubG9jYWyC +CGNsaWVudC1igglsb2NhbGhvc3SHBH8AAAQwHQYDVR0OBBYEFE8wBa5HoEDEV347 +eHY1wuTeGB52MB8GA1UdIwQYMBaAFONY2y4BmnR4i8dPdtWyw6LtJB0BMA0GCSqG +SIb3DQEBCwUAA4ICAQAfNbYmzhNKTJT+e4VZaJdqxFmsm2oHUtRXHVKHcPKYZEd5 +ujKtIjbdhjQ82Rhfmof9cydvAK8qEm+ydwUBvN/9q7Dd4V3rafKbsrVizB63HbSl +AZujvRxwIKF9Gzc3Sqliy1/LZYfk+FHHooUtzmL/K5cTVlHaBqT8m4zmqp4djFjQ +YnshmdaMBmgmgluO4/JyPswFHpRlKcp29GA8n39/+25yFyiIunryypCwPAFb/owh +sXVshhs04+JUwEdWGHoesbhjbIik706poPOlvUf9xHDcB6PXIwPmo08+1u2QEaV3 +Dqw4TjNcUA7OJdxzKhF0J4tVXAD1Hg2yrOYedtTeXDPntjgb3Uq4DWnAAJ+fMF1U +T1vJogzgzq6y5jl0KClqpSA9dOKt4IG2hL5WcoudyTk0ao4wkVdOQwR1vNz25Fub +LSl582PpHxvK3GYk4PegoPnHzz02IN4B+AxUDvXPH4HIZ4QtubnX4x4j8Kk4bPCK +ZBBA3t8K/6W/bjOB0Xh+LyXf9dIndVmFHC0iOf8YQgyNHhXhzpbpM/LWdDk3aEiG +6y99wNdWuu7F3T7wFk7dDhxxroHqxUjsP8921LD0JDqdryWQK6wE23fSpiThOEDN +o2FnBG3514QI/v6zlUWY4LtqJO2UCwPLbWAsuXd+AxWOO3urg0ucW0r1UNzmIQ== +-----END CERTIFICATE----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientListenerServer/client-b.key b/Tokenization/backend/wrapper/src/test/testCerts/clientListenerServer/client-b.key new file mode 100644 index 000000000..b2fc830da --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/clientListenerServer/client-b.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDHGqAsEkV5CAtx +G+Q+PpLXqC5Imwk38wnEYCeZZAXe62v2d5Rd4gPYpQQw1RPZvvcQ78sPfP7wm8OF +8bgn1jX+fL8K5oBuYcpcf+i3ScjBaN3XXJeOFbJdC42oYeK6eJfYpoMfn6sg9dhT +p5SPH5XdVfSKJgQVSLM9FSHOYnwA41K7jc64x2FmjsWtvipRzj+KhWqGIBvdJ2b+ +coNYj05BHpTHwxzY5o9jfBADivdkHP2glTcbsEvzltyWaTU8NmFHpPT+ijT0WJOP +zNNny+ihqskf5MAPYQGxPB8h0FPXK4QIC57O3FqizarZNSwnPd/qFBoSc6qLPqdn +wVx5e52BAgMBAAECggEAA0i8JZ3ziWiJj8cO/7vWfjom8UmlYEfg/F09qfkNY7zs +Xfdg+h91QsiOBiQtnKTavGvIJKxCJEPdeMMg739ICreSCyL8MVXpmZb+hq9v4UjS +h+/eDBjthT1gi8t5iuvcTVWJyia/Et8bP13/RFEYDruROgogfR1i33oOwbG8K+OM +iDSIRo9swBaVNqFuWZKqZr1pjY9KUfh4jnSA+x1hueVHREJYt6qvX2KNhHFhyfyo +RRuLSKmz7Xx3sTu7qQD1NRKsD/8lM4oZWGPTit0F6qx+FfzbI0nBn9In8czmrPcm +VqwdgT5aR5I6lfzAj6kMHlmY2n0f4J0Hk6Ha6MgkJQKBgQD6AnjeToUw6PcIbTrJ +18mDfzOCMhQjQHDXipHrJ06K65B+4XX8/oTkRahyeqn4sbKKHP11e6bsDPVNRK0P +9G3wYw7nPNcGtF1pojWvh0HxBTJ/iugDgZT5ngtwbydvAT7+m6De3DicEJo5ZUPP +IAweL+qf9Nh30PtKiOSgJ2GrRwKBgQDL3+eX0sQg+dfYb3zAJmsLwXoY4fAOGc1o +KI4UI26Bq4TPrJv9guaDjSYNE62M7+H7vWudlG+KPxt6jNaxxpIo5J1c7qVl+gx6 +kTDZ053peVLtWJzLrRT9/e238bCYnKfFpKwRzUGT9kEkMvmFqb56lnhKBXS/OPuP +3dAOWHvE9wKBgAJM4YXSHSGdEyDNuHvA84a1NekdwtesMR2alcsfGnbmwfaY5ngE +c36SMYGUJVo3cFga+i4JjDihyeQDHMCH1DchAjMYeTYDlNRy/KF30iCAlr1brtTR +bWh6jspjC27XCRhYoDtMtWyiLnkWuHAAcHwansMIArHfh2BhMBFVK23jAoGAGwrf +GFdfppQdWlsnbAFsj4mhXW2SvvwTL+65MdilTtPmcPmPU2gqlWaClpd2nMww6Ihu +nt9SkD7gsTe/PqN9PaldajdJfyZUw2lA1pPoTVDHfC4V1jpmH26wOob3ira01lWK +cW4NdcfjSh7s1Br45h/RYtgobTjsvV+Jum1oNW8CgYAeY82AEydYzqjZRH66xAjQ +pSxaT6B2YS5JqJFbRAtHA/ndApHbW7ALX5vqoJ6EkJ+aORQQMZUkX32n3Y0WFNE2 +FR9JEcnBjimwmG+JTBRCk1luamsZZpH/sHZohH1bsq9+dGo9xIeeBdi5DPxpdsKM +6NuWljVhDbuJeRiamlo5tg== +-----END PRIVATE KEY----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a-client.crt b/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a-client.crt new file mode 100644 index 000000000..72ddb5561 --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a-client.crt @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEuDCCAqCgAwIBAgIUTk9RrYkrtmjMiKjWusjyURgmSUEwDQYJKoZIhvcNAQEL +BQAwMzELMAkGA1UEBhMCUEwxDTALBgNVBAoMBFRlc3QxFTATBgNVBAMMDFRlc3Qg +Um9vdCBDQTAeFw0yNTA5MjUyMDU1MTVaFw0yNjA5MjUyMDU1MTVaMEYxCzAJBgNV +BAYTAlBMMRcwFQYDVQQKDA5FeGFtcGxlQ28gVGVzdDEeMBwGA1UEAwwVY2xpZW50 +LWEtY2xpZW50LmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +nJySLgwWLpLbt8EqFDfhKciKJGlxRUfcn/2u2EyxnkpKwwiLDSzKguZZ50xKviDt +Jao2FuZ251uyJDTfuPYt549OklkKIQYFSmP4MxNDAz501TVSJ45a9WQugScGc9lk +invmIADGBEa0rj2keRkT4MYvnWT2IGQJ91N99g9tDoQVPem5naHU1PxIwyxVVRIv +6mVCaro6OULqx6iFvDffvL0ef/5lbt8+vqX4QWwPH8rF5CvaV1KYYMkvQEXSn615 +8FE2YUepEN6wGEEDAIr98D5vNlgabkpLzxDULFT+tdk7v6Shb7bKql1W0QlBkEh/ +mFDCEkqOesGXLuzPG8pVAwIDAQABo4GwMIGtMAkGA1UdEwQCMAAwDgYDVR0PAQH/ +BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMDsGA1UdEQQ0MDKCFWNsaWVudC1h +LWNsaWVudC5sb2NhbIIIY2xpZW50LWGCCWxvY2FsaG9zdIcEfwAAAzAdBgNVHQ4E +FgQUScQWL/kot0/d3H9JQ9sZsO3xniAwHwYDVR0jBBgwFoAU41jbLgGadHiLx092 +1bLDou0kHQEwDQYJKoZIhvcNAQELBQADggIBALVJfl1YWb3mrXT51+pZ4pYJfY0Z +iXCGAMVLUtdnhgMmX5GVJhWvCAt+vwHggOJ+EA+1vw5CRe/GLDa5QurOtLtH6uBt +OjenYmOKRZ5f+mG8ZR20PyhH70m8DZ01OGlfuieFb8KgyYtEFNLCFZWxVDlvdwNL +2HVPSjM0JYudBELnTs4N8YZUTLGDRDZ9sz/KQYMJrSojN45k05qqr/EXWHwVDzVL +LefvuKi6H6DLzmU+oDy9TRcCydV4/h6i3MUxWm/IBrgoKIg4fmd/Evnen9KSc6md +yMHoKR6iHcha526txtUu4w0/0Le45yYxE1/eNm5jfMNwTFuoTbmlk+lItXd5Upn7 +1Pk+TF81WLl8prdvVoZPVYaKbj80JQlleQlWu76SaaY9ofkbRwP85npJmdl0wDCu +Bmil+ziBxzK73TT+UMBbRKRmXdeEh3fjQ8X4qlRNVPEarj1f2UiZ+45G/eFogA2r +EtVTqQ5MetNtWssgK0GFf2KeUIfXRdvYuFvLOhcd7uccxThq+o9KDFIulPzhy6uq +nu2AS8NELydQeHh6GjKqsxNoMS5l+YSzTGPvFWTYqzfRH5+h2J0H8Oex2Grb5C9A +35F8f35zLViv8C9mU32W9bSgcJElKaOumgBLbRtfrzHesFBFyOkTbtWnKJJJwXQH +7QZyDraKRsXdHAMr +-----END CERTIFICATE----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a-server.crt b/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a-server.crt new file mode 100644 index 000000000..23fc77a5d --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a-server.crt @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqjCCApKgAwIBAgIULDdlrJiTv9xZIx3QqggR9bBIgwswDQYJKoZIhvcNAQEL +BQAwMzELMAkGA1UEBhMCUEwxDTALBgNVBAoMBFRlc3QxFTATBgNVBAMMDFRlc3Qg +Um9vdCBDQTAeFw0yNTA5MjUyMDU1MDJaFw0yNjA5MjUyMDU1MDJaMD8xCzAJBgNV +BAYTAlBMMRcwFQYDVQQKDA5FeGFtcGxlQ28gVGVzdDEXMBUGA1UEAwwOY2xpZW50 +LWEubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCcnJIuDBYu +ktu3wSoUN+EpyIokaXFFR9yf/a7YTLGeSkrDCIsNLMqC5lnnTEq+IO0lqjYW5nbn +W7IkNN+49i3nj06SWQohBgVKY/gzE0MDPnTVNVInjlr1ZC6BJwZz2WSKe+YgAMYE +RrSuPaR5GRPgxi+dZPYgZAn3U332D20OhBU96bmdodTU/EjDLFVVEi/qZUJqujo5 +QurHqIW8N9+8vR5//mVu3z6+pfhBbA8fysXkK9pXUphgyS9ARdKfrXnwUTZhR6kQ +3rAYQQMAiv3wPm82WBpuSkvPENQsVP612Tu/pKFvtsqqXVbRCUGQSH+YUMISSo56 +wZcu7M8bylUDAgMBAAGjgakwgaYwCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCBaAw +EwYDVR0lBAwwCgYIKwYBBQUHAwEwNAYDVR0RBC0wK4IOY2xpZW50LWEubG9jYWyC +CGNsaWVudC1hgglsb2NhbGhvc3SHBH8AAAIwHQYDVR0OBBYEFEnEFi/5KLdP3dx/ +SUPbGbDt8Z4gMB8GA1UdIwQYMBaAFONY2y4BmnR4i8dPdtWyw6LtJB0BMA0GCSqG +SIb3DQEBCwUAA4ICAQAKtTGDurzAkajMDN+WqhiA6daqIstsRzLz9VnBwqIlWcOr +c1As4ah+YZSf2Qw1AMZ387fpk4oF2QZD4ZG7kigZdn5ricFVhBRMZUzJV1ommu2H +8Mub+oRyKQ/TtRqkq1JJqLKz7rDxBMM9LxSBPR4Nj2C4IVioxI5KYXYxlmqMeoYA +sMglGi8c3loRSy9LNwvcQu+UPI6kcFG+J0rfXJlWx10GRWIURudXt8oAAIVBLvSt +HR29TXWjOTULwqun0y5V4eksJek5jEhGTWuODAdPmCSSjAE4VSLECex/jql6jNFB +zmE9Q7vcss4zR9TASMeJYT3S+mXVb9sNf4ps+9rhx63tluSCH1vwtpMoQXucbIgo +tBUz+5gCIA7n1bMUJ8b1MajnTVH0nJa1ZWi0zTYnSd6WL0S0Se5exZ5Ws1ZWnFl9 +lVPCn2Mt8agRu0s0VAT7t4nY4VjHTDqjj9Z99tcfUWCO8gAAR28kkqdRYxrgVMkx +pv8IwTt0tBldDnpwdCqBnXP75sta4Gq7IOpe0oQB6kizWqbII84tYSxUch9SkkaH +rE7BhGtUywAJxc+dnAFuePuu6BE2ZsQK86FpuHYIxR6DU7hH1i8258qxGt0/EVBg +ekhyT6tFaAWl5N+OVmEu1JvNdqNiw6sJc+xy9AcviWlAOvkGd2Aw0eTRNQLboQ== +-----END CERTIFICATE----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.key b/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.key new file mode 100644 index 000000000..7377803ae --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCcnJIuDBYuktu3 +wSoUN+EpyIokaXFFR9yf/a7YTLGeSkrDCIsNLMqC5lnnTEq+IO0lqjYW5nbnW7Ik +NN+49i3nj06SWQohBgVKY/gzE0MDPnTVNVInjlr1ZC6BJwZz2WSKe+YgAMYERrSu +PaR5GRPgxi+dZPYgZAn3U332D20OhBU96bmdodTU/EjDLFVVEi/qZUJqujo5QurH +qIW8N9+8vR5//mVu3z6+pfhBbA8fysXkK9pXUphgyS9ARdKfrXnwUTZhR6kQ3rAY +QQMAiv3wPm82WBpuSkvPENQsVP612Tu/pKFvtsqqXVbRCUGQSH+YUMISSo56wZcu +7M8bylUDAgMBAAECggEANyBDsjKt8inebjFvmttKhfchbQyygsT3S1ez3k4srT+Q +TlNpArOz+tyTY7+uhXs4jmv6CxiHXQuhSm5UG5qH8Py4FvqBfrtMTHGg8XWDvpYS +8OOKbgMFUGA5oFt4wXmRks9m4vfyu5mZysVG6htiLFoGc5wQqLkd6vF4Io8uf4+A +Dd/oY85K7x/JnrNQaF7LmpeqTMRlhtzSzGNVqEwT9b9FoQRADGHrg1ksY5xXixAw +Jf+wvJqd6AaoxzK0rDuZiQpVnjepJ6aeip61RL7coc+yAlkXhfDOWvzWCefRFUTf +Q/iTyOset8ejRg2mF8InLUKYC2kwKLeucVvFkijz2QKBgQDbgf5L2aK/lUEQVtQf +ueqCc89PJZHU34tEXRC0doGYLQnhZQPVvLeTv+CkZ7GibUxPiMLdtzzfyFxlLbs+ +gUxBXV540hI3acpDqdiiZmHeYtMPvyjUkkP1ymQSnLwwjOkBh7Wt/+zMBEHtO8hz +Mo5vfoV6JV+b+JQg2f7cyd1t2wKBgQC2pclNJ1Dj58Vgmfd2EZhZgG0XRVPfR7PT +QjEIcnFmmNvEiN83dYqYw8fVOegcXrCFMTP3aW6ONoGnk+owhTSBtEfYNCKOwIi9 +GHM+MJFNIgxxPM+xSvpxHtAF9meYFkMRqjMJPM8ICz04Uz3AsfbdveYwUNMudSap +znigNKfh+QKBgDCvvH+GXhqwOCYvnA0NZ35XwXuEkbvteS5IlhPw1P2zv6VGins1 +yGH1BRZyCWxFYc+iPdZ/dfkMr7GhWw6aDxfQZcvWjEPOKxam7W3X141DzhyIAb5k +Ur6JjXizWupJ1sSIHTvir9rwds7vm54xcHY6UdCtyW8Gy5QdxfGitIJRAoGAbJ2I +aT5RJ0bEJJ9K/saV39u0hBsxNl2QfbgmKozMDSQnxOdUPsnCgvgiVRXbh0t0E7Df +42iqWx3k2n/my7XbNKq98r+GMXgjmLf6iGgfcEwoNAriw97/sdeOA421q0bJ2a5q +LTshLvpoDJ/L4FS0psbwJZlbDIyUUnS7XSITGBkCgYBNvMefT86P85yN5nDyxRjO +lCitw08NjE+6WZWL6BTbRqVovFG2HAaWGEjG2+bpApy4S4AN3NmmOby38ZZDR7bJ +bQxCHRt61yqX6IphkRrzv8K7DbrN3jnKO2FN8TBWwcvFzx2d5hl8Nsv2OmEyixZZ +ySbU4WOBCdu/mJy/+Xb9gA== +-----END PRIVATE KEY----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.pub.pem b/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.pub.pem new file mode 100644 index 000000000..f565de4a2 --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.pub.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnJySLgwWLpLbt8EqFDfh +KciKJGlxRUfcn/2u2EyxnkpKwwiLDSzKguZZ50xKviDtJao2FuZ251uyJDTfuPYt +549OklkKIQYFSmP4MxNDAz501TVSJ45a9WQugScGc9lkinvmIADGBEa0rj2keRkT +4MYvnWT2IGQJ91N99g9tDoQVPem5naHU1PxIwyxVVRIv6mVCaro6OULqx6iFvDff +vL0ef/5lbt8+vqX4QWwPH8rF5CvaV1KYYMkvQEXSn6158FE2YUepEN6wGEEDAIr9 +8D5vNlgabkpLzxDULFT+tdk7v6Shb7bKql1W0QlBkEh/mFDCEkqOesGXLuzPG8pV +AwIDAQAB +-----END PUBLIC KEY----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.svc.local.crt b/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.svc.local.crt deleted file mode 100644 index a6d5025b6..000000000 --- a/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.svc.local.crt +++ /dev/null @@ -1,13 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIB8jCCAZigAwIBAgIUa4ibeAgx6Ga3UklyqWn8uEtEcQcwCgYIKoZIzj0EAwIw -RTEdMBsGA1UEAwwURXhhbXBsZSBUZXN0IFJvb3QgQ0ExFzAVBgNVBAoMDkV4YW1w -bGVDbyBUZXN0MQswCQYDVQQGEwJQTDAeFw0yNTA4MjYxNTIzNDVaFw0yNjA4MjYx -NTI4NDVaMEMxGzAZBgNVBAMMEmNsaWVudC1hLnN2Yy5sb2NhbDEXMBUGA1UECgwO -RXhhbXBsZUNvIFRlc3QxCzAJBgNVBAYTAlBMMFkwEwYHKoZIzj0CAQYIKoZIzj0D -AQcDQgAEU9voQyzju4VEGHJd/5KWROHG1ZqjP8ClUT8qxQ4fo7vQTQAitXYxr4sh -jfI4LvX63Yd/FzbTiFD3vlIJHdMrnqNoMGYwDAYDVR0TAQH/BAIwADAnBgNVHREE -IDAeghJjbGllbnQtYS5zdmMubG9jYWyCCGNsaWVudC1hMA4GA1UdDwEB/wQEAwID -qDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwCgYIKoZIzj0EAwIDSAAw -RQIgCqma8A91M48a9t4f/b38nCEgG2LQ0TRLiWqMw7G+3vwCIQDcMDbM9Beuh1ap -JudMwo61iUWxeF+TLgapxxO/R4jWqA== ------END CERTIFICATE----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.svc.local.key b/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.svc.local.key deleted file mode 100644 index 9531bc087..000000000 --- a/Tokenization/backend/wrapper/src/test/testCerts/clientSender/client-a.svc.local.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIL9ACEBWYWkPUs6t9ZbrWETL1kpsPe/pxKQIqnqHlWyroAoGCCqGSM49 -AwEHoUQDQgAEU9voQyzju4VEGHJd/5KWROHG1ZqjP8ClUT8qxQ4fo7vQTQAitXYx -r4shjfI4LvX63Yd/FzbTiFD3vlIJHdMrng== ------END EC PRIVATE KEY----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/testCerts.ts b/Tokenization/backend/wrapper/src/test/testCerts/testCerts.ts index c37742655..dcb90aa19 100644 --- a/Tokenization/backend/wrapper/src/test/testCerts/testCerts.ts +++ b/Tokenization/backend/wrapper/src/test/testCerts/testCerts.ts @@ -7,11 +7,11 @@ export const getTestCentralCertPaths = const CA_CERT_PATH = path.join(__dirname, "./ca.crt"); const SERVER_CERT_PATH = path.join( __dirname, - "./centralSystem/central.system.svc.local.crt" + "./centralSystem/central-system.crt" ); const SERVER_KEY_PATH = path.join( __dirname, - "./centralSystem/central.system.svc.local.key" + "./centralSystem/central-system.key" ); return { @@ -26,11 +26,11 @@ export const getTestClientListenerCertPaths = const CA_CERT_PATH = path.join(__dirname, "./ca.crt"); const CLIENT_CERT_PATH = path.join( __dirname, - "./clientListener/client-b.svc.local.crt" + "./clientListener/client-b-client.crt" ); const CLIENT_KEY_PATH = path.join( __dirname, - "./clientListener/client-b.svc.local.key" + "./clientListener/client-b.key" ); return { @@ -40,17 +40,33 @@ export const getTestClientListenerCertPaths = }; }; -export const getTestClientSenderCertPaths = +export const getTestClientListenerServerCertPaths = (): gRPCWrapperConfig["clientCerts"] => { const CA_CERT_PATH = path.join(__dirname, "./ca.crt"); const CLIENT_CERT_PATH = path.join( __dirname, - "./clientSender/client-a.svc.local.crt" + "./clientListenerServer/client-b-server.crt" ); const CLIENT_KEY_PATH = path.join( __dirname, - "./clientSender/client-a.svc.local.key" + "./clientListenerServer/client-b.key" + ); + + return { + caCertPath: CA_CERT_PATH, + certPath: CLIENT_CERT_PATH, + keyPath: CLIENT_KEY_PATH, + }; + }; + +export const getTestClientSenderCertPaths = + (): gRPCWrapperConfig["clientCerts"] => { + const CA_CERT_PATH = path.join(__dirname, "./ca.crt"); + const CLIENT_CERT_PATH = path.join( + __dirname, + "./clientSender/client-a-client.crt" ); + const CLIENT_KEY_PATH = path.join(__dirname, "./clientSender/client-a.key"); return { caCertPath: CA_CERT_PATH, @@ -63,11 +79,11 @@ export const getTestCerts = () => { const CA_CERT_PATH = path.join(__dirname, "./ca.crt"); const SERVER_CERT_PATH = path.join( __dirname, - "./centralSystem/central.system.svc.local.crt" + "./centralSystem/central-system.crt" ); const SERVER_KEY_PATH = path.join( __dirname, - "./centralSystem/central.system.svc.local.key" + "./centralSystem/central-system.key" ); const caCert = fs.readFileSync(CA_CERT_PATH); From 1ce05882d273d5cc73e1adac09cb3c95668fc15d Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki <42175519+OmegaCreations@users.noreply.github.com> Date: Fri, 26 Sep 2025 09:46:24 +0200 Subject: [PATCH 064/103] Potential fix for code scanning alert no. 252: Unused variable, import, function or class Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- Tokenization/backend/wrapper/src/client/Connection/Connection.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts index b4ca2e597..7e4a75e40 100644 --- a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts +++ b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts @@ -20,7 +20,6 @@ import { FetchResponse, } from "../../models/connection.model"; import * as grpc from "@grpc/grpc-js"; -import * as fs from "fs"; /** * @description This class represents a connection to a target client and manages sending messages to it. From 27f7015d4ad4079fa645a5b1cc8dc9911ce76d32 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Sat, 27 Sep 2025 19:50:28 +0200 Subject: [PATCH 065/103] feat: add implementation of auth interceptor --- .../backend/wrapper/package-lock.json | 29 ++++ Tokenization/backend/wrapper/package.json | 1 + .../ConnectionManager/ConnectionManager.ts | 17 ++- .../Interceptors/grpc.auth.interceptor.ts | 140 ++++++++++++++++++ .../backend/wrapper/src/client/gRPCWrapper.ts | 27 +++- .../wrapper/src/models/config.model.ts | 8 +- .../wrapper/src/models/connection.model.ts | 2 + 7 files changed, 212 insertions(+), 12 deletions(-) create mode 100644 Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts diff --git a/Tokenization/backend/wrapper/package-lock.json b/Tokenization/backend/wrapper/package-lock.json index cd3e9595a..4a2bef0f0 100644 --- a/Tokenization/backend/wrapper/package-lock.json +++ b/Tokenization/backend/wrapper/package-lock.json @@ -11,12 +11,14 @@ "@grpc/grpc-js": "^1.13.4", "@grpc/proto-loader": "^0.7.15", "express": "^5.1.0", + "jose": "^6.1.0", "ts-node": "^10.9.2", "typescript": "^5.8.3" }, "devDependencies": { "@types/express": "^5.0.3", "@types/jest": "^29.5.14", + "@types/jsonwebtoken": "^9.0.10", "jest": "^29.7.0", "ts-jest": "^29.4.0", "tsc-alias": "^1.8.16" @@ -1254,6 +1256,17 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -1261,6 +1274,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.0.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.0.tgz", @@ -3657,6 +3677,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jose": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.0.tgz", + "integrity": "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/Tokenization/backend/wrapper/package.json b/Tokenization/backend/wrapper/package.json index e099eeaab..9a23e0b19 100644 --- a/Tokenization/backend/wrapper/package.json +++ b/Tokenization/backend/wrapper/package.json @@ -19,6 +19,7 @@ "@grpc/grpc-js": "^1.13.4", "@grpc/proto-loader": "^0.7.15", "express": "^5.1.0", + "jose": "^6.1.0", "ts-node": "^10.9.2", "typescript": "^5.8.3" } diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts index 4f8ffc756..27f4a8842 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts @@ -25,6 +25,7 @@ import { } from "../../models/message.model"; import { ConnectionStatus } from "../../models/connection.model"; import * as fs from "fs"; +import { gRPCAuthInterceptor } from "./Interceptors/grpc.auth.interceptor"; /** * @description Manages all the connection between clients and central system. @@ -245,8 +246,9 @@ export class ConnectionManager { /** Starts a listener server for p2p connections */ public async listenForPeers( port: number, - listenerKey: NonSharedBuffer, + listenerPublicKey: NonSharedBuffer, listenerCert: NonSharedBuffer, + listenerPrivateKey: NonSharedBuffer, baseAPIPath?: string ): Promise { if (baseAPIPath) this.baseAPIPath = baseAPIPath; @@ -262,6 +264,15 @@ export class ConnectionManager { call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData ) => { + // run auth interceptor + gRPCAuthInterceptor( + call, + callback, + this.receivingConnections, + listenerPrivateKey, + listenerPublicKey + ); + try { const clientAddress = call.getPeer(); this.logger.infoMessage(`Incoming request from ${clientAddress}`); @@ -334,7 +345,7 @@ export class ConnectionManager { this.caCert, [ { - private_key: listenerKey, + private_key: listenerPublicKey, cert_chain: listenerCert, }, ], @@ -349,4 +360,6 @@ export class ConnectionManager { this.logger.infoMessage(`Peer server listening on localhost:${port}`); } + + private createPeerAuthInterceptor() {} } diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts new file mode 100644 index 000000000..938097a62 --- /dev/null +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts @@ -0,0 +1,140 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import * as grpc from "@grpc/grpc-js"; +import { Connection } from "client/Connection/Connection"; +import { importPKCS8, importJWK, compactDecrypt, compactVerify } from "jose"; + +interface TokenPayload { + serialNumber: string; + allowedRequests: ("POST" | "GET" | "PUT" | "DELETE" | "PATCH")[]; +} + +export const gRPCAuthInterceptor = async ( + call: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData, + clientConnections: Map, + privateKeyBuffer: NonSharedBuffer, + publicKeyBuffer: NonSharedBuffer +): Promise => { + const metadata = call.metadata.getMap(); + const jweToken = metadata.token as string; + + // check if token exists + if (!jweToken) { + const error = { + name: "AuthenticationError", + message: "No token provided", + code: grpc.status.UNAUTHENTICATED, + }; + + callback(error, null); + return false; + } + + // validate JWE (encrypted JWS) - decode JWE -> JWS + let privateKey: any; + let jwsToken: any; + try { + privateKey = await importPKCS8( + privateKeyBuffer.toString("utf-8"), + "RSA-OAEP-256" + ); + const { plaintext } = await compactDecrypt(jweToken, privateKey); // decrypt JWE token + jwsToken = plaintext.toString(); + } catch (_e) { + const error = { + name: "AuthenticationError", + message: "Incorrect token provided", + code: grpc.status.UNAUTHENTICATED, + }; + + // TODO?: inform central system about incorrect token coming for peer + // or create counter with incorrect tries and then inform central system + // it potentially might be an attack here. + + callback(error, null); + return false; + } + + // check if connection is blocked + const conn = clientConnections.get(); + + // validate JWS signature + let publicKey: any; + let payload: TokenPayload; + try { + publicKey = await importJWK(JSON.parse(publicKeyBuffer.toString())); + const { payload: jwtPayload } = await compactVerify(jwsToken, publicKey); + + const payloadString = new TextDecoder().decode(jwtPayload); + payload = JSON.parse(payloadString); + } catch (e: any) { + const error = { + name: "AuthenticationError", + message: `JWS ${ + e.message.includes("expired") ? "Expiration" : "Verification" + } error`, + code: e.message.includes("expired") + ? grpc.status.UNAUTHENTICATED + : grpc.status.PERMISSION_DENIED, + }; + + // TODO?: inform central system about incorrect token coming for peer + // or create counter with incorrect tries and then inform central system + // it potentially might be an attack here. + + callback(error, null); + return false; + } + + // Connection tunnel verification with SN + const peerCert = (call as any).getPeerCertificate(); // its not publicly exposed + const clientSerialNumber = peerCert ? peerCert.serialNumber : null; + const tokenSerialNumber = payload.serialNumber; // Serial number is inside payload + + if (!clientSerialNumber || tokenSerialNumber !== clientSerialNumber) { + const error = { + name: "AuthenticationError", + code: grpc.status.PERMISSION_DENIED, + message: "Serial number mismatch.", + } as any; + + // TODO?: inform central system about incorrect token coming for peer + // or create counter with incorrect tries and then inform central system + // it potentially might be an attack here. + + callback(error, null); + return false; + } + + // Validate permission for request method + const method = String(call.request?.method || "POST").toUpperCase(); + if (!payload.allowedRequests.includes(method as any)) { + const error = { + name: "AuthorizationError", + code: grpc.status.PERMISSION_DENIED, + message: `Request of type ${method} is not allowed.`, + } as any; + + // TODO?: inform central system about incorrect token coming for peer + // or create counter with incorrect tries and then inform central system + // it potentially might be an attack here. + + callback(error, null); + return false; + } + + return true; +}; diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index 5be3e7ee3..b8d617abc 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -40,7 +40,8 @@ import { LogManager } from "@aliceo2/web-ui"; */ export class gRPCWrapper { private ConnectionManager: ConnectionManager; - private listenerKey?: NonSharedBuffer; + private listenerPublicKey?: NonSharedBuffer; + private listenerPrivateKey?: NonSharedBuffer; private listenerCert?: NonSharedBuffer; private logger = LogManager.getLogger("gRPCWrapper"); @@ -57,16 +58,21 @@ export class gRPCWrapper { !config.clientCerts || !config.clientCerts.caCertPath || !config.clientCerts.certPath || - !config.clientCerts.keyPath + !config.clientCerts.publicKeyPath ) { throw new Error("Invalid gRPCWrapper configuration provided."); } if ( - config.listenerCertPaths?.keyPath && + config.listenerCertPaths?.publicKeyPath && config.listenerCertPaths?.certPath ) { - this.listenerKey = fs.readFileSync(config.listenerCertPaths.keyPath); + this.listenerPublicKey = fs.readFileSync( + config.listenerCertPaths.publicKeyPath + ); + this.listenerPrivateKey = fs.readFileSync( + config.listenerCertPaths.privateKeyPath + ); this.listenerCert = fs.readFileSync(config.listenerCertPaths.certPath); } @@ -75,7 +81,7 @@ export class gRPCWrapper { config.centralAddress, config.clientCerts.caCertPath, config.clientCerts.certPath, - config.clientCerts.keyPath + config.clientCerts.publicKeyPath ); this.ConnectionManager.registerCommandHandlers([ { @@ -119,11 +125,15 @@ export class gRPCWrapper { listenerCertPaths?: { keyPath: string; certPath: string } ): Promise { if (listenerCertPaths?.keyPath && listenerCertPaths?.certPath) { - this.listenerKey = fs.readFileSync(listenerCertPaths.keyPath); + this.listenerPublicKey = fs.readFileSync(listenerCertPaths.keyPath); this.listenerCert = fs.readFileSync(listenerCertPaths.certPath); } - if (!this.listenerKey || !this.listenerCert) { + if ( + !this.listenerPublicKey || + !this.listenerPrivateKey || + !this.listenerCert + ) { this.logger.errorMessage( "Listener certificates are required to start P2P listener. Please provide valid paths." ); @@ -132,8 +142,9 @@ export class gRPCWrapper { return this.ConnectionManager.listenForPeers( port, - this.listenerKey, + this.listenerPublicKey, this.listenerCert, + this.listenerPrivateKey, baseAPIPath ); } diff --git a/Tokenization/backend/wrapper/src/models/config.model.ts b/Tokenization/backend/wrapper/src/models/config.model.ts index 6d6b18555..2e4e9e9ac 100644 --- a/Tokenization/backend/wrapper/src/models/config.model.ts +++ b/Tokenization/backend/wrapper/src/models/config.model.ts @@ -37,10 +37,14 @@ export interface gRPCWrapperConfig { /** Client TLS certificates paths. */ clientCerts: { caCertPath: string; - keyPath: string; + publicKeyPath: string; certPath: string; }; /** Optional listener TLS certificates paths. If provided, the gRPCWrapper will be able to accept incoming connections. */ - listenerCertPaths?: { keyPath: string; certPath: string }; + listenerCertPaths?: { + publicKeyPath: string; + privateKeyPath: string; + certPath: string; + }; } diff --git a/Tokenization/backend/wrapper/src/models/connection.model.ts b/Tokenization/backend/wrapper/src/models/connection.model.ts index f18c24fa4..f624cb312 100644 --- a/Tokenization/backend/wrapper/src/models/connection.model.ts +++ b/Tokenization/backend/wrapper/src/models/connection.model.ts @@ -27,6 +27,8 @@ export enum ConnectionStatus { RECONNECTING = "RECONNECTING", // The connection is refreshing its authentication token TOKEN_REFRESH = "TOKEN_REFRESH", + // The connection has been blocked + BLOCKED = "BLOCKED", } export type ConnectionHeaders = Record; From e0da42496d71bad9912185b7dbcb7399fc8481b0 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Sun, 28 Sep 2025 13:23:39 +0200 Subject: [PATCH 066/103] feat: update connection for tokens, fix decryption in auth interceptor, fix tests --- .../src/client/Connection/Connection.ts | 153 ++++++++++++++---- .../ConnectionManager/ConnectionManager.ts | 86 +++++----- .../Interceptors/grpc.auth.interceptor.ts | 140 +++++++++------- .../wrapper/src/models/connection.model.ts | 5 + .../src/test/client/Commands/newToken.test.ts | 35 ++-- .../test/client/Commands/revokeToken.test.ts | 28 +++- .../ConnectionManager.test.ts | 21 ++- .../wrapper/src/test/testCerts/testCerts.ts | 6 +- 8 files changed, 314 insertions(+), 160 deletions(-) diff --git a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts index 7e4a75e40..4ba96f9b2 100644 --- a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts +++ b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts @@ -18,90 +18,151 @@ import { ConnectionStatus, FetchOptions, FetchResponse, + TokenPayload, } from "../../models/connection.model"; import * as grpc from "@grpc/grpc-js"; +import { LogManager } from "@aliceo2/web-ui"; + +type ConnectionCerts = { + caCert: NonSharedBuffer; + clientCert: NonSharedBuffer; + clientKey: NonSharedBuffer; +}; /** * @description This class represents a connection to a target client and manages sending messages to it. */ export class Connection { - private token: string; + private jweToken: string; private status: ConnectionStatus; private peerClient?: any; // a client grpc connection instance + // security management variables + private clientSerialNumber?: string; // The certificate SN used to uniquely identify the peer. + private lastActiveTimestamp: number; // Timestamp of the last successful request (for garbage collection). + private authFailures: number; // Counter for consecutive authentication failures (for anti-DDoS/throttling). + private cachedTokenPayload?: TokenPayload; // Cache of the successfully verified token payload. + public targetAddress: string; public direction: ConnectionDirection; + // utils + private logger; + /** * @description Creates a new Connection instance with the given token, target address, and connection direction. * - * @param token - The authentication token for the connection. + * @param jweToken - The encrypted JWE token for the connection. * @param targetAddress - The unique address of the target client. * @param direction - The direction of the connection (e.g., sending or receiving). - * @param peerCtor - The constructor for the gRPC client to be used for communication. - * @param caCertPath - Path to the CA certificate file. - * @param clientCertPath - Path to the client certificate file. - * @param clientKeyPath - Path to the client key file. + * @param clientSN - Optional serial number of the peer's certificate (used for lookups). */ constructor( - token: string, + jweToken: string, targetAddress: string, direction: ConnectionDirection, - peerCtor: any, - private readonly connectionCerts: { - caCert: NonSharedBuffer; - clientCert: NonSharedBuffer; - clientKey: NonSharedBuffer; - } + clientSN?: string ) { - this.token = token; + this.jweToken = jweToken; this.targetAddress = targetAddress; this.direction = direction; + // Initialize state fields + this.clientSerialNumber = clientSN; + this.lastActiveTimestamp = Date.now(); + this.authFailures = 0; + this.status = ConnectionStatus.CONNECTED; + + this.logger = LogManager.getLogger(`Connection ${targetAddress}`); + } + + /** + * @description Creates the mTLS gRPC client and attaches it to the connection. + * This method is REQUIRED ONLY for outbound (SENDING) connections. + * * @param peerCtor - The constructor for the gRPC client to be used for communication. + * @param connectionCerts - Required sending client certificates for mTLS. + */ + public createSslTunnel( + peerCtor: any, + connectionCerts: ConnectionCerts + ): void { + if (this.direction !== ConnectionDirection.SENDING) { + this.logger.warnMessage( + "Attempted to create SSL tunnel on a RECEIVING connection. This is usually unnecessary." + ); + } + if ( !connectionCerts.caCert || !connectionCerts.clientCert || !connectionCerts.clientKey ) { throw new Error( - "Connection certificates are required to create a Connection." + "Connection certificates are required to create an mTLS tunnel." ); } // create grpc credentials const sslCreds = grpc.credentials.createSsl( - this.connectionCerts.caCert, - this.connectionCerts.clientKey, - this.connectionCerts.clientCert + connectionCerts.caCert, + connectionCerts.clientKey, + connectionCerts.clientCert ); - this.peerClient = new peerCtor(targetAddress, sslCreds); - - this.status = ConnectionStatus.CONNECTED; + this.peerClient = new peerCtor(this.targetAddress, sslCreds); + this.updateStatus(ConnectionStatus.CONNECTED); } /** * @description Replace newly generated token - * @param token New token to be replaced + * @param jweToken New token to be replaced */ - public handleNewToken(token: string): void { - this.token = token; + public handleNewToken(jweToken: string): void { + this.jweToken = jweToken; } /** * @description Revoke current token and set status of unauthorized connection */ public handleRevokeToken(): void { - this.token = ""; + this.jweToken = ""; this.status = ConnectionStatus.UNAUTHORIZED; } + /** + * @description Handles a successful authentication event. Updates the active timestamp, + * resets the failure counter, and caches the new token payload. + * This is crucial for high-performance applications to avoid re-validating the same token. + * @param payload The decoded and verified token payload. + */ + public handleSuccessfulAuth(payload: TokenPayload): void { + this.lastActiveTimestamp = Date.now(); + this.authFailures = 0; + this.cachedTokenPayload = payload; + this.updateStatus(ConnectionStatus.CONNECTED); + } + + /** + * @description Handles an authentication failure. Increments the failure counter. + * If the failure count exceeds a local threshold, the connection is locally marked as BLOCKED. + * @returns The new count of consecutive failures. + */ + public handleFailedAuth(): number { + this.authFailures += 1; + + // Local throttling mechanism + if (this.authFailures >= 5) { + this.updateStatus(ConnectionStatus.BLOCKED); + } + return this.authFailures; + } + /** * @description Returns token for this Connection object * @returns Connection token */ public getToken(): string { - return this.token; + return this.jweToken; } /** @@ -128,6 +189,39 @@ export class Connection { return this.targetAddress; } + /** + * @description Returns the client's Serial Number (SN). + * @returns The client's serial number or undefined. + */ + public getSerialNumber(): string | undefined { + return this.clientSerialNumber; + } + + /** + * @description Sets the client's Serial Number. Primarily used for RECEIVING connections + * where the SN is extracted during the first mTLS handshake in the interceptor. + * @param serialNumber The serial number string. + */ + public setSerialNumber(serialNumber: string): void { + this.clientSerialNumber = serialNumber; + } + + /** + * @description Returns the timestamp of the last successful interaction. + * @returns UNIX timestamp in milliseconds. + */ + public getLastActiveTimestamp(): number { + return this.lastActiveTimestamp; + } + + /** + * @description Returns the cached token payload. + * @returns The cached payload or undefined. + */ + public getCachedTokenPayload(): TokenPayload | undefined { + return this.cachedTokenPayload; + } + /** * @description Attaches gRPC client to that connection */ @@ -151,6 +245,11 @@ export class Connection { const path = options.path || "/"; const headers: ConnectionHeaders = { ...(options.headers || {}) }; + // set mandatory grpc metadata + const metadata = new grpc.Metadata(); + metadata.set("jweToken", this.jweToken); + + // build body buffer let bodyBuf: Buffer = Buffer.alloc(0); const b = options.body; if (b != null) { @@ -167,7 +266,7 @@ export class Connection { // return promise with response return new Promise((resolve, reject) => { - this.peerClient.Fetch(req, (err: any, resp: any) => { + this.peerClient.Fetch(req, metadata, (err: any, resp: any) => { if (err) return reject(err); const resBody = resp?.body ? Buffer.from(resp.body) : Buffer.alloc(0); diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts index 27f4a8842..efe4c0953 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts @@ -114,23 +114,6 @@ export class ConnectionManager { centralClient, this.centralDispatcher ); - - this.sendingConnections.set( - "a", - new Connection("1", "a", ConnectionDirection.SENDING, this.peerCtor, { - caCert: this.caCert, - clientCert: this.clientCert, - clientKey: this.clientKey, - }) - ); - this.sendingConnections.set( - "b", - new Connection("2", "b", ConnectionDirection.SENDING, this.peerCtor, { - caCert: this.caCert, - clientCert: this.clientCert, - clientKey: this.clientKey, - }) - ); } /** @@ -166,12 +149,12 @@ export class ConnectionManager { * Creates new connection * @param address Target (external) address of the connection * @param direction Direction of connection - * @param token Optional token for connection + * @param jweToken Optional encrypted JWE token for connection */ public async createNewConnection( address: string, direction: ConnectionDirection, - token?: string + jweToken?: string ) { let conn: Connection | undefined; @@ -183,14 +166,15 @@ export class ConnectionManager { // Return existing connection if found if (conn) { - if (token) { - conn.handleNewToken(token); + if (jweToken) { + conn.handleNewToken(jweToken); } return conn; } // Create new connection - conn = new Connection(token || "", address, direction, this.peerCtor, { + conn = new Connection(jweToken || "", address, direction); + conn.createSslTunnel(this.peerCtor, { caCert: this.caCert, clientCert: this.clientCert, clientKey: this.clientKey, @@ -229,6 +213,27 @@ export class ConnectionManager { } } + /** + * @description Searches through all receiving and sending connections to find a connection by its client Serial Number (SN). + * @param serialNumber The unique serial number of the peer's certificate. + * @returns The matching Connection object or undefined. + */ + getConnectionBySerialNumber(serialNumber: string): Connection | undefined { + // Check receiving connections first + for (const conn of this.receivingConnections.values()) { + if (conn.getSerialNumber() === serialNumber) { + return conn; + } + } + // Check sending connections + for (const conn of this.sendingConnections.values()) { + if (conn.getSerialNumber() === serialNumber) { + return conn; + } + } + return undefined; + } + /** * Returns object with all connections * @returns Object of all connections @@ -246,7 +251,6 @@ export class ConnectionManager { /** Starts a listener server for p2p connections */ public async listenForPeers( port: number, - listenerPublicKey: NonSharedBuffer, listenerCert: NonSharedBuffer, listenerPrivateKey: NonSharedBuffer, baseAPIPath?: string @@ -265,39 +269,25 @@ export class ConnectionManager { callback: grpc.sendUnaryData ) => { // run auth interceptor - gRPCAuthInterceptor( + const { isAuthenticated, conn } = await gRPCAuthInterceptor( call, callback, this.receivingConnections, listenerPrivateKey, - listenerPublicKey + this.peerCtor ); + if (!isAuthenticated || !conn) { + // Authentication failed - response already sent in interceptor + return; + } + try { const clientAddress = call.getPeer(); this.logger.infoMessage(`Incoming request from ${clientAddress}`); - let conn: Connection | undefined = - this.receivingConnections.get(clientAddress); - - if (!conn) { - conn = new Connection( - "", - clientAddress, - ConnectionDirection.RECEIVING, - this.peerCtor, - { - caCert: this.caCert, - clientCert: this.clientCert, - clientKey: this.clientKey, - } - ); - conn.updateStatus(ConnectionStatus.CONNECTED); - this.receivingConnections.set(clientAddress, conn); - this.logger.infoMessage( - `New incoming connection registered for: ${clientAddress}` - ); - } + conn.updateStatus(ConnectionStatus.CONNECTED); + this.receivingConnections.set(clientAddress, conn); // create request to forward to local API endpoint const method = String(call.request?.method || "POST").toUpperCase(); @@ -345,7 +335,7 @@ export class ConnectionManager { this.caCert, [ { - private_key: listenerPublicKey, + private_key: listenerPrivateKey, cert_chain: listenerCert, }, ], @@ -360,6 +350,4 @@ export class ConnectionManager { this.logger.infoMessage(`Peer server listening on localhost:${port}`); } - - private createPeerAuthInterceptor() {} } diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts index 938097a62..c1c913045 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts @@ -13,128 +13,152 @@ */ import * as grpc from "@grpc/grpc-js"; -import { Connection } from "client/Connection/Connection"; +import { Connection } from "../../../client/Connection/Connection"; import { importPKCS8, importJWK, compactDecrypt, compactVerify } from "jose"; +import { TokenPayload } from "../../../models/connection.model"; +import { ConnectionDirection } from "../../../models/message.model"; -interface TokenPayload { - serialNumber: string; - allowedRequests: ("POST" | "GET" | "PUT" | "DELETE" | "PATCH")[]; -} +// IMPORTANT: This key must be securely provided to the interceptor. +const RAW_ED25519_B64_KEY = "VqkcxlpJYVZI/SxgWH/VqVNeKhMGIbUfHn0okzdGs2E="; +/** + * @description gRPC interceptor function responsible for JWE decryption, JWS verification, + * certificate serial number matching (mTLS binding), and basic authorization. + */ export const gRPCAuthInterceptor = async ( call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, clientConnections: Map, - privateKeyBuffer: NonSharedBuffer, - publicKeyBuffer: NonSharedBuffer -): Promise => { + privateKeyBuffer: NonSharedBuffer, // RSA Private Key (PKCS8) for JWE decryption + peerCtor: any +): Promise<{ isAuthenticated: Boolean; conn: Connection | null }> => { const metadata = call.metadata.getMap(); - const jweToken = metadata.token as string; + const jweToken = metadata.jweToken as string; + const clientAddress = call.getPeer(); + let conn = clientConnections.get(clientAddress); - // check if token exists + // Check if token exists if (!jweToken) { const error = { name: "AuthenticationError", message: "No token provided", code: grpc.status.UNAUTHENTICATED, }; - callback(error, null); - return false; + return { isAuthenticated: false, conn: null }; + } + + // Connection must exist + if (!conn) { + conn = new Connection( + jweToken, + clientAddress, + ConnectionDirection.RECEIVING, + peerCtor + ); } - // validate JWE (encrypted JWS) - decode JWE -> JWS + // JWE decryption (RSA-OAEP-256) -> JWS (Plaintext) let privateKey: any; - let jwsToken: any; + let jwsToken: string; try { + // Importing RSA private key for decryption privateKey = await importPKCS8( privateKeyBuffer.toString("utf-8"), "RSA-OAEP-256" ); - const { plaintext } = await compactDecrypt(jweToken, privateKey); // decrypt JWE token - jwsToken = plaintext.toString(); + + const { plaintext } = await compactDecrypt(jweToken, privateKey); + jwsToken = new TextDecoder().decode(plaintext).trim(); } catch (_e) { const error = { name: "AuthenticationError", - message: "Incorrect token provided", + message: "Incorrect token provided (JWE Decryption failed)", code: grpc.status.UNAUTHENTICATED, }; - - // TODO?: inform central system about incorrect token coming for peer - // or create counter with incorrect tries and then inform central system - // it potentially might be an attack here. - + // TODO: Consider logging or informing a central security system about potential attack/misconfiguration. callback(error, null); - return false; + return { isAuthenticated: false, conn }; } - // check if connection is blocked - const conn = clientConnections.get(); - - // validate JWS signature - let publicKey: any; + // Verify JWS (With signature) and payload extraction + let pub: any; let payload: TokenPayload; + try { - publicKey = await importJWK(JSON.parse(publicKeyBuffer.toString())); - const { payload: jwtPayload } = await compactVerify(jwsToken, publicKey); + // Convert a raw Base64 Ed25519 public key to JWK format + const jwk = { + kty: "OKP", + crv: "Ed25519", + x: Buffer.from(RAW_ED25519_B64_KEY, "base64").toString("base64url"), + }; + + // Importing the Ed25519 public key for verification - using "EdDSA" algorithm + pub = await importJWK(jwk, "EdDSA"); + + // Compact verify - verify with key and decode the JWS token in one step + const { payload: jwtPayload, protectedHeader } = await compactVerify( + jwsToken, + pub + ); + + // Optional: Additional check to ensure correct signing algorithm was used + // if (protectedHeader.alg !== "EdDSA" && protectedHeader.alg !== "Ed25519") { + // throw new Error("JWS signed with an unexpected algorithm."); + // } + // Decode and parse the JWT payload const payloadString = new TextDecoder().decode(jwtPayload); payload = JSON.parse(payloadString); } catch (e: any) { + const isExpired = e.message?.includes("expired"); const error = { name: "AuthenticationError", - message: `JWS ${ - e.message.includes("expired") ? "Expiration" : "Verification" - } error`, - code: e.message.includes("expired") + message: `JWS Verification error: ${ + isExpired ? "Token expired" : "Invalid signature" + }`, + code: isExpired ? grpc.status.UNAUTHENTICATED : grpc.status.PERMISSION_DENIED, }; - - // TODO?: inform central system about incorrect token coming for peer - // or create counter with incorrect tries and then inform central system - // it potentially might be an attack here. - + // TODO: Consider logging or informing a central security system about failed verification. callback(error, null); - return false; + return { isAuthenticated: false, conn }; } - // Connection tunnel verification with SN - const peerCert = (call as any).getPeerCertificate(); // its not publicly exposed + // mTLS binding check and authorization + // Connection tunnel verification with serialNumber (mTLS SN vs Token SN) + const peerCert = (call as any).getPeerCertificate(); // Retrieves the mTLS client certificate details const clientSerialNumber = peerCert ? peerCert.serialNumber : null; - const tokenSerialNumber = payload.serialNumber; // Serial number is inside payload + const tokenSerialNumber = payload.serialNumber; // Serial number is inside the signed payload if (!clientSerialNumber || tokenSerialNumber !== clientSerialNumber) { + // Critical security failure!!!: The token holder does not match the mTLS certificate holder. const error = { name: "AuthenticationError", code: grpc.status.PERMISSION_DENIED, - message: "Serial number mismatch.", + message: "Serial number mismatch (mTLS binding failure).", } as any; - - // TODO?: inform central system about incorrect token coming for peer - // or create counter with incorrect tries and then inform central system - // it potentially might be an attack here. - + // TODO: This should trigger a high-priority security alert. callback(error, null); - return false; + return { isAuthenticated: false, conn }; } - // Validate permission for request method + // Validate permission for request method (Authorization check) const method = String(call.request?.method || "POST").toUpperCase(); if (!payload.allowedRequests.includes(method as any)) { const error = { name: "AuthorizationError", code: grpc.status.PERMISSION_DENIED, - message: `Request of type ${method} is not allowed.`, + message: `Request of type ${method} is not allowed by the token policy.`, } as any; - // TODO?: inform central system about incorrect token coming for peer - // or create counter with incorrect tries and then inform central system - // it potentially might be an attack here. - callback(error, null); - return false; + return { isAuthenticated: false, conn }; } - return true; + // Authentication and Authorization successful + // Update Connection state with SN and status + conn.handleSuccessfulAuth(payload as any); + return { isAuthenticated: true, conn }; }; diff --git a/Tokenization/backend/wrapper/src/models/connection.model.ts b/Tokenization/backend/wrapper/src/models/connection.model.ts index f624cb312..6f7b34183 100644 --- a/Tokenization/backend/wrapper/src/models/connection.model.ts +++ b/Tokenization/backend/wrapper/src/models/connection.model.ts @@ -62,3 +62,8 @@ export type HttpLikeResponse = { headers: Headers; body: Buffer; }; + +export type TokenPayload = { + serialNumber: string; + allowedRequests: ("POST" | "GET" | "PUT" | "DELETE" | "PATCH")[]; +}; diff --git a/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts b/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts index 37e948cf5..7e9b70b7e 100644 --- a/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts @@ -26,13 +26,28 @@ import * as protoLoader from "@grpc/proto-loader"; import path from "path"; import { getTestCerts } from "../../testCerts/testCerts"; +// Mock logger +jest.mock( + "@aliceo2/web-ui", + () => ({ + LogManager: { + getLogger: () => ({ + infoMessage: jest.fn(), + debugMessage: jest.fn(), + errorMessage: jest.fn(), + }), + }, + }), + { virtual: true } +); + /** * Helper to create a new token command with given address, direction, and token. */ -function createEventMessage( +const createEventMessage = ( targetAddress: string, connectionDirection: ConnectionDirection -): Command { +): Command => { return { event: DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN, payload: { @@ -41,7 +56,7 @@ function createEventMessage( token: "test-token", }, } as Command; -} +}; describe("NewTokenHandler", () => { let manager: ConnectionManager; @@ -88,15 +103,10 @@ describe("NewTokenHandler", () => { dir: ConnectionDirection, token: string ) { - const conn = new Connection( - token, - address, - dir, - peerCtor, - getTestCerts() - ); + const conn = new Connection(token, address, dir); if (dir === ConnectionDirection.SENDING) { this.sendingConnections.set(address, conn); + conn.createSslTunnel(peerCtor, getTestCerts()); } else { this.receivingConnections.set(address, conn); } @@ -110,10 +120,9 @@ describe("NewTokenHandler", () => { const conn = new Connection( "old-token", targetAddress, - ConnectionDirection.SENDING, - peerCtor, - getTestCerts() + ConnectionDirection.SENDING ); + conn.createSslTunnel(peerCtor, getTestCerts()); (manager as any).sendingConnections.set(targetAddress, conn); diff --git a/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts b/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts index 85eddcc8a..33815675a 100644 --- a/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts @@ -27,6 +27,21 @@ import * as protoLoader from "@grpc/proto-loader"; import path from "path"; import { getTestCerts } from "../../testCerts/testCerts"; +// Mock logger +jest.mock( + "@aliceo2/web-ui", + () => ({ + LogManager: { + getLogger: () => ({ + infoMessage: jest.fn(), + debugMessage: jest.fn(), + errorMessage: jest.fn(), + }), + }, + }), + { virtual: true } +); + describe("RevokeToken", () => { const protoPath = path.join( __dirname, @@ -48,7 +63,7 @@ describe("RevokeToken", () => { const wrapper = proto.webui.tokenization; const peerCtor = wrapper.Peer2Peer; - function createEventMessage(targetAddress: string) { + const createEventMessage = (targetAddress: string) => { return { event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, payload: { @@ -56,7 +71,7 @@ describe("RevokeToken", () => { token: "test-token", }, } as Command; - } + }; let manager: ConnectionManager; @@ -78,10 +93,9 @@ describe("RevokeToken", () => { const conn = new Connection( "valid-token", targetAddress, - ConnectionDirection.SENDING, - peerCtor, - getTestCerts() + ConnectionDirection.SENDING ); + conn.createSslTunnel(peerCtor, getTestCerts()); (manager as any).sendingConnections!.set(targetAddress, conn); const handler = new RevokeTokenHandler(manager); @@ -100,9 +114,7 @@ describe("RevokeToken", () => { const conn = new Connection( "valid-token", targetAddress, - ConnectionDirection.RECEIVING, - peerCtor, - getTestCerts() + ConnectionDirection.RECEIVING ); (manager as any).receivingConnections.set(targetAddress, conn); diff --git a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts index 503b22a50..00d743b90 100644 --- a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts @@ -37,6 +37,24 @@ const mockClient = { // Mock CentralSystem constructor const CentralSystemMock = jest.fn(() => mockClient); +// Mock gRPC auth interceptor +jest.mock( + "../../../client/ConnectionManager/Interceptors/grpc.auth.interceptor", + () => ({ + gRPCAuthInterceptor: jest.fn((call, callback) => { + return Promise.resolve({ + isAuthenticated: true, + conn: { + updateStatus: jest.fn(), + handleSuccessfulAuth: jest.fn(), + getSerialNumber: jest.fn(), + setSerialNumber: jest.fn(), + }, + }); + }), + }) +); + // Mock dispatcher const mockDispatch = jest.fn(); jest.mock( @@ -242,7 +260,6 @@ describe("ConnectionManager", () => { expect.any(Function) ); - // implementacja metody Fetch została przechwycona expect(capturedServerImpl).toBeTruthy(); expect(typeof capturedServerImpl.Fetch).toBe("function"); }); @@ -255,7 +272,7 @@ describe("ConnectionManager", () => { "http://localhost:40041/api/" ); - // przygotuj dane wywołania + // prepare data to call const call = { getPeer: () => "client-42", request: { diff --git a/Tokenization/backend/wrapper/src/test/testCerts/testCerts.ts b/Tokenization/backend/wrapper/src/test/testCerts/testCerts.ts index dcb90aa19..d47f86cd7 100644 --- a/Tokenization/backend/wrapper/src/test/testCerts/testCerts.ts +++ b/Tokenization/backend/wrapper/src/test/testCerts/testCerts.ts @@ -36,7 +36,7 @@ export const getTestClientListenerCertPaths = return { caCertPath: CA_CERT_PATH, certPath: CLIENT_CERT_PATH, - keyPath: CLIENT_KEY_PATH, + publicKeyPath: CLIENT_KEY_PATH, }; }; @@ -55,7 +55,7 @@ export const getTestClientListenerServerCertPaths = return { caCertPath: CA_CERT_PATH, certPath: CLIENT_CERT_PATH, - keyPath: CLIENT_KEY_PATH, + publicKeyPath: CLIENT_KEY_PATH, }; }; @@ -71,7 +71,7 @@ export const getTestClientSenderCertPaths = return { caCertPath: CA_CERT_PATH, certPath: CLIENT_CERT_PATH, - keyPath: CLIENT_KEY_PATH, + publicKeyPath: CLIENT_KEY_PATH, }; }; From 20d62445ffe75cefce34ffd2f990ce2678c964d9 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Sun, 28 Sep 2025 14:14:02 +0200 Subject: [PATCH 067/103] feat: add handling failed auths in the auth interceptor --- .../ConnectionManager/ConnectionManager.ts | 3 +- .../Interceptors/grpc.auth.interceptor.ts | 129 ++++++++++++++---- 2 files changed, 103 insertions(+), 29 deletions(-) diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts index efe4c0953..ef8d1e0cf 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts @@ -273,8 +273,7 @@ export class ConnectionManager { call, callback, this.receivingConnections, - listenerPrivateKey, - this.peerCtor + listenerPrivateKey ); if (!isAuthenticated || !conn) { diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts index c1c913045..60f93a48d 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts @@ -15,7 +15,10 @@ import * as grpc from "@grpc/grpc-js"; import { Connection } from "../../../client/Connection/Connection"; import { importPKCS8, importJWK, compactDecrypt, compactVerify } from "jose"; -import { TokenPayload } from "../../../models/connection.model"; +import { + ConnectionStatus, + TokenPayload, +} from "../../../models/connection.model"; import { ConnectionDirection } from "../../../models/message.model"; // IMPORTANT: This key must be securely provided to the interceptor. @@ -29,8 +32,7 @@ export const gRPCAuthInterceptor = async ( call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, clientConnections: Map, - privateKeyBuffer: NonSharedBuffer, // RSA Private Key (PKCS8) for JWE decryption - peerCtor: any + privateKeyBuffer: NonSharedBuffer // RSA Private Key (PKCS8) for JWE decryption ): Promise<{ isAuthenticated: Boolean; conn: Connection | null }> => { const metadata = call.metadata.getMap(); const jweToken = metadata.jweToken as string; @@ -48,16 +50,50 @@ export const gRPCAuthInterceptor = async ( return { isAuthenticated: false, conn: null }; } - // Connection must exist - if (!conn) { + // Check if connection exists + if (conn) { + // Check if connection is blocked + if (conn.getStatus() === ConnectionStatus.BLOCKED) { + const error = { + name: "AuthenticationError", + message: "Connection is blocked. Contact administrator.", + code: grpc.status.UNAUTHENTICATED, + }; + callback(error, null); + return { isAuthenticated: false, conn }; + } + + if (conn.getToken() === jweToken) { + // check for allowed requests and serial number match if token is the same + if ( + !isRequestAllowed(conn.getCachedTokenPayload(), call.request, callback) + ) { + return { isAuthenticated: false, conn }; + } + + if ( + !isSerialNumberMatching( + conn.getCachedTokenPayload(), + (call as any).getPeerCertificate(), + callback + ) + ) { + conn.handleFailedAuth(); + return { isAuthenticated: false, conn }; + } + + return { isAuthenticated: true, conn }; + } + } else { conn = new Connection( jweToken, clientAddress, - ConnectionDirection.RECEIVING, - peerCtor + ConnectionDirection.RECEIVING ); + clientConnections.set(clientAddress, conn); } + // New connection - need to authenticate // JWE decryption (RSA-OAEP-256) -> JWS (Plaintext) let privateKey: any; let jwsToken: string; @@ -78,6 +114,7 @@ export const gRPCAuthInterceptor = async ( }; // TODO: Consider logging or informing a central security system about potential attack/misconfiguration. callback(error, null); + conn.handleFailedAuth(); return { isAuthenticated: false, conn }; } @@ -123,30 +160,48 @@ export const gRPCAuthInterceptor = async ( }; // TODO: Consider logging or informing a central security system about failed verification. callback(error, null); + + if (!isExpired) { + conn.handleFailedAuth(); + } + return { isAuthenticated: false, conn }; } // mTLS binding check and authorization // Connection tunnel verification with serialNumber (mTLS SN vs Token SN) - const peerCert = (call as any).getPeerCertificate(); // Retrieves the mTLS client certificate details - const clientSerialNumber = peerCert ? peerCert.serialNumber : null; - const tokenSerialNumber = payload.serialNumber; // Serial number is inside the signed payload - - if (!clientSerialNumber || tokenSerialNumber !== clientSerialNumber) { - // Critical security failure!!!: The token holder does not match the mTLS certificate holder. - const error = { - name: "AuthenticationError", - code: grpc.status.PERMISSION_DENIED, - message: "Serial number mismatch (mTLS binding failure).", - } as any; - // TODO: This should trigger a high-priority security alert. - callback(error, null); + if ( + !isSerialNumberMatching( + payload, + (call as any).getPeerCertificate(), + callback + ) + ) { + conn.handleFailedAuth(); return { isAuthenticated: false, conn }; } // Validate permission for request method (Authorization check) - const method = String(call.request?.method || "POST").toUpperCase(); - if (!payload.allowedRequests.includes(method as any)) { + if (!isRequestAllowed(payload, call.request, callback)) { + return { isAuthenticated: false, conn }; + } + + // Authentication and Authorization successful + // Update Connection state with SN and status + conn.handleSuccessfulAuth(payload as any); + return { isAuthenticated: true, conn }; +}; + +const isRequestAllowed = ( + tokenPayload: TokenPayload | undefined, + request: any, + callback: grpc.sendUnaryData +): Boolean => { + const method = String(request?.method || "POST").toUpperCase(); + if ( + !tokenPayload?.allowedRequests || + !tokenPayload.allowedRequests.includes(method as any) + ) { const error = { name: "AuthorizationError", code: grpc.status.PERMISSION_DENIED, @@ -154,11 +209,31 @@ export const gRPCAuthInterceptor = async ( } as any; callback(error, null); - return { isAuthenticated: false, conn }; + return false; } - // Authentication and Authorization successful - // Update Connection state with SN and status - conn.handleSuccessfulAuth(payload as any); - return { isAuthenticated: true, conn }; + return true; +}; + +const isSerialNumberMatching = ( + tokenPayload: TokenPayload | undefined, + peerCert: any, + callback: grpc.sendUnaryData +): Boolean => { + const clientSerialNumber = peerCert ? peerCert.serialNumber : null; + const tokenSerialNumber = tokenPayload?.serialNumber; // Serial number is inside the signed payload + + if (!clientSerialNumber || tokenSerialNumber !== clientSerialNumber) { + // Critical security failure!!!: The token holder does not match the mTLS certificate holder. + const error = { + name: "AuthenticationError", + code: grpc.status.PERMISSION_DENIED, + message: "Serial number mismatch (mTLS binding failure).", + } as any; + // TODO: This should trigger a high-priority security alert. + callback(error, null); + return false; + } + + return true; }; From 37e43ece2626eb563f4bcae374c8a400a0bc682f Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Sun, 28 Sep 2025 14:56:05 +0200 Subject: [PATCH 068/103] feat: create SecurityContext class and add signing alg verification to the auth interceptor --- .../Interceptors/grpc.auth.interceptor.ts | 17 ++++--- .../src/utils/security/SecurityContext.ts | 44 +++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 Tokenization/backend/wrapper/src/utils/security/SecurityContext.ts diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts index 60f93a48d..d48cf977e 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts @@ -139,10 +139,17 @@ export const gRPCAuthInterceptor = async ( pub ); - // Optional: Additional check to ensure correct signing algorithm was used - // if (protectedHeader.alg !== "EdDSA" && protectedHeader.alg !== "Ed25519") { - // throw new Error("JWS signed with an unexpected algorithm."); - // } + // Additional check to ensure correct signing algorithm was used + if (protectedHeader.alg !== "EdDSA" && protectedHeader.alg !== "Ed25519") { + const error = { + name: "AuthenticationError", + message: "Incorrect signing algorithm for JWS.", + code: grpc.status.UNAUTHENTICATED, + }; + + callback(error, null); + return { isAuthenticated: false, conn }; + } // Decode and parse the JWT payload const payloadString = new TextDecoder().decode(jwtPayload); @@ -188,7 +195,7 @@ export const gRPCAuthInterceptor = async ( // Authentication and Authorization successful // Update Connection state with SN and status - conn.handleSuccessfulAuth(payload as any); + conn.handleSuccessfulAuth(payload); return { isAuthenticated: true, conn }; }; diff --git a/Tokenization/backend/wrapper/src/utils/security/SecurityContext.ts b/Tokenization/backend/wrapper/src/utils/security/SecurityContext.ts new file mode 100644 index 000000000..0a4cb1644 --- /dev/null +++ b/Tokenization/backend/wrapper/src/utils/security/SecurityContext.ts @@ -0,0 +1,44 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +/** + * @description Stores every keys and certificates needed for gRPC mTLS communication and token verifications (JWE/JWS) + */ +export class SecurityContext { + // mTLS keys (RSA) + public readonly caCert: Buffer; + public readonly clientSenderCert: Buffer; + public readonly clientListenerCert: Buffer; + public readonly clientPublicKey: Buffer; + // RSA Private Key (PKCS8) for JWE decryption + public readonly clientPrivateKey: Buffer; + + // Public Ed25519 key for JWS verification + public static readonly JWS_PUBLIC_KEY = + "VqkcxlpJYVZI/SxgWH/VqVNeKhMGIbUfHn0okzdGs2E="; + + constructor( + caCert: Buffer, + clientSenderCert: Buffer, + clientListenerCert: Buffer, + clientPrivateKey: Buffer, + clientPublicKey: Buffer + ) { + this.caCert = caCert; + this.clientSenderCert = clientSenderCert; + this.clientListenerCert = clientListenerCert; + this.clientPrivateKey = clientPrivateKey; + this.clientPublicKey = clientPublicKey; + } +} From fe15cd3ae1b5b51e12938e055e29c2532949aaf5 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Mon, 29 Sep 2025 19:00:13 +0200 Subject: [PATCH 069/103] fix: fix interceptor and write tests --- .../src/client/Connection/Connection.ts | 8 +- .../ConnectionManager/ConnectionManager.ts | 53 +-- .../Interceptors/grpc.auth.interceptor.ts | 78 ++-- .../backend/wrapper/src/client/gRPCWrapper.ts | 82 ++-- .../wrapper/src/models/config.model.ts | 9 +- .../wrapper/src/models/connection.model.ts | 11 +- .../ConnectionManager.test.ts | 39 +- .../grpc.auth.interceptor.test.ts | 402 ++++++++++++++++++ .../client-b-server.crt | 0 .../clientListenerServer/client-b.key | 28 -- .../wrapper/src/test/testCerts/testCerts.ts | 32 +- .../src/utils/security/SecurityContext.ts | 15 +- 12 files changed, 572 insertions(+), 185 deletions(-) create mode 100644 Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts rename Tokenization/backend/wrapper/src/test/testCerts/{clientListenerServer => clientListener}/client-b-server.crt (100%) delete mode 100644 Tokenization/backend/wrapper/src/test/testCerts/clientListenerServer/client-b.key diff --git a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts index 4ba96f9b2..b81e0a813 100644 --- a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts +++ b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts @@ -24,9 +24,9 @@ import * as grpc from "@grpc/grpc-js"; import { LogManager } from "@aliceo2/web-ui"; type ConnectionCerts = { - caCert: NonSharedBuffer; - clientCert: NonSharedBuffer; - clientKey: NonSharedBuffer; + caCert: Buffer; + clientCert: Buffer; + clientKey: Buffer; }; /** @@ -247,7 +247,7 @@ export class Connection { // set mandatory grpc metadata const metadata = new grpc.Metadata(); - metadata.set("jweToken", this.jweToken); + metadata.set("jwetoken", this.jweToken); // build body buffer let bodyBuf: Buffer = Buffer.alloc(0); diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts index ef8d1e0cf..3fe1d46aa 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts @@ -26,6 +26,7 @@ import { import { ConnectionStatus } from "../../models/connection.model"; import * as fs from "fs"; import { gRPCAuthInterceptor } from "./Interceptors/grpc.auth.interceptor"; +import { SecurityContext } from "../../utils/security/SecurityContext"; /** * @description Manages all the connection between clients and central system. @@ -57,11 +58,6 @@ export class ConnectionManager { private peerServer?: grpc.Server; private baseAPIPath: string = "localhost:40041/api/"; - // client certificates - private caCert: NonSharedBuffer; - private clientCert: NonSharedBuffer; - private clientKey: NonSharedBuffer; - /** * @description Initializes a new instance of the ConnectionManager class. * @@ -69,16 +65,12 @@ export class ConnectionManager { * * @param protoPath - The file path to the gRPC proto definition. * @param centralAddress - The address of the central gRPC server (default: "localhost:50051"). - * @param caCertPath - Path to the CA certificate file. - * @param clientCertPath - Path to the client certificate file. - * @param clientKeyPath - Path to the client key file. + * @param securityContext - The security context containing certificates and keys for secure communication. */ constructor( protoPath: string, centralAddress: string = "localhost:50051", - caCertPath: string, - clientCertPath: string, - clientKeyPath: string + private readonly securityContext: SecurityContext ) { const packageDef = protoLoader.loadSync(protoPath, { keepCase: true, @@ -92,16 +84,11 @@ export class ConnectionManager { this.wrapper = proto.webui.tokenization; this.peerCtor = this.wrapper.Peer2Peer; - // read certs - this.caCert = fs.readFileSync(caCertPath); - this.clientCert = fs.readFileSync(clientCertPath); - this.clientKey = fs.readFileSync(clientKeyPath); - // create grpc credentials const sslCreds = grpc.credentials.createSsl( - this.caCert, - this.clientKey, - this.clientCert + this.securityContext.caCert, + this.securityContext.clientPrivateKey, + this.securityContext.clientSenderCert ); const centralClient = new this.wrapper.CentralSystem( centralAddress, @@ -174,16 +161,17 @@ export class ConnectionManager { // Create new connection conn = new Connection(jweToken || "", address, direction); - conn.createSslTunnel(this.peerCtor, { - caCert: this.caCert, - clientCert: this.clientCert, - clientKey: this.clientKey, - }); conn.updateStatus(ConnectionStatus.CONNECTING); if (direction === ConnectionDirection.RECEIVING) { this.receivingConnections.set(address, conn); } else { + // open tunnel only on sending connections + conn.createSslTunnel(this.peerCtor, { + caCert: this.securityContext.caCert, + clientCert: this.securityContext.clientSenderCert, + clientKey: this.securityContext.clientPrivateKey, + }); this.sendingConnections.set(address, conn); } conn.updateStatus(ConnectionStatus.CONNECTED); @@ -251,12 +239,17 @@ export class ConnectionManager { /** Starts a listener server for p2p connections */ public async listenForPeers( port: number, - listenerCert: NonSharedBuffer, - listenerPrivateKey: NonSharedBuffer, baseAPIPath?: string ): Promise { if (baseAPIPath) this.baseAPIPath = baseAPIPath; + if (!this.securityContext.clientListenerCert) { + this.logger.errorMessage( + "Listener certificate not provided in gRPCWrapper. Cannot start peer listener." + ); + return; + } + if (this.peerServer) { this.peerServer.forceShutdown(); this.peerServer = undefined; @@ -273,7 +266,7 @@ export class ConnectionManager { call, callback, this.receivingConnections, - listenerPrivateKey + this.securityContext ); if (!isAuthenticated || !conn) { @@ -331,11 +324,11 @@ export class ConnectionManager { }); const sslCreds = grpc.ServerCredentials.createSsl( - this.caCert, + this.securityContext.caCert, [ { - private_key: listenerPrivateKey, - cert_chain: listenerCert, + private_key: this.securityContext.clientPrivateKey, + cert_chain: this.securityContext.clientListenerCert, }, ], true diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts index d48cf977e..c9a271d56 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts @@ -20,9 +20,7 @@ import { TokenPayload, } from "../../../models/connection.model"; import { ConnectionDirection } from "../../../models/message.model"; - -// IMPORTANT: This key must be securely provided to the interceptor. -const RAW_ED25519_B64_KEY = "VqkcxlpJYVZI/SxgWH/VqVNeKhMGIbUfHn0okzdGs2E="; +import { SecurityContext } from "../../../utils/security/SecurityContext"; /** * @description gRPC interceptor function responsible for JWE decryption, JWS verification, @@ -32,12 +30,13 @@ export const gRPCAuthInterceptor = async ( call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, clientConnections: Map, - privateKeyBuffer: NonSharedBuffer // RSA Private Key (PKCS8) for JWE decryption + securityContext: SecurityContext ): Promise<{ isAuthenticated: Boolean; conn: Connection | null }> => { const metadata = call.metadata.getMap(); - const jweToken = metadata.jweToken as string; + const jweToken = metadata.jwetoken as string; const clientAddress = call.getPeer(); let conn = clientConnections.get(clientAddress); + const peerCert = getPeerCertFromCall(call); // Check if token exists if (!jweToken) { @@ -74,7 +73,7 @@ export const gRPCAuthInterceptor = async ( if ( !isSerialNumberMatching( conn.getCachedTokenPayload(), - (call as any).getPeerCertificate(), + peerCert, callback ) ) { @@ -100,7 +99,7 @@ export const gRPCAuthInterceptor = async ( try { // Importing RSA private key for decryption privateKey = await importPKCS8( - privateKeyBuffer.toString("utf-8"), + securityContext.clientPrivateKey.toString("utf-8"), "RSA-OAEP-256" ); @@ -127,7 +126,9 @@ export const gRPCAuthInterceptor = async ( const jwk = { kty: "OKP", crv: "Ed25519", - x: Buffer.from(RAW_ED25519_B64_KEY, "base64").toString("base64url"), + x: Buffer.from(securityContext.JWS_PUBLIC_KEY, "base64").toString( + "base64url" + ), }; // Importing the Ed25519 public key for verification - using "EdDSA" algorithm @@ -177,13 +178,7 @@ export const gRPCAuthInterceptor = async ( // mTLS binding check and authorization // Connection tunnel verification with serialNumber (mTLS SN vs Token SN) - if ( - !isSerialNumberMatching( - payload, - (call as any).getPeerCertificate(), - callback - ) - ) { + if (!isSerialNumberMatching(payload, peerCert, callback)) { conn.handleFailedAuth(); return { isAuthenticated: false, conn }; } @@ -199,16 +194,20 @@ export const gRPCAuthInterceptor = async ( return { isAuthenticated: true, conn }; }; -const isRequestAllowed = ( +/** + * @description Checks if the request method is allowed based on the token permissions. + * @param tokenPayload payload extracted from the token + * @param request gRPC request object containing method information + * @param callback callback to return gRPC error if needed + * @returns true if request method is allowed, false otherwise + */ +export const isRequestAllowed = ( tokenPayload: TokenPayload | undefined, request: any, callback: grpc.sendUnaryData ): Boolean => { const method = String(request?.method || "POST").toUpperCase(); - if ( - !tokenPayload?.allowedRequests || - !tokenPayload.allowedRequests.includes(method as any) - ) { + if (!tokenPayload?.perm || !Object.keys(tokenPayload.perm).includes(method)) { const error = { name: "AuthorizationError", code: grpc.status.PERMISSION_DENIED, @@ -222,25 +221,50 @@ const isRequestAllowed = ( return true; }; -const isSerialNumberMatching = ( +/** + * @description Checks if the serial number from the peer certificate matches the one in the token payload. + * @param tokenPayload payload extracted from the token + * @param peerCert certificate object retrieved from the gRPC call + * @param callback callback to return gRPC error if needed + * @returns true if serial numbers match, false otherwise + */ +export const isSerialNumberMatching = ( tokenPayload: TokenPayload | undefined, peerCert: any, callback: grpc.sendUnaryData ): Boolean => { - const clientSerialNumber = peerCert ? peerCert.serialNumber : null; - const tokenSerialNumber = tokenPayload?.serialNumber; // Serial number is inside the signed payload + const clientSN = normalizeSerial(peerCert?.serialNumber); + const tokenSN = normalizeSerial(tokenPayload?.subSerialNumber); - if (!clientSerialNumber || tokenSerialNumber !== clientSerialNumber) { - // Critical security failure!!!: The token holder does not match the mTLS certificate holder. + if (!clientSN || clientSN !== tokenSN) { const error = { name: "AuthenticationError", code: grpc.status.PERMISSION_DENIED, message: "Serial number mismatch (mTLS binding failure).", } as any; - // TODO: This should trigger a high-priority security alert. callback(error, null); return false; } - return true; }; + +/** + * @description Normalizes a certificate serial number by removing colons and converting to uppercase. + * @param sn serial number string possibly containing colons or being null/undefined + * @returns normalized serial number string + */ +const normalizeSerial = (sn?: string | null): string => { + // Node retrieves serial number as hex string, without leading 0x and with possible colons so we need to normalize it + return (sn || "").replace(/[^0-9a-f]/gi, "").toUpperCase(); +}; + +/** + * @description Retrieves the peer certificate from the gRPC call object. + * @param call gRPC call object + * @returns peer certificate object from the gRPC call + */ +export const getPeerCertFromCall = (call: any) => { + const session = call?.call?.stream?.session; + const sock = session?.socket as any; + return sock?.getPeerCertificate(true); // whole certificate info from TLS socket +}; diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index b8d617abc..74e11b1d7 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -21,8 +21,8 @@ import { import { Connection } from "./Connection/Connection"; import { NewTokenHandler } from "./Commands/newToken/newToken.handler"; import { gRPCWrapperConfig } from "../models/config.model"; +import { SecurityContext } from "../utils/security/SecurityContext"; import * as fs from "fs"; -import { LogManager } from "@aliceo2/web-ui"; /** * @description Wrapper class for managing secure gRPC wrapper. @@ -35,21 +35,17 @@ import { LogManager } from "@aliceo2/web-ui"; * @example * ```typescript * const grpcWrapper = new gRPCWrapper(PROTO_PATH, CENTRAL_SYSTEM_ADDRESS); - * // Use grpcWrapper to interact with gRPC services + * Use grpcWrapper to interact with gRPC services * ``` */ export class gRPCWrapper { private ConnectionManager: ConnectionManager; - private listenerPublicKey?: NonSharedBuffer; - private listenerPrivateKey?: NonSharedBuffer; - private listenerCert?: NonSharedBuffer; - private logger = LogManager.getLogger("gRPCWrapper"); + private securityContext: SecurityContext; /** * @description Initializes an instance of gRPCWrapper class. * - * @param protoPath - The file path to the gRPC proto definition. - * @param centralAddress - The address of the central gRPC server (default: "localhost:50051"). + * @param config - External configuration object containing necessary paths and addresses. */ constructor(config: gRPCWrapperConfig) { if ( @@ -58,31 +54,42 @@ export class gRPCWrapper { !config.clientCerts || !config.clientCerts.caCertPath || !config.clientCerts.certPath || - !config.clientCerts.publicKeyPath + !config.clientCerts.publicKeyPath || + !config.clientCerts.privateKeyPath ) { - throw new Error("Invalid gRPCWrapper configuration provided."); + throw new Error( + "Invalid gRPCWrapper configuration provided. Missing required paths." + ); } - if ( - config.listenerCertPaths?.publicKeyPath && - config.listenerCertPaths?.certPath - ) { - this.listenerPublicKey = fs.readFileSync( - config.listenerCertPaths.publicKeyPath - ); - this.listenerPrivateKey = fs.readFileSync( - config.listenerCertPaths.privateKeyPath - ); - this.listenerCert = fs.readFileSync(config.listenerCertPaths.certPath); + let clientListenerCert: Buffer = Buffer.alloc(0); + + // Klucze do wysyłania (Sender) są obowiązkowe + const caCert = fs.readFileSync(config.clientCerts.caCertPath); + const clientSenderCert = fs.readFileSync(config.clientCerts.certPath); + const clientPublicKey = fs.readFileSync(config.clientCerts.publicKeyPath); + const clientPrivateKey = fs.readFileSync(config.clientCerts.privateKeyPath); + + if (config.listenerCertPath) { + // If we have dedicated listener cert, use it + clientListenerCert = fs.readFileSync(config.listenerCertPath); } + this.securityContext = new SecurityContext( + caCert, + clientSenderCert, + clientPrivateKey, + clientPublicKey, + clientListenerCert + ); + this.ConnectionManager = new ConnectionManager( config.protoPath, config.centralAddress, - config.clientCerts.caCertPath, - config.clientCerts.certPath, - config.clientCerts.publicKeyPath + this.securityContext ); + + // Register all command handlers this.ConnectionManager.registerCommandHandlers([ { event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, @@ -121,32 +128,9 @@ export class gRPCWrapper { */ public async listenForPeers( port: number, - baseAPIPath?: string, - listenerCertPaths?: { keyPath: string; certPath: string } + baseAPIPath?: string ): Promise { - if (listenerCertPaths?.keyPath && listenerCertPaths?.certPath) { - this.listenerPublicKey = fs.readFileSync(listenerCertPaths.keyPath); - this.listenerCert = fs.readFileSync(listenerCertPaths.certPath); - } - - if ( - !this.listenerPublicKey || - !this.listenerPrivateKey || - !this.listenerCert - ) { - this.logger.errorMessage( - "Listener certificates are required to start P2P listener. Please provide valid paths." - ); - return; - } - - return this.ConnectionManager.listenForPeers( - port, - this.listenerPublicKey, - this.listenerCert, - this.listenerPrivateKey, - baseAPIPath - ); + return this.ConnectionManager.listenForPeers(port, baseAPIPath); } /** diff --git a/Tokenization/backend/wrapper/src/models/config.model.ts b/Tokenization/backend/wrapper/src/models/config.model.ts index 2e4e9e9ac..3167065d1 100644 --- a/Tokenization/backend/wrapper/src/models/config.model.ts +++ b/Tokenization/backend/wrapper/src/models/config.model.ts @@ -37,14 +37,11 @@ export interface gRPCWrapperConfig { /** Client TLS certificates paths. */ clientCerts: { caCertPath: string; - publicKeyPath: string; - certPath: string; - }; - - /** Optional listener TLS certificates paths. If provided, the gRPCWrapper will be able to accept incoming connections. */ - listenerCertPaths?: { publicKeyPath: string; privateKeyPath: string; certPath: string; }; + + /** Optional listener TLS certificate path. If provided, the gRPCWrapper will be able to accept incoming connections. */ + listenerCertPath?: string; } diff --git a/Tokenization/backend/wrapper/src/models/connection.model.ts b/Tokenization/backend/wrapper/src/models/connection.model.ts index 6f7b34183..dc9c97c61 100644 --- a/Tokenization/backend/wrapper/src/models/connection.model.ts +++ b/Tokenization/backend/wrapper/src/models/connection.model.ts @@ -33,6 +33,7 @@ export enum ConnectionStatus { export type ConnectionHeaders = Record; +// Options for making fetch-like requests over a connection export type FetchOptions = { method?: string; path?: string; @@ -40,6 +41,7 @@ export type FetchOptions = { body?: string | Buffer | Uint8Array | null; }; +// A more specific type for fetch responses, including status, headers, and body export type FetchResponse = { status: number; headers: ConnectionHeaders; @@ -63,7 +65,12 @@ export type HttpLikeResponse = { body: Buffer; }; +// Payload structure for authentication tokens export type TokenPayload = { - serialNumber: string; - allowedRequests: ("POST" | "GET" | "PUT" | "DELETE" | "PATCH")[]; + subSerialNumber: string; + aud: string; + perm: Object; + iat: number; + exp: number; + jti: string; }; diff --git a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts index 00d743b90..0ed5b11e1 100644 --- a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test.ts @@ -18,10 +18,7 @@ import { ConnectionDirection, DuplexMessageEvent, } from "../../../models/message.model"; -import { - getTestCentralCertPaths, - getTestCerts, -} from "../../testCerts/testCerts"; +import { SecurityContext } from "../../../utils/security/SecurityContext"; // Mock duplex stream const mockStream = { @@ -146,7 +143,14 @@ jest.mock("@grpc/grpc-js", () => { describe("ConnectionManager", () => { let conn: ConnectionManager; - const { caCertPath, certPath, keyPath } = getTestCentralCertPaths(); + const MOCK_CERT = Buffer.from("MOCK_CERT"); + const securityContext = new SecurityContext( + MOCK_CERT, + MOCK_CERT, + MOCK_CERT, + MOCK_CERT, + MOCK_CERT + ); beforeEach(() => { jest.clearAllMocks(); @@ -155,9 +159,7 @@ describe("ConnectionManager", () => { conn = new ConnectionManager( "dummy.proto", "localhost:12345", - caCertPath, - certPath, - keyPath + securityContext ); }); @@ -241,12 +243,7 @@ describe("ConnectionManager", () => { }); test("listenForPeers() should start server and register service", async () => { - await conn.listenForPeers( - 50055, - getTestCerts().clientCert, - getTestCerts().caCert, - "http://localhost:40041/api/" - ); + await conn.listenForPeers(50055, "http://localhost:40041/api/"); const serverCtor = (grpc.Server as any).mock; expect(serverCtor).toBeDefined(); @@ -265,12 +262,7 @@ describe("ConnectionManager", () => { }); test("p2p Fetch should register incoming receiving connection and forward request", async () => { - await conn.listenForPeers( - 50056, - getTestCerts().clientCert, - getTestCerts().caCert, - "http://localhost:40041/api/" - ); + await conn.listenForPeers(50056, "http://localhost:40041/api/"); // prepare data to call const call = { @@ -328,12 +320,7 @@ describe("ConnectionManager", () => { }); test("p2p Fetch should return INTERNAL on forward error", async () => { - await conn.listenForPeers( - 50057, - getTestCerts().clientCert, - getTestCerts().caCert, - "http://localhost:40041/api/" - ); + await conn.listenForPeers(50057, "http://localhost:40041/api/"); const call = { getPeer: () => "client-error", diff --git a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts new file mode 100644 index 000000000..791e9a4a8 --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts @@ -0,0 +1,402 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import * as grpc from "@grpc/grpc-js"; +import * as jose from "jose"; +import * as interceptor from "../../../../client/ConnectionManager/Interceptors/grpc.auth.interceptor"; + +// Connection class mock +jest.mock( + "../../../../client/Connection/Connection", + () => { + return { + Connection: jest + .fn() + .mockImplementation( + (jweToken: string, address: string, direction: any) => { + return { + jweToken, + address, + direction, + status: 1, + payload: { subSerialNumber: "AABBCC", perm: { POST: true } }, + getStatus: jest.fn(function () { + return this.status; + }), + getToken: jest.fn(function () { + return this.jweToken; + }), + getCachedTokenPayload: jest.fn(function () { + return this.payload; + }), + handleFailedAuth: jest.fn(), + handleSuccessfulAuth: jest.fn(function (p: any) { + this.payload = p; + this.status = 1; + }), + }; + } + ), + }; + }, + { virtual: true } +); + +import { Connection } from "../../../../client/Connection/Connection"; + +jest.mock("jose", () => ({ + importPKCS8: jest.fn(), + importJWK: jest.fn(), + compactDecrypt: jest.fn(), + compactVerify: jest.fn(), +})); + +import { + ConnectionStatus, + TokenPayload, +} from "../../../../models/connection.model"; +import { SecurityContext } from "../../../../utils/security/SecurityContext"; +import { ConnectionDirection } from "../../../../models/message.model"; + +const mockSecurityContext = { + clientPrivateKey: Buffer.from("mock_private_key_rsa"), + JWS_PUBLIC_KEY: "mock_public_key_ed25519", +} as unknown as SecurityContext; + +let isRequestAllowedSpy: jest.SpyInstance; +let isSerialNumberMatchingSpy: jest.SpyInstance; +let getPeerCertFromCallSpy: jest.SpyInstance; + +const mockCall = { + metadata: { getMap: jest.fn(() => ({})) }, + getPeer: jest.fn(() => "ipv4:127.0.0.1:12345"), + request: { method: "POST" }, +} as unknown as grpc.ServerUnaryCall; + +const mockCallback = jest.fn(); +const mockClientConnections = new Map(); + +describe("gRPCAuthInterceptor", () => { + const MOCK_ADDRESS = "ipv4:127.0.0.1:12345"; + const VALID_JWE = "valid.jwe.token"; + const VALID_JWS = "valid.jws.token"; + const DECRYPTED_PAYLOAD: TokenPayload = { + subSerialNumber: "DDEEFF", + perm: { POST: true, GET: false }, + } as any; + + beforeEach(() => { + jest.clearAllMocks(); + mockClientConnections.clear(); + + (mockCall.metadata.getMap as unknown as jest.Mock).mockReturnValue({ + jwetoken: VALID_JWE, + }); + (mockCall.getPeer as unknown as jest.Mock).mockReturnValue(MOCK_ADDRESS); + (mockCall as any).request = { method: "POST" }; + + (jose.importPKCS8 as jest.Mock).mockResolvedValue("mock_priv_key"); + (jose.compactDecrypt as jest.Mock).mockResolvedValue({ + plaintext: Buffer.from(VALID_JWS), + }); + (jose.importJWK as jest.Mock).mockResolvedValue("mock_pub_key"); + (jose.compactVerify as jest.Mock).mockResolvedValue({ + payload: Buffer.from(JSON.stringify(DECRYPTED_PAYLOAD)), + protectedHeader: { alg: "EdDSA" }, + }); + + // mocks of internal functions + isRequestAllowedSpy = jest + .spyOn(interceptor, "isRequestAllowed") + .mockImplementation((_p, _r, _cb) => true); + + isSerialNumberMatchingSpy = jest + .spyOn(interceptor, "isSerialNumberMatching") + .mockImplementation((_p, _pc, _cb) => true); + + getPeerCertFromCallSpy = jest + .spyOn(interceptor, "getPeerCertFromCall") + .mockReturnValue({ serialNumber: "DDEEFF" }); + }); + + const getCreatedConn = () => { + const instances = (Connection as jest.Mock).mock?.instances ?? []; + return ( + instances.find((i: any) => i.address === MOCK_ADDRESS) ?? + mockClientConnections.get(MOCK_ADDRESS) + ); + }; + + it("should fail if no JWE token is provided in the metadata", async () => { + (mockCall.metadata.getMap as unknown as jest.Mock).mockReturnValue({}); + + const result = await interceptor.gRPCAuthInterceptor( + mockCall, + mockCallback, + mockClientConnections as any, + mockSecurityContext + ); + + expect(result.isAuthenticated).toBe(false); + expect(result.conn).toBe(null); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + code: grpc.status.UNAUTHENTICATED, + message: "No token provided", + }), + null + ); + }); + + it("should authenticate instantly if connection exists and token hasn't changed", async () => { + const existingConn = new (Connection as jest.Mock)( + VALID_JWE, + MOCK_ADDRESS, + ConnectionDirection.RECEIVING + ); + existingConn.getToken.mockReturnValue(VALID_JWE); + mockClientConnections.set(MOCK_ADDRESS, existingConn); + + const result = await interceptor.gRPCAuthInterceptor( + mockCall, + mockCallback, + mockClientConnections as any, + mockSecurityContext + ); + + expect(result.isAuthenticated).toBe(true); + expect(result.conn).toBe(existingConn); + expect(isRequestAllowedSpy).toHaveBeenCalledTimes(1); + expect(isSerialNumberMatchingSpy).toHaveBeenCalledTimes(1); + expect(jose.compactDecrypt as jest.Mock).not.toHaveBeenCalled(); + }); + + it("should reject if connection exists but is BLOCKED", async () => { + const existingConn = new (Connection as jest.Mock)( + VALID_JWE, + MOCK_ADDRESS, + ConnectionDirection.RECEIVING + ); + existingConn.status = ConnectionStatus.BLOCKED; + mockClientConnections.set(MOCK_ADDRESS, existingConn); + + const result = await interceptor.gRPCAuthInterceptor( + mockCall, + mockCallback, + mockClientConnections as any, + mockSecurityContext + ); + + expect(result.isAuthenticated).toBe(false); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + code: grpc.status.UNAUTHENTICATED, + message: "Connection is blocked. Contact administrator.", + }), + null + ); + }); + + it("should reject existing connection on serial number mismatch", async () => { + const existingConn = new (Connection as jest.Mock)( + VALID_JWE, + MOCK_ADDRESS, + ConnectionDirection.RECEIVING + ); + existingConn.getToken.mockReturnValue(VALID_JWE); + mockClientConnections.set(MOCK_ADDRESS, existingConn); + + // mock serial number mismatch + isSerialNumberMatchingSpy.mockImplementation((_p, _pc, cb) => { + cb( + { + name: "AuthenticationError", + code: grpc.status.PERMISSION_DENIED, + message: "Serial number mismatch (mTLS binding failure).", + } as any, + null + ); + return false; + }); + + const result = await interceptor.gRPCAuthInterceptor( + mockCall, + mockCallback, + mockClientConnections as any, + mockSecurityContext + ); + + expect(result.isAuthenticated).toBe(false); + expect(existingConn.handleFailedAuth).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + message: "Serial number mismatch (mTLS binding failure).", + }), + null + ); + }); + + it("should successfully authenticate a NEW connection", async () => { + (mockCall.metadata.getMap as unknown as jest.Mock).mockReturnValue({ + jwetoken: "NEW.JWE.TOKEN", + }); + + const result = await interceptor.gRPCAuthInterceptor( + mockCall, + mockCallback, + mockClientConnections as any, + mockSecurityContext + ); + + const created = getCreatedConn(); + expect(result.isAuthenticated).toBe(true); + expect(created).toBeDefined(); + expect(created!.handleSuccessfulAuth).toHaveBeenCalledWith( + DECRYPTED_PAYLOAD + ); + expect(jose.compactDecrypt as jest.Mock).toHaveBeenCalledTimes(1); + expect(jose.compactVerify as jest.Mock).toHaveBeenCalledTimes(1); + expect(isSerialNumberMatchingSpy).toHaveBeenCalledTimes(1); + expect(isRequestAllowedSpy).toHaveBeenCalledTimes(1); + }); + + it("should fail if JWE decryption fails", async () => { + (jose.compactDecrypt as jest.Mock).mockRejectedValue( + new Error("Decryption failed") + ); + + const result = await interceptor.gRPCAuthInterceptor( + mockCall, + mockCallback, + mockClientConnections as any, + mockSecurityContext + ); + + const created = getCreatedConn(); + expect(result.isAuthenticated).toBe(false); + expect(created!.handleFailedAuth).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + message: "Incorrect token provided (JWE Decryption failed)", + }), + null + ); + }); + + it("should fail if JWS verification fails", async () => { + (jose.compactVerify as jest.Mock).mockRejectedValue( + new Error("Invalid signature") + ); + + const result = await interceptor.gRPCAuthInterceptor( + mockCall, + mockCallback, + mockClientConnections as any, + mockSecurityContext + ); + + const created = getCreatedConn(); + expect(result.isAuthenticated).toBe(false); + expect(created!.handleFailedAuth).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + message: "JWS Verification error: Invalid signature", + }), + null + ); + }); + + it("should fail if JWS is expired", async () => { + (jose.compactVerify as jest.Mock).mockRejectedValue({ + message: "JWT is expired", + }); + + const result = await interceptor.gRPCAuthInterceptor( + mockCall, + mockCallback, + mockClientConnections as any, + mockSecurityContext + ); + + const created = getCreatedConn(); + expect(result.isAuthenticated).toBe(false); + expect(created!.handleFailedAuth).not.toHaveBeenCalled(); // for expired token, we do not block the connection + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + message: "JWS Verification error: Token expired", + code: grpc.status.UNAUTHENTICATED, + }), + null + ); + }); + + it("should fail if mTLS serial number mismatch occurs after decryption", async () => { + isSerialNumberMatchingSpy.mockImplementation((_p, _pc, cb) => { + cb( + { + name: "AuthenticationError", + code: grpc.status.PERMISSION_DENIED, + message: "Serial number mismatch (mTLS binding failure).", + } as any, + null + ); + return false; + }); + + const result = await interceptor.gRPCAuthInterceptor( + mockCall, + mockCallback, + mockClientConnections as any, + mockSecurityContext + ); + + const created = getCreatedConn(); + expect(result.isAuthenticated).toBe(false); + expect(created!.handleFailedAuth).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + message: "Serial number mismatch (mTLS binding failure).", + }), + null + ); + }); + + it("should fail if request authorization check fails", async () => { + isRequestAllowedSpy.mockImplementation((_p, _r, cb) => { + cb( + { + name: "AuthorizationError", + code: grpc.status.PERMISSION_DENIED, + message: "Request of type POST is not allowed by the token policy.", + } as any, + null + ); + return false; + }); + + const result = await interceptor.gRPCAuthInterceptor( + mockCall, + mockCallback, + mockClientConnections as any, + mockSecurityContext + ); + + expect(result.isAuthenticated).toBe(false); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ code: grpc.status.PERMISSION_DENIED }), + null + ); + expect(isSerialNumberMatchingSpy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientListenerServer/client-b-server.crt b/Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b-server.crt similarity index 100% rename from Tokenization/backend/wrapper/src/test/testCerts/clientListenerServer/client-b-server.crt rename to Tokenization/backend/wrapper/src/test/testCerts/clientListener/client-b-server.crt diff --git a/Tokenization/backend/wrapper/src/test/testCerts/clientListenerServer/client-b.key b/Tokenization/backend/wrapper/src/test/testCerts/clientListenerServer/client-b.key deleted file mode 100644 index b2fc830da..000000000 --- a/Tokenization/backend/wrapper/src/test/testCerts/clientListenerServer/client-b.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDHGqAsEkV5CAtx -G+Q+PpLXqC5Imwk38wnEYCeZZAXe62v2d5Rd4gPYpQQw1RPZvvcQ78sPfP7wm8OF -8bgn1jX+fL8K5oBuYcpcf+i3ScjBaN3XXJeOFbJdC42oYeK6eJfYpoMfn6sg9dhT -p5SPH5XdVfSKJgQVSLM9FSHOYnwA41K7jc64x2FmjsWtvipRzj+KhWqGIBvdJ2b+ -coNYj05BHpTHwxzY5o9jfBADivdkHP2glTcbsEvzltyWaTU8NmFHpPT+ijT0WJOP -zNNny+ihqskf5MAPYQGxPB8h0FPXK4QIC57O3FqizarZNSwnPd/qFBoSc6qLPqdn -wVx5e52BAgMBAAECggEAA0i8JZ3ziWiJj8cO/7vWfjom8UmlYEfg/F09qfkNY7zs -Xfdg+h91QsiOBiQtnKTavGvIJKxCJEPdeMMg739ICreSCyL8MVXpmZb+hq9v4UjS -h+/eDBjthT1gi8t5iuvcTVWJyia/Et8bP13/RFEYDruROgogfR1i33oOwbG8K+OM -iDSIRo9swBaVNqFuWZKqZr1pjY9KUfh4jnSA+x1hueVHREJYt6qvX2KNhHFhyfyo -RRuLSKmz7Xx3sTu7qQD1NRKsD/8lM4oZWGPTit0F6qx+FfzbI0nBn9In8czmrPcm -VqwdgT5aR5I6lfzAj6kMHlmY2n0f4J0Hk6Ha6MgkJQKBgQD6AnjeToUw6PcIbTrJ -18mDfzOCMhQjQHDXipHrJ06K65B+4XX8/oTkRahyeqn4sbKKHP11e6bsDPVNRK0P -9G3wYw7nPNcGtF1pojWvh0HxBTJ/iugDgZT5ngtwbydvAT7+m6De3DicEJo5ZUPP -IAweL+qf9Nh30PtKiOSgJ2GrRwKBgQDL3+eX0sQg+dfYb3zAJmsLwXoY4fAOGc1o -KI4UI26Bq4TPrJv9guaDjSYNE62M7+H7vWudlG+KPxt6jNaxxpIo5J1c7qVl+gx6 -kTDZ053peVLtWJzLrRT9/e238bCYnKfFpKwRzUGT9kEkMvmFqb56lnhKBXS/OPuP -3dAOWHvE9wKBgAJM4YXSHSGdEyDNuHvA84a1NekdwtesMR2alcsfGnbmwfaY5ngE -c36SMYGUJVo3cFga+i4JjDihyeQDHMCH1DchAjMYeTYDlNRy/KF30iCAlr1brtTR -bWh6jspjC27XCRhYoDtMtWyiLnkWuHAAcHwansMIArHfh2BhMBFVK23jAoGAGwrf -GFdfppQdWlsnbAFsj4mhXW2SvvwTL+65MdilTtPmcPmPU2gqlWaClpd2nMww6Ihu -nt9SkD7gsTe/PqN9PaldajdJfyZUw2lA1pPoTVDHfC4V1jpmH26wOob3ira01lWK -cW4NdcfjSh7s1Br45h/RYtgobTjsvV+Jum1oNW8CgYAeY82AEydYzqjZRH66xAjQ -pSxaT6B2YS5JqJFbRAtHA/ndApHbW7ALX5vqoJ6EkJ+aORQQMZUkX32n3Y0WFNE2 -FR9JEcnBjimwmG+JTBRCk1luamsZZpH/sHZohH1bsq9+dGo9xIeeBdi5DPxpdsKM -6NuWljVhDbuJeRiamlo5tg== ------END PRIVATE KEY----- diff --git a/Tokenization/backend/wrapper/src/test/testCerts/testCerts.ts b/Tokenization/backend/wrapper/src/test/testCerts/testCerts.ts index d47f86cd7..3e2f3ef93 100644 --- a/Tokenization/backend/wrapper/src/test/testCerts/testCerts.ts +++ b/Tokenization/backend/wrapper/src/test/testCerts/testCerts.ts @@ -28,15 +28,20 @@ export const getTestClientListenerCertPaths = __dirname, "./clientListener/client-b-client.crt" ); - const CLIENT_KEY_PATH = path.join( + const CLIENT_PRIVATE_KEY_PATH = path.join( __dirname, "./clientListener/client-b.key" ); + const CLIENT_PUBLIC_KEY_PATH = path.join( + __dirname, + "./clientListener/client-b.pub.pem" + ); return { caCertPath: CA_CERT_PATH, certPath: CLIENT_CERT_PATH, - publicKeyPath: CLIENT_KEY_PATH, + privateKeyPath: CLIENT_PRIVATE_KEY_PATH, + publicKeyPath: CLIENT_PUBLIC_KEY_PATH, }; }; @@ -47,15 +52,20 @@ export const getTestClientListenerServerCertPaths = __dirname, "./clientListenerServer/client-b-server.crt" ); - const CLIENT_KEY_PATH = path.join( + const CLIENT_PRIVATE_KEY_PATH = path.join( + __dirname, + "./clientListener/client-b.key" + ); + const CLIENT_PUBLIC_KEY_PATH = path.join( __dirname, - "./clientListenerServer/client-b.key" + "./clientListener/client-b.pub.pem" ); return { caCertPath: CA_CERT_PATH, certPath: CLIENT_CERT_PATH, - publicKeyPath: CLIENT_KEY_PATH, + publicKeyPath: CLIENT_PUBLIC_KEY_PATH, + privateKeyPath: CLIENT_PRIVATE_KEY_PATH, }; }; @@ -66,12 +76,20 @@ export const getTestClientSenderCertPaths = __dirname, "./clientSender/client-a-client.crt" ); - const CLIENT_KEY_PATH = path.join(__dirname, "./clientSender/client-a.key"); + const CLIENT_PRIVATE_KEY_PATH = path.join( + __dirname, + "./clientSender/client-a.key" + ); + const CLIENT_PUBLIC_KEY_PATH = path.join( + __dirname, + "./clientSender/client-a.pub.pem" + ); return { caCertPath: CA_CERT_PATH, certPath: CLIENT_CERT_PATH, - publicKeyPath: CLIENT_KEY_PATH, + privateKeyPath: CLIENT_PRIVATE_KEY_PATH, + publicKeyPath: CLIENT_PUBLIC_KEY_PATH, }; }; diff --git a/Tokenization/backend/wrapper/src/utils/security/SecurityContext.ts b/Tokenization/backend/wrapper/src/utils/security/SecurityContext.ts index 0a4cb1644..a60315036 100644 --- a/Tokenization/backend/wrapper/src/utils/security/SecurityContext.ts +++ b/Tokenization/backend/wrapper/src/utils/security/SecurityContext.ts @@ -19,26 +19,29 @@ export class SecurityContext { // mTLS keys (RSA) public readonly caCert: Buffer; public readonly clientSenderCert: Buffer; - public readonly clientListenerCert: Buffer; + public readonly clientListenerCert?: Buffer; public readonly clientPublicKey: Buffer; // RSA Private Key (PKCS8) for JWE decryption public readonly clientPrivateKey: Buffer; // Public Ed25519 key for JWS verification - public static readonly JWS_PUBLIC_KEY = - "VqkcxlpJYVZI/SxgWH/VqVNeKhMGIbUfHn0okzdGs2E="; + public readonly JWS_PUBLIC_KEY = + "hTb3l5gwoIWISOLi6cQMwcultawKyA6vxnimXWtE6JI="; constructor( caCert: Buffer, clientSenderCert: Buffer, - clientListenerCert: Buffer, clientPrivateKey: Buffer, - clientPublicKey: Buffer + clientPublicKey: Buffer, + clientListenerCert?: Buffer ) { this.caCert = caCert; this.clientSenderCert = clientSenderCert; - this.clientListenerCert = clientListenerCert; this.clientPrivateKey = clientPrivateKey; this.clientPublicKey = clientPublicKey; + + if (clientListenerCert) { + this.clientListenerCert = clientListenerCert; + } } } From cbc3ede7b1c48b2ea50ecd57f6479322b55aa720 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Wed, 1 Oct 2025 21:34:25 +0200 Subject: [PATCH 070/103] fix: unit tests and update token interface --- .../Interceptors/grpc.auth.interceptor.ts | 86 +++++++++++++++---- .../wrapper/src/models/connection.model.ts | 18 ++-- .../grpc.auth.interceptor.test.ts | 24 ------ .../src/utils/security/SecurityContext.ts | 12 ++- 4 files changed, 93 insertions(+), 47 deletions(-) diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts index c9a271d56..5e959ed2b 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts @@ -156,23 +156,15 @@ export const gRPCAuthInterceptor = async ( const payloadString = new TextDecoder().decode(jwtPayload); payload = JSON.parse(payloadString); } catch (e: any) { - const isExpired = e.message?.includes("expired"); const error = { name: "AuthenticationError", - message: `JWS Verification error: ${ - isExpired ? "Token expired" : "Invalid signature" - }`, - code: isExpired - ? grpc.status.UNAUTHENTICATED - : grpc.status.PERMISSION_DENIED, + message: `JWS Verification error: Invalid signature`, + code: grpc.status.PERMISSION_DENIED, }; // TODO: Consider logging or informing a central security system about failed verification. callback(error, null); - if (!isExpired) { - conn.handleFailedAuth(); - } - + conn.handleFailedAuth(); return { isAuthenticated: false, conn }; } @@ -207,11 +199,25 @@ export const isRequestAllowed = ( callback: grpc.sendUnaryData ): Boolean => { const method = String(request?.method || "POST").toUpperCase(); - if (!tokenPayload?.perm || !Object.keys(tokenPayload.perm).includes(method)) { + const isValidPayload = validateTokenPayload(tokenPayload, request.method); + let isUnexpired; + + if (isValidPayload) { + isUnexpired = isPermissionUnexpired( + tokenPayload.iat[method], + tokenPayload.exp[method] + ); + } + + if (!isValidPayload || !isUnexpired) { const error = { name: "AuthorizationError", - code: grpc.status.PERMISSION_DENIED, - message: `Request of type ${method} is not allowed by the token policy.`, + code: isUnexpired + ? grpc.status.PERMISSION_DENIED + : grpc.status.UNAUTHENTICATED, + message: isUnexpired + ? `Request of type ${method} is not allowed by the token policy.` + : `Request of type ${method}, permission has expired.`, } as any; callback(error, null); @@ -221,6 +227,56 @@ export const isRequestAllowed = ( return true; }; +/** + * @description Validates the structure and types of the token payload. + * @returns true if token payload is valid, false otherwise + */ +const validateTokenPayload = ( + tokenPayload: TokenPayload | undefined, + method: string +): tokenPayload is TokenPayload => { + if (!tokenPayload) { + return false; + } + + if ( + typeof tokenPayload.iat !== "object" || + typeof tokenPayload.exp !== "object" || + typeof tokenPayload.sub !== "string" || + typeof tokenPayload.aud !== "string" || + typeof tokenPayload.iss !== "string" || + typeof tokenPayload.jti !== "string" || + Object.keys(tokenPayload.iat).length === 0 || + Object.keys(tokenPayload.exp).length === 0 || + !tokenPayload.iat.hasOwnProperty(method) || + !tokenPayload.exp.hasOwnProperty(method) + ) { + return false; + } + + return true; +}; + +/** + * @description Checks if the permissions granted in the token have expired. + * @param iat issued-at timestamp for the specific method + * @param exp expiration timestamp for the specific method + * @returns true if permission is still valid, false if expired + */ +export const isPermissionUnexpired = (iat: number, exp: number): Boolean => { + const nowInSeconds = Math.floor(Date.now() / 1000); + + if (nowInSeconds >= exp) { + return false; + } + + if (iat > nowInSeconds) { + return false; + } + + return true; +}; + /** * @description Checks if the serial number from the peer certificate matches the one in the token payload. * @param tokenPayload payload extracted from the token @@ -234,7 +290,7 @@ export const isSerialNumberMatching = ( callback: grpc.sendUnaryData ): Boolean => { const clientSN = normalizeSerial(peerCert?.serialNumber); - const tokenSN = normalizeSerial(tokenPayload?.subSerialNumber); + const tokenSN = normalizeSerial(tokenPayload?.sub); if (!clientSN || clientSN !== tokenSN) { const error = { diff --git a/Tokenization/backend/wrapper/src/models/connection.model.ts b/Tokenization/backend/wrapper/src/models/connection.model.ts index dc9c97c61..165dee5b6 100644 --- a/Tokenization/backend/wrapper/src/models/connection.model.ts +++ b/Tokenization/backend/wrapper/src/models/connection.model.ts @@ -65,12 +65,20 @@ export type HttpLikeResponse = { body: Buffer; }; -// Payload structure for authentication tokens +/** + * @description Payload structure for authentication tokens + * @sub {string} sub - Subject: Client's certificate serial number + * @aud {string} aud - Audience: Listener's certificate serial number + * @iss {string} iss - Issuer: Central system's certificate serial number + * @iat {Object} iat - Issued At: Permissions granted to the client (e.g., allowed HTTP methods with timestamps) + * @exp {number} exp - Expiration: Expiry timestamps for the granted permissions + * @jti {string} jti - JWT ID: Unique identifier for the token + */ export type TokenPayload = { - subSerialNumber: string; + sub: string; aud: string; - perm: Object; - iat: number; - exp: number; + iss: string; + iat: { [method: string]: number }; + exp: { [method: string]: number }; jti: string; }; diff --git a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts index 791e9a4a8..ac9a170ec 100644 --- a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts @@ -317,30 +317,6 @@ describe("gRPCAuthInterceptor", () => { ); }); - it("should fail if JWS is expired", async () => { - (jose.compactVerify as jest.Mock).mockRejectedValue({ - message: "JWT is expired", - }); - - const result = await interceptor.gRPCAuthInterceptor( - mockCall, - mockCallback, - mockClientConnections as any, - mockSecurityContext - ); - - const created = getCreatedConn(); - expect(result.isAuthenticated).toBe(false); - expect(created!.handleFailedAuth).not.toHaveBeenCalled(); // for expired token, we do not block the connection - expect(mockCallback).toHaveBeenCalledWith( - expect.objectContaining({ - message: "JWS Verification error: Token expired", - code: grpc.status.UNAUTHENTICATED, - }), - null - ); - }); - it("should fail if mTLS serial number mismatch occurs after decryption", async () => { isSerialNumberMatchingSpy.mockImplementation((_p, _pc, cb) => { cb( diff --git a/Tokenization/backend/wrapper/src/utils/security/SecurityContext.ts b/Tokenization/backend/wrapper/src/utils/security/SecurityContext.ts index a60315036..973390045 100644 --- a/Tokenization/backend/wrapper/src/utils/security/SecurityContext.ts +++ b/Tokenization/backend/wrapper/src/utils/security/SecurityContext.ts @@ -25,15 +25,15 @@ export class SecurityContext { public readonly clientPrivateKey: Buffer; // Public Ed25519 key for JWS verification - public readonly JWS_PUBLIC_KEY = - "hTb3l5gwoIWISOLi6cQMwcultawKyA6vxnimXWtE6JI="; + public readonly JWS_PUBLIC_KEY: string; constructor( caCert: Buffer, clientSenderCert: Buffer, clientPrivateKey: Buffer, clientPublicKey: Buffer, - clientListenerCert?: Buffer + clientListenerCert?: Buffer, + JWS_PUBLIC_KEY?: string ) { this.caCert = caCert; this.clientSenderCert = clientSenderCert; @@ -43,5 +43,11 @@ export class SecurityContext { if (clientListenerCert) { this.clientListenerCert = clientListenerCert; } + + if (JWS_PUBLIC_KEY) { + this.JWS_PUBLIC_KEY = JWS_PUBLIC_KEY; + } else { + this.JWS_PUBLIC_KEY = "hTb3l5gwoIWISOLi6cQMwcultawKyA6vxnimXWtE6JI="; + } } } From e5151f185b9c78045f83397334089a4927603a57 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki <42175519+OmegaCreations@users.noreply.github.com> Date: Sat, 4 Oct 2025 17:36:34 +0200 Subject: [PATCH 071/103] Potential fix for code scanning alert no. 254: Unused variable, import, function or class Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .../wrapper/src/client/ConnectionManager/ConnectionManager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts index 3fe1d46aa..b96cd359d 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts @@ -24,7 +24,6 @@ import { DuplexMessageEvent, } from "../../models/message.model"; import { ConnectionStatus } from "../../models/connection.model"; -import * as fs from "fs"; import { gRPCAuthInterceptor } from "./Interceptors/grpc.auth.interceptor"; import { SecurityContext } from "../../utils/security/SecurityContext"; From 37a80219c3fac8f73b4897467677bf5e6f4c9355 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 6 Nov 2025 13:26:50 +0100 Subject: [PATCH 072/103] fix: remove duplicates --- .../wrapper/src/central/CentralSystem.ts | 219 ------------------ .../client/Commands/revokeToken.command.ts | 21 -- .../client/Commands/revokeToken.handler.ts | 34 --- .../backend/wrapper/src/proto/wrapper.proto | 78 ------- .../src/test/central/CentralSystem.test.ts | 132 ----------- .../src/test/client/Commands/newToken.test.ts | 2 +- .../test/client/ConnectionManager/index.ts | 158 ------------- .../test/utils/serialization.utils.test.ts | 71 ------ .../wrapper/src/utils/serialization.utils.ts | 68 ------ 9 files changed, 1 insertion(+), 782 deletions(-) delete mode 100644 Tokenization/backend/wrapper/src/central/CentralSystem.ts delete mode 100644 Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts delete mode 100644 Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts delete mode 100644 Tokenization/backend/wrapper/src/proto/wrapper.proto delete mode 100644 Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts delete mode 100644 Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts delete mode 100644 Tokenization/backend/wrapper/src/test/utils/serialization.utils.test.ts delete mode 100644 Tokenization/backend/wrapper/src/utils/serialization.utils.ts diff --git a/Tokenization/backend/wrapper/src/central/CentralSystem.ts b/Tokenization/backend/wrapper/src/central/CentralSystem.ts deleted file mode 100644 index 58d938eed..000000000 --- a/Tokenization/backend/wrapper/src/central/CentralSystem.ts +++ /dev/null @@ -1,219 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -import * as grpc from "@grpc/grpc-js"; -import * as protoLoader from "@grpc/proto-loader"; -import path from "path"; -import { LogManager } from "@aliceo2/web-ui"; -import { - ConnectionDirection, - DuplexMessageEvent, - DuplexMessageModel, -} from "../models/message.model"; - -/** - * @description Central System gRPC wrapper that manages client connections and handles gRPC streams with them. - */ -export class CentralSystemWrapper { - // utilities - private logger = LogManager.getLogger("CentralSystemWrapper"); - - // class properties - private server: grpc.Server; - - // clients management - private clients = new Map>(); - private clientIps = new Map(); // Peer -> IP map - - /** - * Initializes the Wrapper for CentralSystem. - * @param port The port number to bind the gRPC server to. - */ - constructor(private protoPath: string, private port: number) { - this.server = new grpc.Server(); - this.setupService(); - } - - /** - * @description Loads the gRPC proto definition and sets up the CentralSystem service. - */ - private setupService(): void { - // Load the proto definition with options - const packageDef = protoLoader.loadSync(this.protoPath, { - keepCase: true, - longs: String, - enums: String, - defaults: true, - oneofs: true, - }); - - // Load the package definition into a gRPC object - const proto = grpc.loadPackageDefinition(packageDef) as any; - const wrapper = proto.webui.tokenization; - - // Add the CentralSystem service and bind the stream handler - this.server.addService(wrapper.CentralSystem.service, { - ClientStream: this.clientStreamHandler.bind(this), - }); - } - - /** - * @description Extracts IP address from peer string - * @param peer string e.g. ipv4:127.0.0.1:12345 - * @returns Extracted IP address - */ - private extractIpFromPeer(peer: string): string { - // Context - // IPv4 format: "ipv4:127.0.0.1:12345" - // IPv6 format: "ipv6:[::1]:12345" - - const ipv4Match = peer.match(/^ipv4:(.+?):\d+$/); - if (ipv4Match) return ipv4Match[1]; - - const ipv6Match = peer.match(/^ipv6:\[(.+?)\]:\d+$/); - if (ipv6Match) return ipv6Match[1]; - - // fallback to original peer if pattern doesn't match any - return peer; - } - - /** - * @description Handles the duplex stream from the client. - * @param call The duplex stream call object. - */ - private clientStreamHandler(call: grpc.ServerDuplexStream): void { - const peer = call.getPeer(); - const clientIp = this.extractIpFromPeer(peer); - - this.logger.infoMessage( - `Client ${clientIp} (${peer}) connected to CentralSystem stream` - ); - - // Add client to maps - this.clients.set(clientIp, call); - this.clientIps.set(peer, clientIp); - - // Listen for data events from the client - call.on("data", (payload: any) => { - this.logger.infoMessage(`Received from ${clientIp}:`, payload); - }); - - // Handle stream end event - call.on("end", () => { - this.logger.infoMessage(`Client ${clientIp} ended stream.`); - this.cleanupClient(peer); - call.end(); - }); - - // Handle stream error event - call.on("error", (err) => { - this.logger.infoMessage(`Stream error from client ${clientIp}:`, err); - this.cleanupClient(peer); - }); - } - - /** - * @description Cleans up client resources - * @param peer Original peer string - */ - private cleanupClient(peer: string): void { - const clientIp = this.clientIps.get(peer); - if (clientIp) { - this.clients.delete(clientIp); - this.clientIps.delete(peer); - this.logger.infoMessage(`Cleaned up resources of ${clientIp}`); - } - } - - /** - * @description Sends data to a specific client by IP address - * @param ip Client IP address - * @param data Data to send - * @returns Whether the data was successfully sent - */ - public sendEvent(ip: string, data: DuplexMessageModel): boolean { - const client = this.clients.get(ip); - if (!client) { - this.logger.warnMessage(`Client ${ip} not found for sending event`); - return false; - } - - try { - client.write(data); - this.logger.infoMessage(`Sent event to ${ip}:`, data); - return true; - } catch (err) { - this.logger.errorMessage(`Error sending to ${ip}:`, err); - return false; - } - } - - /** - * @description Gets all connected client IPs - * @returns Array of connected client IPs - */ - public getConnectedClients(): string[] { - return Array.from(this.clients.keys()); - } - - /** - * @desciprion Starts the gRPC server and binds it to the specified in class port. - */ - public listen() { - const addr = `localhost:${this.port}`; - this.server.bindAsync( - addr, - grpc.ServerCredentials.createInsecure(), - (err, _port) => { - if (err) { - this.logger.infoMessage("Server bind error:", err); - return; - } - this.logger.infoMessage(`CentralSytem started listening on ${addr}`); - } - ); - } -} - -// Instantiate the CentralSystemWrapper on port 50051, but don't start automatically -const PROTO_PATH = path.join(__dirname, "../proto/wrapper.proto"); -const centralSystem = new CentralSystemWrapper(PROTO_PATH, 50051); -// Start listening explicitly -centralSystem.listen(); - -setTimeout(() => { - centralSystem.sendEvent(centralSystem.getConnectedClients()[0], { - event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, - payload: { - connectionDirection: ConnectionDirection.SENDING, - targetAddress: "a", - }, - }); - centralSystem.sendEvent(centralSystem.getConnectedClients()[0], { - event: DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN, - payload: { - connectionDirection: ConnectionDirection.SENDING, - targetAddress: "a", - token: "newToken", - }, - }); - centralSystem.sendEvent(centralSystem.getConnectedClients()[0], { - event: DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN, - payload: { - connectionDirection: ConnectionDirection.SENDING, - targetAddress: "c", - token: "tokenForNewAddress", - }, - }); -}, 5000); diff --git a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts b/Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts deleted file mode 100644 index 0e6668ccc..000000000 --- a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.command.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -import { Command } from "../../models/commands.model"; -import { DuplexMessageEvent, TokenMessage } from "../../models/message.model"; - -export class RevokeTokenCommand implements Command { - readonly event = DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN; - constructor(public payload: TokenMessage) {} -} diff --git a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts b/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts deleted file mode 100644 index 1cf9d00d8..000000000 --- a/Tokenization/backend/wrapper/src/client/Commands/revokeToken.handler.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ -import { CommandHandler } from "../../models/commands.model"; -import { RevokeTokenCommand } from "./revokeToken.command"; -import { ConnectionManager } from "../ConnectionManager/ConnectionManager"; - -export class RevokeTokenHandler implements CommandHandler { - constructor(private manager: ConnectionManager) {} - - async handle(command: RevokeTokenCommand): Promise { - const { targetAddress } = command.payload || {}; - if (!targetAddress) { - throw new Error("Target address is required to revoke token."); - } - - const conn = this.manager.getConnectionByAddress( - targetAddress, - command.payload.connectionDirection - ); - - conn?.handleRevokeToken(); - } -} diff --git a/Tokenization/backend/wrapper/src/proto/wrapper.proto b/Tokenization/backend/wrapper/src/proto/wrapper.proto deleted file mode 100644 index 6688164da..000000000 --- a/Tokenization/backend/wrapper/src/proto/wrapper.proto +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -syntax = "proto3"; - -package webui.tokenization; - -// ====================================== -// SERVICES -// ====================================== - -// Central System service handling duplex communication with wrapper client -service CentralSystem { - rpc ClientStream(stream Payload) returns (stream Payload); -} - -// ====================================== -// MESSAGES -// ====================================== - -// Simulates an empty message because protobuffer doesn't support void -message EmptyMessage {} - -// Message with token and target address binded to it -message Token { - string token = 1; - string targetAddress = 2; - ConnectionDirection connectionDirection = 3; -} - -// Stream message that can contain one of specific messages -message Payload { - // Message event type - MessageEvent event = 1; - - // Data related to specific event type - oneof data { - EmptyMessage emptyMessage = 2; - Token payload = 3; - } -} - -// ====================================== -// ENUMS -// ====================================== - -enum MessageEvent { - // Default value, represents an empty event - MESSAGE_EVENT_EMPTY = 0; - - // New token message type, contains a new token and target address - MESSAGE_EVENT_NEW_TOKEN = 1; - - // Revoke token message type, contains a token to be revoked - MESSAGE_EVENT_REVOKE_TOKEN = 2; -} - -enum ConnectionDirection { - // Direction from client to server - SENDING = 1; - - // Direction from server to client - RECEIVING = 2; - - // Duplex connection, both sending and receiving - DUPLEX = 3; -} \ No newline at end of file diff --git a/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts b/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts deleted file mode 100644 index 3b85e4c6e..000000000 --- a/Tokenization/backend/wrapper/src/test/central/CentralSystem.test.ts +++ /dev/null @@ -1,132 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -const mockAddService = jest.fn(); -const mockBindAsync = jest.fn(); -const mockServerInstance = { - addService: mockAddService, - bindAsync: mockBindAsync, -}; - -const logger = { - infoMessage: jest.fn(), -}; - -jest.mock("@aliceo2/web-ui", () => ({ - LogManager: { - getLogger: () => logger, - }, -})); - -jest.mock("@grpc/proto-loader", () => ({ - loadSync: jest.fn(() => { - return {}; - }), -})); - -jest.mock("@grpc/grpc-js", () => { - const original = jest.requireActual("@grpc/grpc-js"); - return { - ...original, - Server: jest.fn(() => mockServerInstance), - ServerCredentials: { - createInsecure: jest.fn(() => "mock-credentials"), - }, - loadPackageDefinition: jest.fn(() => ({ - webui: { - tokenization: { - CentralSystem: { - service: "mock-service", - }, - }, - }, - })), - }; -}); - -import { CentralSystemWrapper } from "../../central/CentralSystem"; -import * as grpc from "@grpc/grpc-js"; - -describe("CentralSystemWrapper", () => { - let wrapper: CentralSystemWrapper; - - beforeEach(() => { - jest.clearAllMocks(); - wrapper = new CentralSystemWrapper("dummy.proto", 12345); - }); - - test("should set up gRPC service and add it to the server", () => { - expect(grpc.Server).toHaveBeenCalled(); - expect(grpc.loadPackageDefinition).toHaveBeenCalled(); - expect(grpc.ServerCredentials.createInsecure).not.toHaveBeenCalled(); - expect(wrapper).toBeDefined(); - }); - - test("should call listen and bind the server", () => { - mockBindAsync.mockImplementation((_addr, _creds, cb) => cb(null, 12345)); - - wrapper.listen(); - - expect(mockBindAsync).toHaveBeenCalledWith( - "localhost:12345", - "mock-credentials", - expect.any(Function) - ); - }); - - test("should log error if bind fails", () => { - const error = new Error("bind failed"); - mockBindAsync.mockImplementation((_addr, _creds, cb) => cb(error, null)); - - wrapper.listen(); - - expect(logger.infoMessage).toHaveBeenCalledWith( - "Server bind error:", - error - ); - }); - - test("should handle client stream events", () => { - const logger = require("@aliceo2/web-ui").LogManager.getLogger(); - - const mockCall = { - getPeer: jest.fn(() => "client123"), - on: jest.fn((event, cb) => { - if (event === "end") cb(); - if (event === "error") cb(new Error("stream error")); - }), - end: jest.fn(), - }; - - const handler = (wrapper as any).clientStreamHandler.bind(wrapper); - handler(mockCall); - - expect(mockCall.on).toHaveBeenCalledWith("data", expect.any(Function)); - expect(mockCall.on).toHaveBeenCalledWith("end", expect.any(Function)); - expect(mockCall.on).toHaveBeenCalledWith("error", expect.any(Function)); - - expect(mockCall.end).toHaveBeenCalled(); - expect(logger.infoMessage).toHaveBeenCalledWith( - expect.stringContaining("Client client123") - ); - - expect(logger.infoMessage).toHaveBeenCalledWith( - "Client client123 ended stream." - ); - expect(logger.infoMessage).toHaveBeenCalledWith( - "Stream error from client client123:", - expect.any(Error) - ); - }); -}); diff --git a/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts b/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts index 91f109204..3c27377e6 100644 --- a/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts @@ -91,7 +91,7 @@ describe("NewTokenHandler", () => { await handler.handle(command); - expect(conn.getToken()).toBe("test-token"); + expect(conn.token).toBe("test-token"); }); it("should create new RECEIVING connection if not found", async () => { diff --git a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts deleted file mode 100644 index f60739154..000000000 --- a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/index.ts +++ /dev/null @@ -1,158 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -import * as grpc from "@grpc/grpc-js"; -import { ConnectionManager } from "../../../client/ConnectionManager/ConnectionManager"; -import { DuplexMessageEvent } from "../../../models/message.model"; - -// Mock duplex stream -const mockStream = { - on: jest.fn(), - end: jest.fn(), -}; - -// Mock gRPC client -const mockClient = { - ClientStream: jest.fn(() => mockStream), -}; - -// Mock CentralSystem constructor -const CentralSystemMock = jest.fn(() => mockClient); - -// Mock dispatcher -const mockDispatch = jest.fn(); -jest.mock( - "../../../client/ConnectionManager/EventManagement/CentralCommandDispatcher", - () => ({ - CentralCommandDispatcher: jest.fn(() => ({ - dispatch: mockDispatch, - })), - }) -); - -// Mock logger -jest.mock("@aliceo2/web-ui", () => ({ - LogManager: { - getLogger: () => ({ - infoMessage: jest.fn(), - }), - }, -})); - -// Mock gRPC proto loader and client -jest.mock("@grpc/proto-loader", () => ({ - loadSync: jest.fn(() => { - return {}; - }), -})); - -jest.mock("@grpc/grpc-js", () => { - const original = jest.requireActual("@grpc/grpc-js"); - return { - ...original, - credentials: { - createInsecure: jest.fn(), - }, - loadPackageDefinition: jest.fn(() => ({ - webui: { - tokenization: { - CentralSystem: CentralSystemMock, - }, - }, - })), - }; -}); - -describe("ConnectionManager", () => { - let conn: ConnectionManager; - - beforeEach(() => { - jest.clearAllMocks(); - conn = new ConnectionManager("dummy.proto", "localhost:12345"); - }); - - test("should initialize client with correct address", () => { - expect(conn).toBeDefined(); - expect(grpc.loadPackageDefinition).toHaveBeenCalled(); - expect(CentralSystemMock).toHaveBeenCalledWith( - "localhost:12345", - undefined - ); - }); - - test("connectToCentralSystem() should set up stream listeners", () => { - conn.connectToCentralSystem(); - - expect(mockClient.ClientStream).toHaveBeenCalled(); - expect(mockStream.on).toHaveBeenCalledWith("data", expect.any(Function)); - expect(mockStream.on).toHaveBeenCalledWith("end", expect.any(Function)); - expect(mockStream.on).toHaveBeenCalledWith("error", expect.any(Function)); - }); - - test("disconnectFromCentralSystem() should end stream", () => { - conn.connectToCentralSystem(); - conn.disconnectFromCentralSystem(); - - expect(mockStream.end).toHaveBeenCalled(); - }); - - test("should reconnect on stream 'end'", () => { - jest.useFakeTimers(); - conn.connectToCentralSystem(); - const onEnd = mockStream.on.mock.calls.find( - ([event]) => event === "end" - )?.[1]; - - onEnd?.(); // simulate 'end' - jest.advanceTimersByTime(2000); - - expect(mockClient.ClientStream).toHaveBeenCalledTimes(2); - jest.useRealTimers(); - }); - - test("should reconnect on stream 'error'", () => { - jest.useFakeTimers(); - conn.connectToCentralSystem(); - const onError = mockStream.on.mock.calls.find( - ([event]) => event === "error" - )?.[1]; - - onError?.(new Error("Simulated error")); - jest.advanceTimersByTime(2000); - - expect(mockClient.ClientStream).toHaveBeenCalledTimes(2); - jest.useRealTimers(); - }); - - test("should dispatch event when 'data' is received", () => { - conn.connectToCentralSystem(); - const onData = mockStream.on.mock.calls.find( - ([event]) => event === "data" - )?.[1]; - - const mockMessage = { - event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, - data: { - revokeToken: { - token: "abc123", - targetAddress: "peer-123", - }, - }, - }; - - onData?.(mockMessage); - - expect(mockDispatch).toHaveBeenCalledWith(mockMessage); - }); -}); diff --git a/Tokenization/backend/wrapper/src/test/utils/serialization.utils.test.ts b/Tokenization/backend/wrapper/src/test/utils/serialization.utils.test.ts deleted file mode 100644 index 93d5c7d7c..000000000 --- a/Tokenization/backend/wrapper/src/test/utils/serialization.utils.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -import { - deserializeRequest, - serializeRequest, -} from "../../utils/serialization.utils"; -import { describe, expect, test } from "@jest/globals"; - -describe("serializeRequest", () => { - test("serializes URL and options correctly", () => { - const url = "/api/test"; - const options = { - method: "POST", - headers: { "Content-Type": "application/json" }, - }; - - const buffer = serializeRequest(url, options); - const view = new Uint8Array(buffer); - - const typeLength = view[0]; - const typeBytes = view.slice(1, 1 + typeLength); - const jsonBytes = view.slice(1 + typeLength); - - const contentType = new TextDecoder().decode(typeBytes); - const json = JSON.parse(new TextDecoder().decode(jsonBytes)); - - expect(contentType).toBe("application/json"); - expect(json.url).toBe(url); - expect(json.options.method).toBe(options.method); - }); -}); - -describe("deserializeRequest", () => { - test("deserializes payload into correct request object", () => { - const url = "/api/test"; - const options = { method: "GET" }; - - const buffer = serializeRequest(url, options); - const result = deserializeRequest(buffer); - - expect(result.url).toBe(url); - expect(result.options.method).toBe("GET"); - }); - - test("throws error on unsupported content type", () => { - const encoder = new TextEncoder(); - const badType = encoder.encode("text/plain"); - const json = encoder.encode(JSON.stringify({ url: "/x" })); - - const buffer = new Uint8Array(1 + badType.length + json.length); - buffer[0] = badType.length; - buffer.set(badType, 1); - buffer.set(json, 1 + badType.length); - - expect(() => { - deserializeRequest(buffer.buffer); - }).toThrow("Unsupported content type: text/plain"); - }); -}); diff --git a/Tokenization/backend/wrapper/src/utils/serialization.utils.ts b/Tokenization/backend/wrapper/src/utils/serialization.utils.ts deleted file mode 100644 index 57ca8bcd2..000000000 --- a/Tokenization/backend/wrapper/src/utils/serialization.utils.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -/** - * @description Serializes Json formatted request into binary payload with specific endpoint - * @param url - The endpoint URL to which the request is made - * @param options - Request options, such as headers or body - * @return {ArrayBuffer} - The serialized binary payload containing the URL and options - */ -export const serializeRequest = (url: string, options: any): ArrayBuffer => { - const encoder = new TextEncoder(); - const contentTypeBytes = encoder.encode("application/json"); - - // build JSON data - const jsonData = { - url: url, - options: options, - }; - - const jsonString = JSON.stringify(jsonData); - const jsonBytes = encoder.encode(jsonString); - - // Buffer following structure: - // 1 byte -> type length - // N bytes -> types - // rest -> JSON data - const buffer = new Uint8Array(1 + contentTypeBytes.length + jsonBytes.length); - - buffer[0] = contentTypeBytes.length; - buffer.set(contentTypeBytes, 1); - buffer.set(jsonBytes, 1 + contentTypeBytes.length); - - return buffer.buffer; -}; - -/** - * @description Deserializes binary payload to Json formated request - * @param payload - The binary payload to deserialize - * @return {any} - The deserialized request object containing the URL and options - */ -export const deserializeRequest = (payload: ArrayBuffer): any => { - const view = new Uint8Array(payload); - const decoder = new TextDecoder(); - - const contentTypeLength = view[0]; - const contentTypeBytes = view.slice(1, 1 + contentTypeLength); - const contentType = decoder.decode(contentTypeBytes); - - const dataBytes = view.slice(1 + contentTypeLength); - - // deserialization of JSON content - if (contentType === "application/json") { - return JSON.parse(decoder.decode(dataBytes)); - } else { - throw new Error(`Unsupported content type: ${contentType}`); - } -}; From feeb637f41d20043cac285453c918bd2753ae04a Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 6 Nov 2025 13:27:57 +0100 Subject: [PATCH 073/103] fix: unit tests --- .../src/client/Commands/newToken/newToken.handler.ts | 2 +- .../wrapper/src/test/client/Commands/newToken.test.ts | 6 +++--- Tokenization/package-lock.json | 6 ------ 3 files changed, 4 insertions(+), 10 deletions(-) delete mode 100644 Tokenization/package-lock.json diff --git a/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts b/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts index cac108af3..a7c0fcd7f 100644 --- a/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts +++ b/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts @@ -51,7 +51,7 @@ export class NewTokenHandler implements CommandHandler { if (!conn) { conn = this.manager.createNewConnection(targetAddress, dir, token); } - conn.handleNewToken(token); + conn.token = token; } } } diff --git a/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts b/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts index 3c27377e6..be51dbe44 100644 --- a/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts @@ -106,7 +106,7 @@ describe("NewTokenHandler", () => { const conn = (manager as any).receivingConnections.get(targetAddress); expect(conn).toBeDefined(); - expect(conn.getToken()).toBe("test-token"); + expect(conn.token).toBe("test-token"); }); it("should handle DUPLEX direction by updating/creating both connections", async () => { @@ -126,8 +126,8 @@ describe("NewTokenHandler", () => { expect(sendingConn).toBeDefined(); expect(receivingConn).toBeDefined(); - expect(sendingConn.getToken()).toBe("test-token"); - expect(receivingConn.getToken()).toBe("test-token"); + expect(sendingConn.token).toBe("test-token"); + expect(receivingConn.token).toBe("test-token"); }); it("should throw error when payload is missing required fields", async () => { diff --git a/Tokenization/package-lock.json b/Tokenization/package-lock.json deleted file mode 100644 index c7142dc66..000000000 --- a/Tokenization/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Tokenization", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} From 44f58e4eb6286873e43980ed519f91f127fe6dc2 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 6 Nov 2025 19:16:09 +0100 Subject: [PATCH 074/103] fix: move .gitignore to parent .gitignore --- Tokenization/.gitignore | 5 ++++- Tokenization/backend/wrapper/.gitignore | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 Tokenization/backend/wrapper/.gitignore diff --git a/Tokenization/.gitignore b/Tokenization/.gitignore index dba9268cc..b391dd070 100644 --- a/Tokenization/.gitignore +++ b/Tokenization/.gitignore @@ -2,4 +2,7 @@ webapp/node_modules webapp/.react-router webapp/build -backend/node_modules \ No newline at end of file +backend/node_modules +backend/wrapper/dist/ +backend/wrapper/node_modules/ +backend/wrapper/src/run_tests/ \ No newline at end of file diff --git a/Tokenization/backend/wrapper/.gitignore b/Tokenization/backend/wrapper/.gitignore deleted file mode 100644 index 484d4560e..000000000 --- a/Tokenization/backend/wrapper/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -dist/ -node_modules/ -run_tests/ From c87f1f19ef7b46a27a9aba2319577ff1d2403387 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 6 Nov 2025 19:19:30 +0100 Subject: [PATCH 075/103] fix: default wrapper port changed to 4100 --- Tokenization/backend/wrapper/src/client/gRPCWrapper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index d88546149..73f247589 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -39,9 +39,9 @@ export class gRPCWrapper { * @description Initializes an instance of gRPCWrapper class. * * @param protoPath - The file path to the gRPC proto definition. - * @param centralAddress - The address of the central gRPC server (default: "localhost:50051"). + * @param centralAddress - The address of the central gRPC server (default: "localhost:4100"). */ - constructor(protoPath: string, centralAddress: string = "localhost:50051") { + constructor(protoPath: string, centralAddress: string = "localhost:4100") { this.ConnectionManager = new ConnectionManager(protoPath, centralAddress); this.ConnectionManager.registerCommandHandlers([ { From b2f24c64a182152b5fbccf86fc160f6f0a1de046 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 6 Nov 2025 19:26:23 +0100 Subject: [PATCH 076/103] fix: change private variables naming --- .../src/central/CentralSystemWrapper.ts | 56 ++++++++++--------- .../src/client/Connection/Connection.ts | 4 +- .../ConnectionManager/CentralConnection.ts | 42 +++++++------- .../ConnectionManager/ConnectionManager.ts | 45 ++++++--------- .../CentralCommandDispatcher.ts | 16 +++--- .../backend/wrapper/src/client/gRPCWrapper.ts | 16 +++--- 6 files changed, 88 insertions(+), 91 deletions(-) diff --git a/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts b/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts index a007c2ed9..dd20fbcaa 100644 --- a/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts +++ b/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts @@ -20,22 +20,26 @@ import { DuplexMessageModel } from "../models/message.model"; * @description Central System gRPC wrapper that manages client connections and handles gRPC streams with them. */ export class CentralSystemWrapper { + // config + private _protoPath: string; + // utilities - private logger = LogManager.getLogger("CentralSystemWrapper"); + private _logger = LogManager.getLogger("CentralSystemWrapper"); // class properties - private server: grpc.Server; + private _server: grpc.Server; // clients management - private clients = new Map>(); - private clientIps = new Map(); // Peer -> IP map + private _clients = new Map>(); + private _clientIps = new Map(); // Peer -> IP map /** * Initializes the Wrapper for CentralSystem. * @param port The port number to bind the gRPC server to. */ - constructor(private protoPath: string, private port: number) { - this.server = new grpc.Server(); + constructor(protoPath: string, private port: number) { + this._protoPath = protoPath; + this._server = new grpc.Server(); this.setupService(); } @@ -44,7 +48,7 @@ export class CentralSystemWrapper { */ private setupService(): void { // Load the proto definition with options - const packageDef = protoLoader.loadSync(this.protoPath, { + const packageDef = protoLoader.loadSync(this._protoPath, { keepCase: true, longs: String, enums: String, @@ -57,7 +61,7 @@ export class CentralSystemWrapper { const wrapper = proto.webui.tokenization; // Add the CentralSystem service and bind the stream handler - this.server.addService(wrapper.CentralSystem.service, { + this._server.addService(wrapper.CentralSystem.service, { ClientStream: this.clientStreamHandler.bind(this), }); } @@ -90,29 +94,29 @@ export class CentralSystemWrapper { const peer = call.getPeer(); const clientIp = this.extractIpFromPeer(peer); - this.logger.infoMessage( + this._logger.infoMessage( `Client ${clientIp} (${peer}) connected to CentralSystem stream` ); // Add client to maps - this.clients.set(clientIp, call); - this.clientIps.set(peer, clientIp); + this._clients.set(clientIp, call); + this._clientIps.set(peer, clientIp); // Listen for data events from the client call.on("data", (payload: any) => { - this.logger.infoMessage(`Received from ${clientIp}:`, payload); + this._logger.infoMessage(`Received from ${clientIp}:`, payload); }); // Handle stream end event call.on("end", () => { - this.logger.infoMessage(`Client ${clientIp} ended stream.`); + this._logger.infoMessage(`Client ${clientIp} ended stream.`); this.cleanupClient(peer); call.end(); }); // Handle stream error event call.on("error", (err) => { - this.logger.infoMessage(`Stream error from client ${clientIp}:`, err); + this._logger.infoMessage(`Stream error from client ${clientIp}:`, err); this.cleanupClient(peer); }); } @@ -122,11 +126,11 @@ export class CentralSystemWrapper { * @param peer Original peer string */ private cleanupClient(peer: string): void { - const clientIp = this.clientIps.get(peer); + const clientIp = this._clientIps.get(peer); if (clientIp) { - this.clients.delete(clientIp); - this.clientIps.delete(peer); - this.logger.infoMessage(`Cleaned up resources of ${clientIp}`); + this._clients.delete(clientIp); + this._clientIps.delete(peer); + this._logger.infoMessage(`Cleaned up resources of ${clientIp}`); } } @@ -137,18 +141,18 @@ export class CentralSystemWrapper { * @returns Whether the data was successfully sent */ public sendEvent(ip: string, data: DuplexMessageModel): boolean { - const client = this.clients.get(ip); + const client = this._clients.get(ip); if (!client) { - this.logger.warnMessage(`Client ${ip} not found for sending event`); + this._logger.warnMessage(`Client ${ip} not found for sending event`); return false; } try { client.write(data); - this.logger.infoMessage(`Sent event to ${ip}:`, data); + this._logger.infoMessage(`Sent event to ${ip}:`, data); return true; } catch (err) { - this.logger.errorMessage(`Error sending to ${ip}:`, err); + this._logger.errorMessage(`Error sending to ${ip}:`, err); return false; } } @@ -158,7 +162,7 @@ export class CentralSystemWrapper { * @returns Array of connected client IPs */ public get connectedClients(): string[] { - return Array.from(this.clients.keys()); + return Array.from(this._clients.keys()); } /** @@ -166,15 +170,15 @@ export class CentralSystemWrapper { */ public listen() { const addr = `localhost:${this.port}`; - this.server.bindAsync( + this._server.bindAsync( addr, grpc.ServerCredentials.createInsecure(), (err, _port) => { if (err) { - this.logger.infoMessage("Server bind error:", err); + this._logger.infoMessage("Server bind error:", err); return; } - this.logger.infoMessage(`CentralSytem started listening on ${addr}`); + this._logger.infoMessage(`CentralSytem started listening on ${addr}`); } ); } diff --git a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts index 86c0464e1..2a6684cd6 100644 --- a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts +++ b/Tokenization/backend/wrapper/src/client/Connection/Connection.ts @@ -22,6 +22,7 @@ export class Connection { private _token: string; private _targetAddress: string; private _status: ConnectionStatus; + public direction: ConnectionDirection; /** * @description Creates a new Connection instance with the given token, target address, and connection direction. @@ -33,10 +34,11 @@ export class Connection { constructor( token: string, targetAddress: string, - public direction: ConnectionDirection + direction: ConnectionDirection ) { this._token = token; this._targetAddress = targetAddress; + this.direction = direction; this._status = ConnectionStatus.CONNECTED; } diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts index f41a586fd..fec725dca 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts @@ -22,40 +22,40 @@ import { DuplexMessageModel } from "../../models/message.model"; * It is responsible for connecting, reconnecting with backoff, and delegating received messages. */ export class CentralConnection { - private logger = LogManager.getLogger("CentralConnection"); - private stream?: grpc.ClientDuplexStream; + private _logger = LogManager.getLogger("CentralConnection"); + private _stream?: grpc.ClientDuplexStream; constructor( - private client: any, - private dispatcher: CentralCommandDispatcher + private _client: any, + private _dispatcher: CentralCommandDispatcher ) {} /** * @description Initializes the duplex stream and sets up event handlers. */ connect() { - if (this.stream) return; + if (this._stream) return; - this.stream = this.client.ClientStream(); + this._stream = this._client.ClientStream(); - this.stream!.on("data", (payload: DuplexMessageModel) => { - this.logger.debugMessage(`Received payload: ${JSON.stringify(payload)}`); - this.dispatcher.dispatch(payload); + this._stream!.on("data", (payload: DuplexMessageModel) => { + this._logger.debugMessage(`Received payload: ${JSON.stringify(payload)}`); + this._dispatcher.dispatch(payload); }); - this.stream!.on("end", () => { - this.logger.infoMessage(`Stream ended, attempting to reconnect...`); - this.stream = undefined; + this._stream!.on("end", () => { + this._logger.infoMessage(`Stream ended, attempting to reconnect...`); + this._stream = undefined; this.scheduleReconnect(); }); - this.stream!.on("error", (err: any) => { - this.logger.infoMessage( + this._stream!.on("error", (err: any) => { + this._logger.infoMessage( "Stream error:", err, " attempting to reconnect..." ); - this.stream = undefined; + this._stream = undefined; this.scheduleReconnect(); }); } @@ -65,7 +65,7 @@ export class CentralConnection { */ private scheduleReconnect() { setTimeout(() => { - this.logger.infoMessage(`Trying to reconnect...`); + this._logger.infoMessage(`Trying to reconnect...`); this.connect(); }, 2000); } @@ -75,17 +75,17 @@ export class CentralConnection { */ start() { this.connect(); - this.logger.infoMessage(`Connected to CentralSystem`); + this._logger.infoMessage(`Connected to CentralSystem`); } /** * @description Disconnects from the gRPC stream and resets attempts. */ disconnect() { - if (this.stream) { - this.stream.end(); - this.stream = undefined; + if (this._stream) { + this._stream.end(); + this._stream = undefined; } - this.logger.infoMessage(`Disconnected from CentralSystem`); + this._logger.infoMessage(`Disconnected from CentralSystem`); } } diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts index d426e96c1..f211e1aed 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts @@ -42,11 +42,11 @@ import { * - `receivingConnections`: Map of active inbound connections. */ export class ConnectionManager { - private logger = LogManager.getLogger("ConnectionManager"); - private centralDispatcher: CentralCommandDispatcher; - private centralConnection: CentralConnection; - private sendingConnections = new Map(); - private receivingConnections = new Map(); + private _logger = LogManager.getLogger("ConnectionManager"); + private _centralDispatcher: CentralCommandDispatcher; + private _centralConnection: CentralConnection; + private _sendingConnections = new Map(); + private _receivingConnections = new Map(); /** * @description Initializes a new instance of the ConnectionManager class. @@ -74,19 +74,10 @@ export class ConnectionManager { ); // event dispatcher for central system events - this.centralDispatcher = new CentralCommandDispatcher(); - this.centralConnection = new CentralConnection( + this._centralDispatcher = new CentralCommandDispatcher(); + this._centralConnection = new CentralConnection( client, - this.centralDispatcher - ); - - this.sendingConnections.set( - "a", - new Connection("1", "a", ConnectionDirection.SENDING) - ); - this.sendingConnections.set( - "b", - new Connection("2", "b", ConnectionDirection.SENDING) + this._centralDispatcher ); } @@ -101,7 +92,7 @@ export class ConnectionManager { }[] ): void { commandHandlers.forEach(({ event, handler }) => { - this.centralDispatcher.register(event, handler); + this._centralDispatcher.register(event, handler); }); } @@ -109,14 +100,14 @@ export class ConnectionManager { * @description Starts the connection to the central system. */ connectToCentralSystem(): void { - this.centralConnection.start(); + this._centralConnection.start(); } /** * @description Disconnects from the central system. */ disconnectFromCentralSystem(): void { - this.centralConnection.disconnect(); + this._centralConnection.disconnect(); } /** @@ -133,9 +124,9 @@ export class ConnectionManager { const conn = new Connection(token || "", address, direction); if (direction === ConnectionDirection.RECEIVING) { - this.receivingConnections.set(address, conn); + this._receivingConnections.set(address, conn); } else { - this.sendingConnections.set(address, conn); + this._sendingConnections.set(address, conn); } return conn; @@ -151,11 +142,11 @@ export class ConnectionManager { ): Connection | undefined { switch (direction) { case ConnectionDirection.SENDING: - return this.sendingConnections.get(address); + return this._sendingConnections.get(address); case ConnectionDirection.RECEIVING: - return this.receivingConnections.get(address); + return this._receivingConnections.get(address); default: - this.logger.errorMessage(`Invalid connection direction: ${direction}`); + this._logger.errorMessage(`Invalid connection direction: ${direction}`); return undefined; } } @@ -170,8 +161,8 @@ export class ConnectionManager { receiving: Connection[]; } { return { - sending: [...this.sendingConnections.values()], - receiving: [...this.receivingConnections.values()], + sending: [...this._sendingConnections.values()], + receiving: [...this._receivingConnections.values()], }; } } diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts index 3a433edbc..83ba83990 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts @@ -22,8 +22,8 @@ import { DuplexMessageEvent } from "../../../models/message.model"; * command messages coming from central system to the appropriate handler functions. */ export class CentralCommandDispatcher { - private handlers = new Map(); - private logger = LogManager.getLogger("CentralCommandDispatcher"); + private _handlers = new Map(); + private _logger = LogManager.getLogger("CentralCommandDispatcher"); /** * Registers a command handler for a specific command event type. @@ -35,8 +35,8 @@ export class CentralCommandDispatcher { event: DuplexMessageEvent, handler: CommandHandler ): void { - this.logger.infoMessage(`Registering handler for command type: ${event}`); - this.handlers.set(event, handler); + this._logger.infoMessage(`Registering handler for command type: ${event}`); + this._handlers.set(event, handler); } /** @@ -46,10 +46,10 @@ export class CentralCommandDispatcher { * @param command - The command object containing an event and its associated payload. */ async dispatch(command: Command): Promise { - const handler = this.handlers.get(command.event); - this.logger.debugMessage(`Dispatching command: ${command.event}`); + const handler = this._handlers.get(command.event); + this._logger.debugMessage(`Dispatching command: ${command.event}`); if (!handler) { - this.logger.warnMessage( + this._logger.warnMessage( `No handler registered for command type: ${command.event}` ); return; @@ -58,7 +58,7 @@ export class CentralCommandDispatcher { try { await handler.handle(command); } catch (error) { - this.logger.errorMessage( + this._logger.errorMessage( `Error handling command ${command.event}:`, error ); diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index 73f247589..3ebdb742d 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -33,7 +33,7 @@ import { NewTokenHandler } from "./Commands/newToken/newToken.handler"; * ``` */ export class gRPCWrapper { - private ConnectionManager: ConnectionManager; + private _connectionManager: ConnectionManager; /** * @description Initializes an instance of gRPCWrapper class. @@ -42,15 +42,15 @@ export class gRPCWrapper { * @param centralAddress - The address of the central gRPC server (default: "localhost:4100"). */ constructor(protoPath: string, centralAddress: string = "localhost:4100") { - this.ConnectionManager = new ConnectionManager(protoPath, centralAddress); - this.ConnectionManager.registerCommandHandlers([ + this._connectionManager = new ConnectionManager(protoPath, centralAddress); + this._connectionManager.registerCommandHandlers([ { event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, - handler: new RevokeTokenHandler(this.ConnectionManager), + handler: new RevokeTokenHandler(this._connectionManager), }, { event: DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN, - handler: new NewTokenHandler(this.ConnectionManager), + handler: new NewTokenHandler(this._connectionManager), }, ]); } @@ -59,7 +59,7 @@ export class gRPCWrapper { * @description Starts the Connection Manager stream connection with Central System */ public connectToCentralSystem() { - this.ConnectionManager.connectToCentralSystem(); + this._connectionManager.connectToCentralSystem(); } /** @@ -71,11 +71,11 @@ export class gRPCWrapper { sending: Connection[]; receiving: Connection[]; } { - return this.ConnectionManager.connections; + return this._connectionManager.connections; } public getSummary(): string { - const conn = this.ConnectionManager.connections; + const conn = this._connectionManager.connections; return ( `Wrapper Summary: ` + `\nSending Connections: ${conn.sending.length}` + From d0437171c220de0909e6611e5102ad97f2d7f204 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 6 Nov 2025 19:34:16 +0100 Subject: [PATCH 077/103] fix: rename directories to start with small letters --- Tokenization/backend/wrapper/package.json | 3 +- .../backend/wrapper/scripts/banner.js | 73 ------------------- .../newToken/newToken.command.ts | 0 .../newToken/newToken.handler.ts | 0 .../revokeToken/revokeToken.command.ts | 0 .../revokeToken/revokeToken.handler.ts | 0 .../{Connection => connection}/Connection.ts | 0 .../CentralConnection.ts | 0 .../ConnectionManager.ts | 0 .../CentralCommandDispatcher.ts | 0 .../{Commands => commands}/newToken.test.ts | 0 .../revokeToken.test.ts | 0 .../ConnectionManager.test..ts | 0 13 files changed, 1 insertion(+), 75 deletions(-) delete mode 100644 Tokenization/backend/wrapper/scripts/banner.js rename Tokenization/backend/wrapper/src/client/{Commands => commands}/newToken/newToken.command.ts (100%) rename Tokenization/backend/wrapper/src/client/{Commands => commands}/newToken/newToken.handler.ts (100%) rename Tokenization/backend/wrapper/src/client/{Commands => commands}/revokeToken/revokeToken.command.ts (100%) rename Tokenization/backend/wrapper/src/client/{Commands => commands}/revokeToken/revokeToken.handler.ts (100%) rename Tokenization/backend/wrapper/src/client/{Connection => connection}/Connection.ts (100%) rename Tokenization/backend/wrapper/src/client/{ConnectionManager => connectionManager}/CentralConnection.ts (100%) rename Tokenization/backend/wrapper/src/client/{ConnectionManager => connectionManager}/ConnectionManager.ts (100%) rename Tokenization/backend/wrapper/src/client/{ConnectionManager/EventManagement => connectionManager/eventManagement}/CentralCommandDispatcher.ts (100%) rename Tokenization/backend/wrapper/src/test/client/{Commands => commands}/newToken.test.ts (100%) rename Tokenization/backend/wrapper/src/test/client/{Commands => commands}/revokeToken.test.ts (100%) rename Tokenization/backend/wrapper/src/test/client/{ConnectionManager => connectionManager}/ConnectionManager.test..ts (100%) diff --git a/Tokenization/backend/wrapper/package.json b/Tokenization/backend/wrapper/package.json index 0c420b84c..7f472886a 100644 --- a/Tokenization/backend/wrapper/package.json +++ b/Tokenization/backend/wrapper/package.json @@ -6,8 +6,7 @@ "test": "jest", "build": "tsc -p tsconfig.build.json && cp -r src/proto dist", "client": "node dist/client/gRPCWrapper.js", - "central": "node dist/central/CentralSystem.js", - "process-banners": "node scripts/banner.js" + "central": "node dist/central/CentralSystem.js" }, "author": "ALICEO2", "devDependencies": { diff --git a/Tokenization/backend/wrapper/scripts/banner.js b/Tokenization/backend/wrapper/scripts/banner.js deleted file mode 100644 index a8a9be9e2..000000000 --- a/Tokenization/backend/wrapper/scripts/banner.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -const fs = require("fs"); -const path = require("path"); - -const banner = `/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ -`; - -const processFile = (filePath) => { - try { - const content = fs.readFileSync(filePath, "utf8"); - - if ( - content.includes(`@license`) && - content.includes( - `Copyright 2019-2020 CERN and copyright holders of ALICE O2.` - ) - ) { - return; - } - - const newContent = banner + "\n" + content; - fs.writeFileSync(filePath, newContent, "utf8"); - console.log(`Added banner to: ${filePath}`); - } catch (err) { - console.error(`Error with file ${filePath}:`, err); - } -}; - -const excludedDirs = ["node_modules", "dist"]; -const walkDir = (dir) => { - const files = fs.readdirSync(dir, { withFileTypes: true }); - - for (const file of files) { - const fullPath = path.join(dir, file.name); - - if (file.isDirectory() && !excludedDirs.includes(fullPath)) { - walkDir(fullPath); - } else if (file.isFile()) { - if (/\.(js|ts|jsx|tsx|mjs|cjs|proto)$/.test(file.name)) { - processFile(fullPath); - } - } - } -}; - -const startDir = "./src/"; -walkDir(startDir); -console.log("Banners processed."); diff --git a/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.command.ts b/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.command.ts similarity index 100% rename from Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.command.ts rename to Tokenization/backend/wrapper/src/client/commands/newToken/newToken.command.ts diff --git a/Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts b/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts similarity index 100% rename from Tokenization/backend/wrapper/src/client/Commands/newToken/newToken.handler.ts rename to Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts diff --git a/Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.command.ts b/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.command.ts similarity index 100% rename from Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.command.ts rename to Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.command.ts diff --git a/Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.handler.ts b/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts similarity index 100% rename from Tokenization/backend/wrapper/src/client/Commands/revokeToken/revokeToken.handler.ts rename to Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts diff --git a/Tokenization/backend/wrapper/src/client/Connection/Connection.ts b/Tokenization/backend/wrapper/src/client/connection/Connection.ts similarity index 100% rename from Tokenization/backend/wrapper/src/client/Connection/Connection.ts rename to Tokenization/backend/wrapper/src/client/connection/Connection.ts diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts b/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts similarity index 100% rename from Tokenization/backend/wrapper/src/client/ConnectionManager/CentralConnection.ts rename to Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts similarity index 100% rename from Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts rename to Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts b/Tokenization/backend/wrapper/src/client/connectionManager/eventManagement/CentralCommandDispatcher.ts similarity index 100% rename from Tokenization/backend/wrapper/src/client/ConnectionManager/EventManagement/CentralCommandDispatcher.ts rename to Tokenization/backend/wrapper/src/client/connectionManager/eventManagement/CentralCommandDispatcher.ts diff --git a/Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts b/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts similarity index 100% rename from Tokenization/backend/wrapper/src/test/client/Commands/newToken.test.ts rename to Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts diff --git a/Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts b/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts similarity index 100% rename from Tokenization/backend/wrapper/src/test/client/Commands/revokeToken.test.ts rename to Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts diff --git a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test..ts b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts similarity index 100% rename from Tokenization/backend/wrapper/src/test/client/ConnectionManager/ConnectionManager.test..ts rename to Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts From 48c51dc3d2aec074b7c99817960e943c49fecfb2 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 6 Nov 2025 19:39:52 +0100 Subject: [PATCH 078/103] fix: fix imports and add breaklines after banners --- .../backend/wrapper/src/central/CentralSystemWrapper.ts | 1 + .../src/client/commands/newToken/newToken.handler.ts | 2 +- .../client/commands/revokeToken/revokeToken.handler.ts | 2 +- .../src/client/connectionManager/CentralConnection.ts | 2 +- .../src/client/connectionManager/ConnectionManager.ts | 4 ++-- Tokenization/backend/wrapper/src/client/gRPCWrapper.ts | 8 ++++---- .../wrapper/src/test/client/commands/newToken.test.ts | 8 ++++---- .../wrapper/src/test/client/commands/revokeToken.test.ts | 8 ++++---- .../client/connectionManager/ConnectionManager.test..ts | 2 +- 9 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts b/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts index dd20fbcaa..1b1761495 100644 --- a/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts +++ b/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts @@ -11,6 +11,7 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ + import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; import { LogManager } from "@aliceo2/web-ui"; diff --git a/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts b/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts index a7c0fcd7f..50ded2144 100644 --- a/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts +++ b/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts @@ -14,7 +14,7 @@ import { CommandHandler } from "../../../models/commands.model"; import { NewTokenCommand } from "./newToken.command"; -import { ConnectionManager } from "../../ConnectionManager/ConnectionManager"; +import { ConnectionManager } from "../../connectionManager/ConnectionManager"; import { ConnectionDirection } from "../../../models/message.model"; /** diff --git a/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts b/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts index af923fc36..24954961d 100644 --- a/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts +++ b/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts @@ -14,7 +14,7 @@ import { CommandHandler } from "../../../models/commands.model"; import { RevokeTokenCommand } from "./revokeToken.command"; -import { ConnectionManager } from "../../ConnectionManager/ConnectionManager"; +import { ConnectionManager } from "../../connectionManager/ConnectionManager"; /** * RevokeTokenHandler is responsible for handling the RevokeTokenCommand. diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts b/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts index fec725dca..aff176da9 100644 --- a/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts +++ b/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts @@ -14,7 +14,7 @@ import * as grpc from "@grpc/grpc-js"; import { LogManager } from "@aliceo2/web-ui"; -import { CentralCommandDispatcher } from "./EventManagement/CentralCommandDispatcher"; +import { CentralCommandDispatcher } from "./eventManagement/CentralCommandDispatcher"; import { DuplexMessageModel } from "../../models/message.model"; /** diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts index f211e1aed..9b073690b 100644 --- a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts @@ -15,8 +15,8 @@ import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; import { CentralConnection } from "./CentralConnection"; -import { CentralCommandDispatcher } from "./EventManagement/CentralCommandDispatcher"; -import { Connection } from "../Connection/Connection"; +import { CentralCommandDispatcher } from "./eventManagement/CentralCommandDispatcher"; +import { Connection } from "../connection/Connection"; import { LogManager } from "@aliceo2/web-ui"; import { Command, CommandHandler } from "models/commands.model"; import { diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index 3ebdb742d..d7feee7e0 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -12,11 +12,11 @@ * or submit itself to any jurisdiction. */ -import { ConnectionManager } from "./ConnectionManager/ConnectionManager"; -import { RevokeTokenHandler } from "./Commands/revokeToken/revokeToken.handler"; +import { ConnectionManager } from "./connectionManager/ConnectionManager"; +import { RevokeTokenHandler } from "./commands/revokeToken/revokeToken.handler"; import { DuplexMessageEvent } from "../models/message.model"; -import { Connection } from "./Connection/Connection"; -import { NewTokenHandler } from "./Commands/newToken/newToken.handler"; +import { Connection } from "./connection/Connection"; +import { NewTokenHandler } from "./commands/newToken/newToken.handler"; /** * @description Wrapper class for managing secure gRPC wrapper. diff --git a/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts b/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts index be51dbe44..cfdf493cd 100644 --- a/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts @@ -12,10 +12,10 @@ * or submit itself to any jurisdiction. */ -import { NewTokenCommand } from "../../../client/Commands/newToken/newToken.command"; -import { NewTokenHandler } from "../../../client/Commands/newToken/newToken.handler"; -import { Connection } from "../../../client/Connection/Connection"; -import { ConnectionManager } from "../../../client/ConnectionManager/ConnectionManager"; +import { NewTokenCommand } from "../../../client/commands/newToken/newToken.command"; +import { NewTokenHandler } from "../../../client/commands/newToken/newToken.handler"; +import { Connection } from "../../../client/connection/Connection"; +import { ConnectionManager } from "../../../client/connectionManager/ConnectionManager"; import { Command } from "models/commands.model"; import { ConnectionDirection, diff --git a/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts b/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts index 5c07c0d53..157090985 100644 --- a/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts @@ -12,10 +12,10 @@ * or submit itself to any jurisdiction. */ -import { RevokeTokenCommand } from "../../../client/Commands/revokeToken/revokeToken.command"; -import { RevokeTokenHandler } from "../../../client/Commands/revokeToken/revokeToken.handler"; -import { Connection } from "../../../client/Connection/Connection"; -import { ConnectionManager } from "../../../client/ConnectionManager/ConnectionManager"; +import { RevokeTokenCommand } from "../../../client/commands/revokeToken/revokeToken.command"; +import { RevokeTokenHandler } from "../../../client/commands/revokeToken/revokeToken.handler"; +import { Connection } from "../../../client/connection/Connection"; +import { ConnectionManager } from "../../../client/connectionManager/ConnectionManager"; import { ConnectionDirection, DuplexMessageEvent, diff --git a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts index f60739154..2ffb68dc6 100644 --- a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts +++ b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts @@ -13,7 +13,7 @@ */ import * as grpc from "@grpc/grpc-js"; -import { ConnectionManager } from "../../../client/ConnectionManager/ConnectionManager"; +import { ConnectionManager } from "../../../client/connectionManager/ConnectionManager"; import { DuplexMessageEvent } from "../../../models/message.model"; // Mock duplex stream From 9ae5c9bd59370d3b8488b780ed4603e22f232c47 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 6 Nov 2025 19:44:36 +0100 Subject: [PATCH 079/103] feat: inform about central system address on connection --- .../src/client/connectionManager/CentralConnection.ts | 7 +++++-- .../src/client/connectionManager/ConnectionManager.ts | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts b/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts index aff176da9..833dc0d78 100644 --- a/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts +++ b/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts @@ -27,7 +27,8 @@ export class CentralConnection { constructor( private _client: any, - private _dispatcher: CentralCommandDispatcher + private _dispatcher: CentralCommandDispatcher, + public centralAddress: string ) {} /** @@ -75,7 +76,9 @@ export class CentralConnection { */ start() { this.connect(); - this._logger.infoMessage(`Connected to CentralSystem`); + this._logger.infoMessage( + `Connected to CentralSystem on ${this.centralAddress}` + ); } /** diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts index 9b073690b..cb11230e1 100644 --- a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts @@ -77,7 +77,8 @@ export class ConnectionManager { this._centralDispatcher = new CentralCommandDispatcher(); this._centralConnection = new CentralConnection( client, - this._centralDispatcher + this._centralDispatcher, + centralAddress ); } From 75a9f14de063c0a5884c04900e788075b2e1beef Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 6 Nov 2025 19:50:03 +0100 Subject: [PATCH 080/103] fix: tests testing initializtion of object shouuld have object creation inside of them --- .../wrapper/src/test/central/CentralSystemWrapper.test.ts | 3 ++- .../client/connectionManager/ConnectionManager.test..ts | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Tokenization/backend/wrapper/src/test/central/CentralSystemWrapper.test.ts b/Tokenization/backend/wrapper/src/test/central/CentralSystemWrapper.test.ts index 3da27e23e..416fb2cc2 100644 --- a/Tokenization/backend/wrapper/src/test/central/CentralSystemWrapper.test.ts +++ b/Tokenization/backend/wrapper/src/test/central/CentralSystemWrapper.test.ts @@ -67,10 +67,11 @@ describe("CentralSystemWrapper", () => { }); test("should set up gRPC service and add it to the server", () => { + const testWrapper = new CentralSystemWrapper("dummy.proto", 12345); expect(grpc.Server).toHaveBeenCalled(); expect(grpc.loadPackageDefinition).toHaveBeenCalled(); expect(grpc.ServerCredentials.createInsecure).not.toHaveBeenCalled(); - expect(wrapper).toBeDefined(); + expect(testWrapper).toBeDefined(); }); test("should call listen and bind the server", () => { diff --git a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts index 2ffb68dc6..7ce6394ee 100644 --- a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts +++ b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts @@ -83,7 +83,8 @@ describe("ConnectionManager", () => { }); test("should initialize client with correct address", () => { - expect(conn).toBeDefined(); + const connManager = new ConnectionManager("dummy.proto", "localhost:12345"); + expect(connManager).toBeDefined(); expect(grpc.loadPackageDefinition).toHaveBeenCalled(); expect(CentralSystemMock).toHaveBeenCalledWith( "localhost:12345", @@ -92,7 +93,8 @@ describe("ConnectionManager", () => { }); test("connectToCentralSystem() should set up stream listeners", () => { - conn.connectToCentralSystem(); + const connManager = new ConnectionManager("dummy.proto", "localhost:12345"); + connManager.connectToCentralSystem(); expect(mockClient.ClientStream).toHaveBeenCalled(); expect(mockStream.on).toHaveBeenCalledWith("data", expect.any(Function)); From f6cb71230b2fd423f2ea70a265750ac3d4a97741 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 6 Nov 2025 19:56:58 +0100 Subject: [PATCH 081/103] feat: implement missing descriptions --- .../wrapper/src/client/connection/Connection.ts | 4 ++++ .../client/connectionManager/CentralConnection.ts | 7 +++++++ .../backend/wrapper/src/client/gRPCWrapper.ts | 6 ++++++ .../wrapper/src/models/connection.model.ts | 15 +++++++++++++++ 4 files changed, 32 insertions(+) diff --git a/Tokenization/backend/wrapper/src/client/connection/Connection.ts b/Tokenization/backend/wrapper/src/client/connection/Connection.ts index 2a6684cd6..b44984f40 100644 --- a/Tokenization/backend/wrapper/src/client/connection/Connection.ts +++ b/Tokenization/backend/wrapper/src/client/connection/Connection.ts @@ -51,6 +51,10 @@ export class Connection { this._token = token; } + /** + * Revokes the token of the connection, effectively invalidating it. + * The connection status is set to UNAUTHORIZED. + */ public handleRevokeToken(): void { this._token = ""; this._status = ConnectionStatus.UNAUTHORIZED; diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts b/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts index 833dc0d78..dbdceeed9 100644 --- a/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts +++ b/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts @@ -25,6 +25,13 @@ export class CentralConnection { private _logger = LogManager.getLogger("CentralConnection"); private _stream?: grpc.ClientDuplexStream; + /** + * @description Constructor for the CentralConnection class. + * + * @param {_client} - The gRPC client instance used to connect to the CentralSystem. + * @param {_dispatcher} - The CentralCommandDispatcher instance used to delegate incoming messages. + * @param {centralAddress} - The address of the CentralSystem gRPC service. + */ constructor( private _client: any, private _dispatcher: CentralCommandDispatcher, diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index d7feee7e0..c70084677 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -74,6 +74,12 @@ export class gRPCWrapper { return this._connectionManager.connections; } + /** + * @description Returns a summary of the connections managed by the ConnectionManager. + * The summary includes the number of sending and receiving connections, as well as the target address, direction, and status of each connection. + * + * @returns A string summary of the connections. + */ public getSummary(): string { const conn = this._connectionManager.connections; return ( diff --git a/Tokenization/backend/wrapper/src/models/connection.model.ts b/Tokenization/backend/wrapper/src/models/connection.model.ts index 8bfde9da3..402397dc3 100644 --- a/Tokenization/backend/wrapper/src/models/connection.model.ts +++ b/Tokenization/backend/wrapper/src/models/connection.model.ts @@ -12,6 +12,21 @@ * or submit itself to any jurisdiction. */ +/** + * Represents the lifecycle states of a connection between the client and server. + * + * Each value denotes a distinct stage or condition the connection can be in, + * and can be used to drive UI state, logging, and error-handling logic. + * + * Members: + * - CONNECTING: The connection is in the process of being established. + * - CONNECTED: The connection has been successfully established. + * - UNAUTHORIZED: Authentication failed or the token has expired/been revoked. + * - CLOSED: The connection has been closed. + * - ERROR: An error occurred with the connection. + * - RECONNECTING: The connection is attempting to re-establish after a disruption. + * - TOKEN_REFRESH: The connection is refreshing its authentication token. + */ export enum ConnectionStatus { // The connection is in the process of being established CONNECTING = "CONNECTING", From d0c57bfaf90e79322fe67625db41c33b10d3c942 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 6 Nov 2025 20:01:37 +0100 Subject: [PATCH 082/103] fix: make sure that proto file enums have number 0 with default, undefined choice --- Tokenization/backend/proto/wrapper.proto | 6 ++++++ Tokenization/backend/wrapper/src/models/message.model.ts | 2 ++ 2 files changed, 8 insertions(+) diff --git a/Tokenization/backend/proto/wrapper.proto b/Tokenization/backend/proto/wrapper.proto index 6688164da..cd74d8d7d 100644 --- a/Tokenization/backend/proto/wrapper.proto +++ b/Tokenization/backend/proto/wrapper.proto @@ -56,6 +56,9 @@ message Payload { // ====================================== enum MessageEvent { + // Unspecified event type + MESSAGE_EVENT_UNSPECIFIED = 0; + // Default value, represents an empty event MESSAGE_EVENT_EMPTY = 0; @@ -67,6 +70,9 @@ enum MessageEvent { } enum ConnectionDirection { + // Unspecified direction + UNSPECIFIED = 0; + // Direction from client to server SENDING = 1; diff --git a/Tokenization/backend/wrapper/src/models/message.model.ts b/Tokenization/backend/wrapper/src/models/message.model.ts index fb64a4002..3d86a937e 100644 --- a/Tokenization/backend/wrapper/src/models/message.model.ts +++ b/Tokenization/backend/wrapper/src/models/message.model.ts @@ -23,6 +23,7 @@ * @property MESSAGE_EVENT_REVOKE_TOKEN: Event for revoking an existing token. */ export enum DuplexMessageEvent { + MESSAGE_EVENT_UNSPECIFIED = "MESSAGE_EVENT_UNSPECIFIED", MESSAGE_EVENT_EMPTY = "MESSAGE_EVENT_EMPTY", MESSAGE_EVENT_NEW_TOKEN = "MESSAGE_EVENT_NEW_TOKEN", MESSAGE_EVENT_REVOKE_TOKEN = "MESSAGE_EVENT_REVOKE_TOKEN", @@ -35,6 +36,7 @@ export enum DuplexMessageEvent { * @property DUPLEX: Indicates a connection that can both send and receive messages. */ export enum ConnectionDirection { + UNSPECIFIED = "UNSPECIFIED", SENDING = "SENDING", RECEIVING = "RECEIVING", DUPLEX = "DUPLEX", From 3c82edce44910105228dbeae8fee7926d20555d3 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 6 Nov 2025 20:34:07 +0100 Subject: [PATCH 083/103] feat: implement eslint and fix its errors --- Tokenization/backend/wrapper/.prettierrc | 7 + Tokenization/backend/wrapper/eslint.config.js | 227 + .../backend/wrapper/package-lock.json | 5946 +++++------------ Tokenization/backend/wrapper/package.json | 12 +- .../src/central/CentralSystemWrapper.ts | 60 +- .../commands/newToken/newToken.command.ts | 13 +- .../commands/newToken/newToken.handler.ts | 26 +- .../revokeToken/revokeToken.command.ts | 12 +- .../revokeToken/revokeToken.handler.ts | 13 +- .../src/client/connection/Connection.ts | 22 +- .../connectionManager/CentralConnection.ts | 46 +- .../connectionManager/ConnectionManager.ts | 60 +- .../CentralCommandDispatcher.ts | 22 +- .../backend/wrapper/src/client/gRPCWrapper.ts | 28 +- .../wrapper/src/models/commands.model.ts | 2 +- .../wrapper/src/models/connection.model.ts | 16 +- .../wrapper/src/models/message.model.ts | 16 +- .../test/central/CentralSystemWrapper.test.ts | 72 +- .../src/test/client/commands/newToken.test.ts | 91 +- .../test/client/commands/revokeToken.test.ts | 85 +- .../ConnectionManager.test..ts | 70 +- .../wrapper/src/utils/types/webui.d.ts | 2 +- 22 files changed, 2312 insertions(+), 4536 deletions(-) create mode 100644 Tokenization/backend/wrapper/.prettierrc create mode 100644 Tokenization/backend/wrapper/eslint.config.js diff --git a/Tokenization/backend/wrapper/.prettierrc b/Tokenization/backend/wrapper/.prettierrc new file mode 100644 index 000000000..a3cf86361 --- /dev/null +++ b/Tokenization/backend/wrapper/.prettierrc @@ -0,0 +1,7 @@ +{ + "singleQuote": true, + "semi": true, + "tabWidth": 2, + "trailingComma": "es5", + "printWidth": 145 +} diff --git a/Tokenization/backend/wrapper/eslint.config.js b/Tokenization/backend/wrapper/eslint.config.js new file mode 100644 index 000000000..71286bc1c --- /dev/null +++ b/Tokenization/backend/wrapper/eslint.config.js @@ -0,0 +1,227 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +const globals = require('globals'); +const pluginJs = require('@eslint/js'); +const tseslint = require('typescript-eslint'); +const jsdoc = require('eslint-plugin-jsdoc'); +const stylisticTs = require('@stylistic/eslint-plugin-ts'); +const stylisticJs = require('@stylistic/eslint-plugin-js'); +const eslintConfigPrettier = require('eslint-config-prettier'); + +const licenseHeader = `/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */`; + +const licenseHeaderRule = { + meta: { + type: 'layout', + docs: { + description: 'Require license header at the top of files', + category: 'Stylistic Issues', + }, + fixable: 'code', + schema: [], + }, + create(context) { + return { + Program(node) { + const sourceCode = context.getSourceCode(); + const text = sourceCode.getText(); + + if (text.includes('@license')) { + return; + } + + context.report({ + node, + message: 'Missing license header', + fix(fixer) { + return fixer.insertTextBefore(node, licenseHeader + '\n\n'); + }, + }); + }, + }; + }, +}; + +module.exports = [ + { + ignores: [ + '**/test/', + '**/tests/', + '**/node_modules/', + '**/build/', + '**/dist/', + '**/database/data/', + '**/lib/public/assets/', + '**/cpp-api-client/', + '**/tmp/', + '**/.nyc_output/', + '**/jest.config.ts', + '**/package*.json', + '**/tsconfig*.json', + '**/eslint.config.js', + '**/*d.ts', + ], + }, + + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + + { + files: ['**/*.{js,ts}'], + plugins: { + '@typescript-eslint': tseslint.plugin, + jsdoc: jsdoc, + '@stylistic/ts': stylisticTs, + '@stylistic/js': stylisticJs, + custom: { + rules: { + 'license-header': licenseHeaderRule, + }, + }, + }, + + languageOptions: { + parser: tseslint.parser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'commonjs', + project: ['./tsconfig.json'], + }, + globals: { + ...globals.node, + ...globals.es2021, + ...globals.commonjs, + }, + }, + + settings: { + jsdoc: { + mode: 'typescript', + tagNamePreference: { + returns: 'return', + }, + }, + }, + + rules: { + // === CUSTOM RULES === + 'custom/license-header': 'error', + + // === TYPESCRIPT SPECIFIC RULES === + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + ignoreRestSiblings: true, + }, + ], + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/prefer-nullish-coalescing': 'error', + '@typescript-eslint/prefer-optional-chain': 'error', + '@typescript-eslint/no-non-null-assertion': 'warn', + '@typescript-eslint/consistent-type-imports': 'error', + + // === GENERAL CODE QUALITY === + 'arrow-body-style': ['error', 'as-needed'], + curly: 'error', + 'no-console': 'error', + 'no-implicit-coercion': 'error', + 'no-return-assign': 'error', + 'no-var': 'error', + 'one-var': ['error', 'never'], + 'prefer-const': 'error', + 'prefer-destructuring': 'warn', + 'prefer-template': 'error', + radix: 'error', + + // === COMMENTS AND DOCUMENTATION === + 'capitalized-comments': ['error', 'always'], + 'jsdoc/require-description': 'error', + 'jsdoc/require-returns': 'off', + 'jsdoc/require-jsdoc': [ + 'error', + { + require: { + FunctionDeclaration: true, + MethodDefinition: true, + ClassDeclaration: true, + ArrowFunctionExpression: false, + FunctionExpression: true, + }, + }, + ], + + // === TYPESCRIPT STYLISTIC RULES === + '@stylistic/ts/comma-dangle': ['error', 'always-multiline'], + '@stylistic/ts/comma-spacing': ['error', { before: false, after: true }], + '@stylistic/ts/indent': ['error', 2], + '@stylistic/ts/quotes': ['error', 'single', { avoidEscape: true }], + '@stylistic/ts/semi': 'error', + '@stylistic/ts/space-before-blocks': 'error', + '@stylistic/ts/space-infix-ops': 'error', + '@stylistic/ts/object-curly-spacing': ['error', 'always'], + '@stylistic/ts/keyword-spacing': 'error', + '@stylistic/ts/type-annotation-spacing': 'error', + '@stylistic/ts/member-delimiter-style': 'error', + + // === JAVASCRIPT STYLISTIC RULES === + '@stylistic/js/array-bracket-spacing': ['error', 'never'], + '@stylistic/js/brace-style': ['error', '1tbs'], + '@stylistic/js/no-trailing-spaces': 'error', + '@stylistic/js/eol-last': ['error', 'always'], + '@stylistic/js/max-len': ['error', { code: 145 }], + '@stylistic/js/no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }], + + // === DISABLED RULES === + 'no-magic-numbers': 'off', + 'sort-keys': 'off', + 'sort-imports': 'off', + 'sort-vars': 'off', + }, + }, + { + files: ['**/*.test.{js,ts}', '**/test/**/*.{js,ts}'], + languageOptions: { + globals: { + ...globals.jest, + + describe: 'readonly', + it: 'readonly', + expect: 'readonly', + beforeEach: 'readonly', + afterEach: 'readonly', + }, + }, + + rules: { + 'no-console': 'off', + 'jsdoc/require-jsdoc': 'off', + }, + }, + eslintConfigPrettier, +]; diff --git a/Tokenization/backend/wrapper/package-lock.json b/Tokenization/backend/wrapper/package-lock.json index ef28121ab..c0ecc6551 100644 --- a/Tokenization/backend/wrapper/package-lock.json +++ b/Tokenization/backend/wrapper/package-lock.json @@ -7,15 +7,23 @@ "": { "name": "grpc-wrapper", "version": "1.0.0", - "license": "ISC", "dependencies": { + "@grpc/grpc-js": "^1.13.4", + "@grpc/proto-loader": "^0.7.15", "ts-node": "^10.9.2", "typescript": "^5.8.3" }, "devDependencies": { + "@stylistic/eslint-plugin-js": "^4.4.1", + "@stylistic/eslint-plugin-ts": "^4.4.1", "@types/jest": "^29.5.14", + "eslint": "^9.39.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-jsdoc": "^61.1.12", "jest": "^29.7.0", - "ts-jest": "^29.3.4" + "ts-jest": "^29.4.0", + "tsc-alias": "^1.8.16", + "typescript-eslint": "^8.46.3" } }, "node_modules/@ampproject/remapping": { @@ -540,6 +548,293 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.76.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.76.0.tgz", + "integrity": "sha512-g+RihtzFgGTx2WYCuTHbdOXJeAlGnROws0TeALx9ow/ZmOROOZkVg5wp/B44n0WJgI4SQFP1eWM2iRPlU2Y14w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.8", + "@typescript-eslint/types": "^8.46.0", + "comment-parser": "1.4.1", + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~6.10.0" + }, + "engines": { + "node": ">=20.11.0" + } + }, + "node_modules/@es-joy/resolve.exports": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@es-joy/resolve.exports/-/resolve.exports-1.2.0.tgz", + "integrity": "sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz", + "integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -910,4055 +1205,52 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "license": "MIT" - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" + "engines": { + "node": ">= 8" } }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", - "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", - "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/node": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.0.tgz", - "integrity": "sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.8.0" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "license": "MIT" - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, - "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", - "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", - "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001721", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", - "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dedent": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", - "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.166", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz", - "integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/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, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "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, - "license": "MIT" - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "license": "ISC" - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-jest": { - "version": "29.3.4", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.4.tgz", - "integrity": "sha512-Iqbrm8IXOmV+ggWHOTEbjwyCf2xZlUMv5npExksXohL+tk8va4Fjhb+X2+Rt9NBmgO7bJ8WpnMLOwih/DnMlFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bs-logger": "^0.2.6", - "ejs": "^3.1.10", - "fast-json-stable-stringify": "^2.1.0", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.7.2", - "type-fest": "^4.41.0", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "license": "MIT" - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} - -{ - "name": "grpc-wrapper", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "grpc-wrapper", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@grpc/grpc-js": "^1.13.4", - "@grpc/proto-loader": "^0.7.15", - "ts-node": "^10.9.2", - "typescript": "^5.8.3" - }, - "devDependencies": { - "@types/jest": "^29.5.14", - "jest": "^29.7.0", - "ts-jest": "^29.3.4" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", - "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", - "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@grpc/grpc-js": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz", - "integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/proto-loader": "^0.7.13", - "@js-sdsl/ordered-map": "^4.4.2" - }, - "engines": { - "node": ">=12.10.0" - } - }, - "node_modules/@grpc/proto-loader": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", - "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", - "license": "Apache-2.0", - "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.2.5", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@js-sdsl/ordered-map": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" } }, "node_modules/@protobufjs/aspromise": { @@ -5032,6 +1324,19 @@ "dev": true, "license": "MIT" }, + "node_modules/@sindresorhus/base62": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/base62/-/base62-1.0.0.tgz", + "integrity": "sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -5052,6 +1357,41 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@stylistic/eslint-plugin-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-4.4.1.tgz", + "integrity": "sha512-eLisyHvx7Sel8vcFZOEwDEBGmYsYM1SqDn81BWgmbqEXfXRf8oe6Rwp+ryM/8odNjlxtaaxp0Ihmt86CnLAxKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0" + } + }, + "node_modules/@stylistic/eslint-plugin-ts": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-ts/-/eslint-plugin-ts-4.4.1.tgz", + "integrity": "sha512-2r6cLcmdF6til66lx8esBYvBvsn7xCmLT50gw/n1rGGlTq/OxeNjBIh4c3VEaDGMa/5TybrZTia6sQUHdIWx1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.32.1", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -5121,6 +1461,13 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -5169,6 +1516,13 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.0.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.0.tgz", @@ -5178,29 +1532,300 @@ "undici-types": "~7.8.0" } }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.3.tgz", + "integrity": "sha512-sbaQ27XBUopBkRiuY/P9sWGOWUW4rl8fDoHIUmLpZd8uldsTyB4/Zg6bWTegPoTLnKj9Hqgn3QD6cjPNB32Odw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/type-utils": "8.46.3", + "@typescript-eslint/utils": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.46.3", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.3.tgz", + "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.3.tgz", + "integrity": "sha512-Fz8yFXsp2wDFeUElO88S9n4w1I4CWDTXDqDr9gYvZgUpwXQqmZBr9+NTTql5R3J7+hrJZPdpiWaB9VNhAKYLuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.46.3", + "@typescript-eslint/types": "^8.46.3", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.3.tgz", + "integrity": "sha512-FCi7Y1zgrmxp3DfWfr+3m9ansUUFoy8dkEdeQSgA9gbm8DaHYvZCdkFRQrtKiedFf3Ha6VmoqoAaP68+i+22kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.3.tgz", + "integrity": "sha512-GLupljMniHNIROP0zE7nCcybptolcH8QZfXOpCfhQDAdwJ/ZTlcaBOYebSOZotpti/3HrHSw7D3PZm75gYFsOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.3.tgz", + "integrity": "sha512-ZPCADbr+qfz3aiTTYNNkCbUt+cjNwI/5McyANNrFBpVxPt7GqpEYz5ZfdwuFyGUnJ9FdDXbGODUu6iRCI6XRXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3", + "@typescript-eslint/utils": "8.46.3", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.3.tgz", + "integrity": "sha512-G7Ok9WN/ggW7e/tOf8TQYMaxgID3Iujn231hfi0Pc7ZheztIJVpO44ekY00b7akqc6nZcvregk0Jpah3kep6hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.3.tgz", + "integrity": "sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.46.3", + "@typescript-eslint/tsconfig-utils": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, - "license": "MIT" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, - "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "node_modules/@typescript-eslint/utils": { + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.3.tgz", + "integrity": "sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g==", "dev": true, "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.3.tgz", + "integrity": "sha512-uk574k8IU0rOF/AjniX8qbLSGURJVUCeM5e4MIMKBFFi8weeiLrG1fyQejyLXQpRZbU/1BuQasleV/RfHC3hHg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.3", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } }, "node_modules/acorn": { "version": "8.15.0", @@ -5214,6 +1839,16 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/acorn-walk": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", @@ -5226,6 +1861,23 @@ "node": ">=0.4.0" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -5280,6 +1932,16 @@ "node": ">= 8" } }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -5296,6 +1958,16 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -5426,6 +2098,19 @@ "dev": true, "license": "MIT" }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5581,6 +2266,31 @@ "node": ">=10" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -5654,6 +2364,26 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5712,9 +2442,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -5744,104 +2474,365 @@ } } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.166", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz", + "integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc": { + "version": "61.1.12", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-61.1.12.tgz", + "integrity": "sha512-CGJTnltz7ovwOW33xYhvA4fMuriPZpR5OnJf09SV28iU2IUpJwMd6P7zvUK8Sl56u5YzO+1F9m46wpSs2dufEw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@es-joy/jsdoccomment": "~0.76.0", + "@es-joy/resolve.exports": "1.2.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.4.3", + "escape-string-regexp": "^4.0.0", + "espree": "^10.4.0", + "esquery": "^1.6.0", + "html-entities": "^2.6.0", + "object-deep-merge": "^2.0.0", + "parse-imports-exports": "^0.2.4", + "semver": "^7.7.3", + "spdx-expression-parse": "^4.0.0", + "to-valid-identifier": "^1.0.0" + }, + "engines": { + "node": ">=20.11.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, - "license": "MIT", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "license": "BSD-3-Clause", + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=0.3.1" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "license": "Apache-2.0", + "license": "ISC", "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=0.10.0" + "node": ">=10.13.0" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.166", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz", - "integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { - "is-arrayish": "^0.2.1" + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -5858,6 +2849,52 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "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", @@ -5908,6 +2945,30 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -5915,6 +2976,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -5925,6 +3003,19 @@ "bser": "2.1.1" } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -5985,6 +3076,27 @@ "node": ">=8" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5992,21 +3104,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -6059,6 +3156,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -6081,6 +3191,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -6091,6 +3214,27 @@ "node": ">=4" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -6098,6 +3242,13 @@ "dev": true, "license": "ISC" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6121,6 +3272,23 @@ "node": ">= 0.4" } }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -6138,6 +3306,43 @@ "node": ">=10.17.0" } }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -6194,6 +3399,19 @@ "dev": true, "license": "MIT" }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -6210,6 +3428,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -6229,6 +3457,19 @@ "node": ">=6" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -6979,6 +4220,16 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-6.10.0.tgz", + "integrity": "sha512-+LexoTRyYui5iOhJGn13N9ZazL23nAHGkXsa1p/C8yeq79WRfLBag6ZZ0FQG2aRoc9yfo59JT9EYCQonOkHKkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -6992,6 +4243,13 @@ "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -6999,6 +4257,20 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -7012,6 +4284,16 @@ "node": ">=6" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -7032,6 +4314,20 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -7065,6 +4361,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", @@ -7133,6 +4436,16 @@ "dev": true, "license": "MIT" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -7177,6 +4490,20 @@ "dev": true, "license": "MIT" }, + "node_modules/mylas": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.13.tgz", + "integrity": "sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/raouldeheer" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -7221,6 +4548,13 @@ "node": ">=8" } }, + "node_modules/object-deep-merge": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/object-deep-merge/-/object-deep-merge-2.0.0.tgz", + "integrity": "sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==", + "dev": true, + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -7247,6 +4581,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -7302,6 +4654,29 @@ "node": ">=6" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-imports-exports": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", + "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-statements": "1.0.11" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -7321,6 +4696,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-statements": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", + "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", + "dev": true, + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7358,6 +4740,16 @@ "dev": true, "license": "MIT" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -7401,6 +4793,29 @@ "node": ">=8" } }, + "node_modules/plimit-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "queue-lit": "^1.5.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -7467,6 +4882,16 @@ "node": ">=12.0.0" } }, + "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", @@ -7484,6 +4909,37 @@ ], "license": "MIT" }, + "node_modules/queue-lit": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -7491,6 +4947,19 @@ "dev": true, "license": "MIT" }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7500,6 +4969,19 @@ "node": ">=0.10.0" } }, + "node_modules/reserved-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", + "integrity": "sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -7544,6 +5026,16 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve.exports": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", @@ -7554,6 +5046,41 @@ "node": ">=10" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -7632,6 +5159,31 @@ "source-map": "^0.6.0" } }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -7796,17 +5348,46 @@ "node": ">=8.0" } }, + "node_modules/to-valid-identifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-valid-identifier/-/to-valid-identifier-1.0.0.tgz", + "integrity": "sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/base62": "^1.0.0", + "reserved-identifiers": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-jest": { - "version": "29.3.4", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.4.tgz", - "integrity": "sha512-Iqbrm8IXOmV+ggWHOTEbjwyCf2xZlUMv5npExksXohL+tk8va4Fjhb+X2+Rt9NBmgO7bJ8WpnMLOwih/DnMlFA==", + "version": "29.4.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.0.tgz", + "integrity": "sha512-d423TJMnJGu80/eSgfQ5w/R+0zFJvdtTxwtF9KzFFunOpSeD+79lHJQIiAhluJoyGRbvj9NZJsl9WjCUo0ND7Q==", "dev": true, "license": "MIT", "dependencies": { "bs-logger": "^0.2.6", "ejs": "^3.1.10", "fast-json-stable-stringify": "^2.1.0", - "jest-util": "^29.0.0", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", @@ -7822,10 +5403,11 @@ }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", "typescript": ">=4.3 <6" }, "peerDependenciesMeta": { @@ -7843,6 +5425,9 @@ }, "esbuild": { "optional": true + }, + "jest-util": { + "optional": true } } }, @@ -7915,6 +5500,41 @@ } } }, + "node_modules/tsc-alias": { + "version": "1.8.16", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz", + "integrity": "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "get-tsconfig": "^4.10.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + }, + "bin": { + "tsc-alias": "dist/bin/index.js" + }, + "engines": { + "node": ">=16.20.2" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -7951,6 +5571,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.3.tgz", + "integrity": "sha512-bAfgMavTuGo+8n6/QQDVQz4tZ4f7Soqg53RbrlZQEoAltYop/XR4RAts/I0BrO3TTClTSTFJ0wYbla+P8cEWJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.46.3", + "@typescript-eslint/parser": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3", + "@typescript-eslint/utils": "8.46.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, "node_modules/undici-types": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", @@ -7988,6 +5632,16 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -8035,6 +5689,16 @@ "node": ">= 8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/Tokenization/backend/wrapper/package.json b/Tokenization/backend/wrapper/package.json index 7f472886a..000944e17 100644 --- a/Tokenization/backend/wrapper/package.json +++ b/Tokenization/backend/wrapper/package.json @@ -6,14 +6,22 @@ "test": "jest", "build": "tsc -p tsconfig.build.json && cp -r src/proto dist", "client": "node dist/client/gRPCWrapper.js", - "central": "node dist/central/CentralSystem.js" + "central": "node dist/central/CentralSystem.js", + "lint": "eslint . --ext .ts", + "lint:fix": "eslint . --ext .ts --fix" }, "author": "ALICEO2", "devDependencies": { + "@stylistic/eslint-plugin-js": "^4.4.1", + "@stylistic/eslint-plugin-ts": "^4.4.1", "@types/jest": "^29.5.14", + "eslint": "^9.39.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-jsdoc": "^61.1.12", "jest": "^29.7.0", "ts-jest": "^29.4.0", - "tsc-alias": "^1.8.16" + "tsc-alias": "^1.8.16", + "typescript-eslint": "^8.46.3" }, "dependencies": { "@grpc/grpc-js": "^1.13.4", diff --git a/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts b/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts index 1b1761495..20f5e2457 100644 --- a/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts +++ b/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts @@ -12,25 +12,25 @@ * or submit itself to any jurisdiction. */ -import * as grpc from "@grpc/grpc-js"; -import * as protoLoader from "@grpc/proto-loader"; -import { LogManager } from "@aliceo2/web-ui"; -import { DuplexMessageModel } from "../models/message.model"; +import * as grpc from '@grpc/grpc-js'; +import * as protoLoader from '@grpc/proto-loader'; +import { LogManager } from '@aliceo2/web-ui'; +import type { DuplexMessageModel } from '../models/message.model'; /** * @description Central System gRPC wrapper that manages client connections and handles gRPC streams with them. */ export class CentralSystemWrapper { - // config + // Config private _protoPath: string; - // utilities - private _logger = LogManager.getLogger("CentralSystemWrapper"); + // Utilities + private _logger = LogManager.getLogger('CentralSystemWrapper'); - // class properties + // Class properties private _server: grpc.Server; - // clients management + // Clients management private _clients = new Map>(); private _clientIps = new Map(); // Peer -> IP map @@ -45,7 +45,7 @@ export class CentralSystemWrapper { } /** - * @description Loads the gRPC proto definition and sets up the CentralSystem service. + * Loads the gRPC proto definition and sets up the CentralSystem service. */ private setupService(): void { // Load the proto definition with options @@ -68,7 +68,7 @@ export class CentralSystemWrapper { } /** - * @description Extracts IP address from peer string + * Extracts IP address from peer string * @param peer string e.g. ipv4:127.0.0.1:12345 * @returns Extracted IP address */ @@ -83,47 +83,45 @@ export class CentralSystemWrapper { const ipv6Match = peer.match(/^ipv6:\[(.+?)\]:\d+$/); if (ipv6Match) return ipv6Match[1]; - // fallback to original peer if pattern doesn't match any + // Fallback to original peer if pattern doesn't match any return peer; } /** - * @description Handles the duplex stream from the client. + * Handles the duplex stream from the client. * @param call The duplex stream call object. */ private clientStreamHandler(call: grpc.ServerDuplexStream): void { const peer = call.getPeer(); const clientIp = this.extractIpFromPeer(peer); - this._logger.infoMessage( - `Client ${clientIp} (${peer}) connected to CentralSystem stream` - ); + this._logger.infoMessage(`Client ${clientIp} (${peer}) connected to CentralSystem stream`); // Add client to maps this._clients.set(clientIp, call); this._clientIps.set(peer, clientIp); // Listen for data events from the client - call.on("data", (payload: any) => { + call.on('data', (payload: any) => { this._logger.infoMessage(`Received from ${clientIp}:`, payload); }); // Handle stream end event - call.on("end", () => { + call.on('end', () => { this._logger.infoMessage(`Client ${clientIp} ended stream.`); this.cleanupClient(peer); call.end(); }); // Handle stream error event - call.on("error", (err) => { + call.on('error', (err) => { this._logger.infoMessage(`Stream error from client ${clientIp}:`, err); this.cleanupClient(peer); }); } /** - * @description Cleans up client resources + * Cleans up client resources * @param peer Original peer string */ private cleanupClient(peer: string): void { @@ -136,7 +134,7 @@ export class CentralSystemWrapper { } /** - * @description Sends data to a specific client by IP address + * Sends data to a specific client by IP address * @param ip Client IP address * @param data Data to send * @returns Whether the data was successfully sent @@ -159,7 +157,7 @@ export class CentralSystemWrapper { } /** - * @description Gets all connected client IPs + * Gets all connected client IPs * @returns Array of connected client IPs */ public get connectedClients(): string[] { @@ -167,20 +165,16 @@ export class CentralSystemWrapper { } /** - * @desciprion Starts the gRPC server and binds it to the specified in class port. + * Starts the gRPC server and binds it to the specified in class port. */ public listen() { const addr = `localhost:${this.port}`; - this._server.bindAsync( - addr, - grpc.ServerCredentials.createInsecure(), - (err, _port) => { - if (err) { - this._logger.infoMessage("Server bind error:", err); - return; - } - this._logger.infoMessage(`CentralSytem started listening on ${addr}`); + this._server.bindAsync(addr, grpc.ServerCredentials.createInsecure(), (err, _port) => { + if (err) { + this._logger.infoMessage('Server bind error:', err); + return; } - ); + this._logger.infoMessage(`CentralSytem started listening on ${addr}`); + }); } } diff --git a/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.command.ts b/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.command.ts index df8889d0d..c5f3a97eb 100644 --- a/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.command.ts +++ b/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.command.ts @@ -12,16 +12,19 @@ * or submit itself to any jurisdiction. */ -import { Command } from "../../../models/commands.model"; -import { - DuplexMessageEvent, - TokenMessage, -} from "../../../models/message.model"; +import type { Command } from '../../../models/commands.model'; +import type { TokenMessage } from '../../../models/message.model'; +import { DuplexMessageEvent } from '../../../models/message.model'; /** * @description Command used to trigger new token for a specific connection. Handles structure logic. */ export class NewTokenCommand implements Command { readonly event = DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN; + + /** + * Constructor for NewTokenCommand. + * @param {TokenMessage} payload - TokenMessage containing the new token. + */ constructor(public payload: TokenMessage) {} } diff --git a/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts b/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts index 50ded2144..ccfd5fc63 100644 --- a/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts +++ b/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts @@ -12,22 +12,24 @@ * or submit itself to any jurisdiction. */ -import { CommandHandler } from "../../../models/commands.model"; -import { NewTokenCommand } from "./newToken.command"; -import { ConnectionManager } from "../../connectionManager/ConnectionManager"; -import { ConnectionDirection } from "../../../models/message.model"; +import type { CommandHandler } from '../../../models/commands.model'; +import type { NewTokenCommand } from './newToken.command'; +import type { ConnectionManager } from '../../connectionManager/ConnectionManager'; +import { ConnectionDirection } from '../../../models/message.model'; /** * @description Handles the NewTokenCommand by updating or creating a connection with a new authentication token. */ export class NewTokenHandler implements CommandHandler { /** - * @param manager - Instance of ConnectionManager used to access and manage connections. + * Constructor for NewTokenHandler. + * + * @param manager - The ConnectionManager instance to manage connections. */ constructor(private manager: ConnectionManager) {} /** - * @description Processes the NewTokenCommand by assigning a new token to the specified connection. + * Processes the NewTokenCommand by assigning a new token to the specified connection. * If the connection does not exist, it is created. * * @param command - The new token event command. @@ -36,21 +38,15 @@ export class NewTokenHandler implements CommandHandler { async handle(command: NewTokenCommand): Promise { const { targetAddress, connectionDirection, token } = command.payload || {}; if (!targetAddress || !token || !connectionDirection) { - throw new Error( - "Insufficient arguments. Expected: targetAddress, connectionDirection, token." - ); + throw new Error('Insufficient arguments. Expected: targetAddress, connectionDirection, token.'); } const directions = - connectionDirection === ConnectionDirection.DUPLEX - ? [ConnectionDirection.SENDING, ConnectionDirection.RECEIVING] - : [connectionDirection]; + connectionDirection === ConnectionDirection.DUPLEX ? [ConnectionDirection.SENDING, ConnectionDirection.RECEIVING] : [connectionDirection]; for (const dir of directions) { let conn = this.manager.getConnectionByAddress(targetAddress, dir); - if (!conn) { - conn = this.manager.createNewConnection(targetAddress, dir, token); - } + conn ??= this.manager.createNewConnection(targetAddress, dir, token); conn.token = token; } } diff --git a/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.command.ts b/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.command.ts index 656bd6496..071a5fcfb 100644 --- a/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.command.ts +++ b/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.command.ts @@ -12,16 +12,18 @@ * or submit itself to any jurisdiction. */ -import { Command } from "../../../models/commands.model"; -import { - DuplexMessageEvent, - TokenMessage, -} from "../../../models/message.model"; +import type { Command } from '../../../models/commands.model'; +import type { TokenMessage } from '../../../models/message.model'; +import { DuplexMessageEvent } from '../../../models/message.model'; /** * @description Command used to trigger token revocation for a specific connection. Handles structure logic. */ export class RevokeTokenCommand implements Command { readonly event = DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN; + /** + * Constructor for RevokeTokenCommand. + * @param {TokenMessage} payload - TokenMessage containing the address and direction of the connection to be revoked. + */ constructor(public payload: TokenMessage) {} } diff --git a/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts b/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts index 24954961d..2fa1a9640 100644 --- a/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts +++ b/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts @@ -12,9 +12,9 @@ * or submit itself to any jurisdiction. */ -import { CommandHandler } from "../../../models/commands.model"; -import { RevokeTokenCommand } from "./revokeToken.command"; -import { ConnectionManager } from "../../connectionManager/ConnectionManager"; +import type { CommandHandler } from '../../../models/commands.model'; +import type { RevokeTokenCommand } from './revokeToken.command'; +import type { ConnectionManager } from '../../connectionManager/ConnectionManager'; /** * RevokeTokenHandler is responsible for handling the RevokeTokenCommand. @@ -38,13 +38,10 @@ export class RevokeTokenHandler implements CommandHandler { async handle(command: RevokeTokenCommand): Promise { const { targetAddress } = command.payload || {}; if (!targetAddress) { - throw new Error("Target address is required to revoke token."); + throw new Error('Target address is required to revoke token.'); } - const conn = this.manager.getConnectionByAddress( - targetAddress, - command.payload.connectionDirection - ); + const conn = this.manager.getConnectionByAddress(targetAddress, command.payload.connectionDirection); conn?.handleRevokeToken(); } diff --git a/Tokenization/backend/wrapper/src/client/connection/Connection.ts b/Tokenization/backend/wrapper/src/client/connection/Connection.ts index b44984f40..25247a8ed 100644 --- a/Tokenization/backend/wrapper/src/client/connection/Connection.ts +++ b/Tokenization/backend/wrapper/src/client/connection/Connection.ts @@ -12,8 +12,8 @@ * or submit itself to any jurisdiction. */ -import { ConnectionDirection } from "../../models/message.model"; -import { ConnectionStatus } from "../../models/connection.model"; +import type { ConnectionDirection } from '../../models/message.model'; +import { ConnectionStatus } from '../../models/connection.model'; /** * @description This class represents a connection to a target client and manages sending messages to it. @@ -25,17 +25,13 @@ export class Connection { public direction: ConnectionDirection; /** - * @description Creates a new Connection instance with the given token, target address, and connection direction. + * Creates a new Connection instance with the given token, target address, and connection direction. * * @param token - The authentication token for the connection. * @param targetAddress - The unique address of the target client. * @param direction - The direction of the connection (e.g., sending or receiving). */ - constructor( - token: string, - targetAddress: string, - direction: ConnectionDirection - ) { + constructor(token: string, targetAddress: string, direction: ConnectionDirection) { this._token = token; this._targetAddress = targetAddress; this.direction = direction; @@ -44,7 +40,7 @@ export class Connection { } /** - * @description Replace newly generated token + * Replace newly generated token * @param token New token to be replaced */ public set token(token: string) { @@ -56,12 +52,12 @@ export class Connection { * The connection status is set to UNAUTHORIZED. */ public handleRevokeToken(): void { - this._token = ""; + this._token = ''; this._status = ConnectionStatus.UNAUTHORIZED; } /** - * @description Returns token for this Connection object + * Returns token for this Connection object * @returns Connection token */ public get token(): string { @@ -69,7 +65,7 @@ export class Connection { } /** - * @description Returns status for specific + * Returns status for specific * @returns Connection status */ public get status(): string { @@ -77,7 +73,7 @@ export class Connection { } /** - * @description Returns target address for this Connection object + * Returns target address for this Connection object * @returns Target address */ public get targetAddress(): string { diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts b/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts index dbdceeed9..2d9907de6 100644 --- a/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts +++ b/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts @@ -12,64 +12,56 @@ * or submit itself to any jurisdiction. */ -import * as grpc from "@grpc/grpc-js"; -import { LogManager } from "@aliceo2/web-ui"; -import { CentralCommandDispatcher } from "./eventManagement/CentralCommandDispatcher"; -import { DuplexMessageModel } from "../../models/message.model"; +import type * as grpc from '@grpc/grpc-js'; +import { LogManager } from '@aliceo2/web-ui'; +import type { CentralCommandDispatcher } from './eventManagement/CentralCommandDispatcher'; +import type { DuplexMessageModel } from '../../models/message.model'; /** - * @description This class manages the duplex stream with the CentralSystem gRPC service. + * This class manages the duplex stream with the CentralSystem gRPC service. * It is responsible for connecting, reconnecting with backoff, and delegating received messages. */ export class CentralConnection { - private _logger = LogManager.getLogger("CentralConnection"); - private _stream?: grpc.ClientDuplexStream; + private _logger = LogManager.getLogger('CentralConnection'); + private _stream?: grpc.ClientDuplexStream; /** - * @description Constructor for the CentralConnection class. + * Constructor for the CentralConnection class. * * @param {_client} - The gRPC client instance used to connect to the CentralSystem. * @param {_dispatcher} - The CentralCommandDispatcher instance used to delegate incoming messages. * @param {centralAddress} - The address of the CentralSystem gRPC service. */ - constructor( - private _client: any, - private _dispatcher: CentralCommandDispatcher, - public centralAddress: string - ) {} + constructor(private _client: any, private _dispatcher: CentralCommandDispatcher, public centralAddress: string) {} /** - * @description Initializes the duplex stream and sets up event handlers. + * Initializes the duplex stream and sets up event handlers. */ connect() { if (this._stream) return; this._stream = this._client.ClientStream(); - this._stream!.on("data", (payload: DuplexMessageModel) => { + this._stream?.on('data', (payload: DuplexMessageModel) => { this._logger.debugMessage(`Received payload: ${JSON.stringify(payload)}`); this._dispatcher.dispatch(payload); }); - this._stream!.on("end", () => { + this._stream?.on('end', () => { this._logger.infoMessage(`Stream ended, attempting to reconnect...`); this._stream = undefined; this.scheduleReconnect(); }); - this._stream!.on("error", (err: any) => { - this._logger.infoMessage( - "Stream error:", - err, - " attempting to reconnect..." - ); + this._stream?.on('error', (err: any) => { + this._logger.infoMessage('Stream error:', err, ' attempting to reconnect...'); this._stream = undefined; this.scheduleReconnect(); }); } /** - * @description Schedules a reconnect with exponential backoff. + * Schedules a reconnect with exponential backoff. */ private scheduleReconnect() { setTimeout(() => { @@ -79,17 +71,15 @@ export class CentralConnection { } /** - * @description Starts the connection to the central system. + * Starts the connection to the central system. */ start() { this.connect(); - this._logger.infoMessage( - `Connected to CentralSystem on ${this.centralAddress}` - ); + this._logger.infoMessage(`Connected to CentralSystem on ${this.centralAddress}`); } /** - * @description Disconnects from the gRPC stream and resets attempts. + * Disconnects from the gRPC stream and resets attempts. */ disconnect() { if (this._stream) { diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts index cb11230e1..57168742d 100644 --- a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts @@ -12,17 +12,15 @@ * or submit itself to any jurisdiction. */ -import * as grpc from "@grpc/grpc-js"; -import * as protoLoader from "@grpc/proto-loader"; -import { CentralConnection } from "./CentralConnection"; -import { CentralCommandDispatcher } from "./eventManagement/CentralCommandDispatcher"; -import { Connection } from "../connection/Connection"; -import { LogManager } from "@aliceo2/web-ui"; -import { Command, CommandHandler } from "models/commands.model"; -import { - ConnectionDirection, - DuplexMessageEvent, -} from "../../models/message.model"; +import * as grpc from '@grpc/grpc-js'; +import * as protoLoader from '@grpc/proto-loader'; +import { CentralConnection } from './CentralConnection'; +import { CentralCommandDispatcher } from './eventManagement/CentralCommandDispatcher'; +import { Connection } from '../connection/Connection'; +import { LogManager } from '@aliceo2/web-ui'; +import type { Command, CommandHandler } from 'models/commands.model'; +import type { DuplexMessageEvent } from '../../models/message.model'; +import { ConnectionDirection } from '../../models/message.model'; /** * @description Manages all the connection between clients and central system. @@ -42,21 +40,21 @@ import { * - `receivingConnections`: Map of active inbound connections. */ export class ConnectionManager { - private _logger = LogManager.getLogger("ConnectionManager"); + private _logger = LogManager.getLogger('ConnectionManager'); private _centralDispatcher: CentralCommandDispatcher; private _centralConnection: CentralConnection; private _sendingConnections = new Map(); private _receivingConnections = new Map(); /** - * @description Initializes a new instance of the ConnectionManager class. + * Initializes a new instance of the ConnectionManager class. * * This constructor sets up the gRPC client for communication with the central system. * * @param protoPath - The file path to the gRPC proto definition. * @param centralAddress - The address of the central gRPC server (default: "localhost:50051"). */ - constructor(protoPath: string, centralAddress: string = "localhost:50051") { + constructor(protoPath: string, centralAddress: string = 'localhost:50051') { const packageDef = protoLoader.loadSync(protoPath, { keepCase: true, longs: String, @@ -68,18 +66,11 @@ export class ConnectionManager { const proto = grpc.loadPackageDefinition(packageDef) as any; const wrapper = proto.webui.tokenization; - const client = new wrapper.CentralSystem( - centralAddress, - grpc.credentials.createInsecure() - ); + const client = new wrapper.CentralSystem(centralAddress, grpc.credentials.createInsecure()); - // event dispatcher for central system events + // Event dispatcher for central system events this._centralDispatcher = new CentralCommandDispatcher(); - this._centralConnection = new CentralConnection( - client, - this._centralDispatcher, - centralAddress - ); + this._centralConnection = new CentralConnection(client, this._centralDispatcher, centralAddress); } /** @@ -98,14 +89,14 @@ export class ConnectionManager { } /** - * @description Starts the connection to the central system. + * Starts the connection to the central system. */ connectToCentralSystem(): void { this._centralConnection.start(); } /** - * @description Disconnects from the central system. + * Disconnects from the central system. */ disconnectFromCentralSystem(): void { this._centralConnection.disconnect(); @@ -117,12 +108,8 @@ export class ConnectionManager { * @param direction Direction of connection * @param token Optional token for connection */ - createNewConnection( - address: string, - direction: ConnectionDirection, - token?: string - ) { - const conn = new Connection(token || "", address, direction); + createNewConnection(address: string, direction: ConnectionDirection, token?: string) { + const conn = new Connection(token ?? '', address, direction); if (direction === ConnectionDirection.RECEIVING) { this._receivingConnections.set(address, conn); @@ -134,13 +121,10 @@ export class ConnectionManager { } /** - * @description Gets the connection instance by address. + * Gets the connection instance by address. * @returns{Connection} connection instance. */ - getConnectionByAddress( - address: string, - direction: ConnectionDirection - ): Connection | undefined { + getConnectionByAddress(address: string, direction: ConnectionDirection): Connection | undefined { switch (direction) { case ConnectionDirection.SENDING: return this._sendingConnections.get(address); @@ -153,7 +137,7 @@ export class ConnectionManager { } /** - * @description Returns all saved connections. + * Returns all saved connections. * * @returns An object containing the sending and receiving connections. */ diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/eventManagement/CentralCommandDispatcher.ts b/Tokenization/backend/wrapper/src/client/connectionManager/eventManagement/CentralCommandDispatcher.ts index 83ba83990..64b557bce 100644 --- a/Tokenization/backend/wrapper/src/client/connectionManager/eventManagement/CentralCommandDispatcher.ts +++ b/Tokenization/backend/wrapper/src/client/connectionManager/eventManagement/CentralCommandDispatcher.ts @@ -12,9 +12,9 @@ * or submit itself to any jurisdiction. */ -import { LogManager } from "@aliceo2/web-ui"; -import { Command, CommandHandler } from "models/commands.model"; -import { DuplexMessageEvent } from "../../../models/message.model"; +import { LogManager } from '@aliceo2/web-ui'; +import type { Command, CommandHandler } from 'models/commands.model'; +import type { DuplexMessageEvent } from '../../../models/message.model'; /** * CentralCommandDispatcher is responsible for registering and dispatching command handlers @@ -23,7 +23,7 @@ import { DuplexMessageEvent } from "../../../models/message.model"; */ export class CentralCommandDispatcher { private _handlers = new Map(); - private _logger = LogManager.getLogger("CentralCommandDispatcher"); + private _logger = LogManager.getLogger('CentralCommandDispatcher'); /** * Registers a command handler for a specific command event type. @@ -31,10 +31,7 @@ export class CentralCommandDispatcher { * @param event - The event type of the command to be handled. * @param handler - The handler that should process commands of the given event type. */ - register( - event: DuplexMessageEvent, - handler: CommandHandler - ): void { + register(event: DuplexMessageEvent, handler: CommandHandler): void { this._logger.infoMessage(`Registering handler for command type: ${event}`); this._handlers.set(event, handler); } @@ -49,19 +46,14 @@ export class CentralCommandDispatcher { const handler = this._handlers.get(command.event); this._logger.debugMessage(`Dispatching command: ${command.event}`); if (!handler) { - this._logger.warnMessage( - `No handler registered for command type: ${command.event}` - ); + this._logger.warnMessage(`No handler registered for command type: ${command.event}`); return; } try { await handler.handle(command); } catch (error) { - this._logger.errorMessage( - `Error handling command ${command.event}:`, - error - ); + this._logger.errorMessage(`Error handling command ${command.event}:`, error); } } } diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index c70084677..76f93bd60 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -12,11 +12,11 @@ * or submit itself to any jurisdiction. */ -import { ConnectionManager } from "./connectionManager/ConnectionManager"; -import { RevokeTokenHandler } from "./commands/revokeToken/revokeToken.handler"; -import { DuplexMessageEvent } from "../models/message.model"; -import { Connection } from "./connection/Connection"; -import { NewTokenHandler } from "./commands/newToken/newToken.handler"; +import { ConnectionManager } from './connectionManager/ConnectionManager'; +import { RevokeTokenHandler } from './commands/revokeToken/revokeToken.handler'; +import { DuplexMessageEvent } from '../models/message.model'; +import type { Connection } from './connection/Connection'; +import { NewTokenHandler } from './commands/newToken/newToken.handler'; /** * @description Wrapper class for managing secure gRPC wrapper. @@ -36,12 +36,12 @@ export class gRPCWrapper { private _connectionManager: ConnectionManager; /** - * @description Initializes an instance of gRPCWrapper class. + * Initializes an instance of gRPCWrapper class. * * @param protoPath - The file path to the gRPC proto definition. * @param centralAddress - The address of the central gRPC server (default: "localhost:4100"). */ - constructor(protoPath: string, centralAddress: string = "localhost:4100") { + constructor(protoPath: string, centralAddress: string = 'localhost:4100') { this._connectionManager = new ConnectionManager(protoPath, centralAddress); this._connectionManager.registerCommandHandlers([ { @@ -56,14 +56,14 @@ export class gRPCWrapper { } /** - * @description Starts the Connection Manager stream connection with Central System + * Starts the Connection Manager stream connection with Central System */ public connectToCentralSystem() { this._connectionManager.connectToCentralSystem(); } /** - * @description Returns all saved connections. + * Returns all saved connections. * * @returns An object containing the sending and receiving connections. */ @@ -75,7 +75,7 @@ export class gRPCWrapper { } /** - * @description Returns a summary of the connections managed by the ConnectionManager. + * Returns a summary of the connections managed by the ConnectionManager. * The summary includes the number of sending and receiving connections, as well as the target address, direction, and status of each connection. * * @returns A string summary of the connections. @@ -85,13 +85,9 @@ export class gRPCWrapper { return ( `Wrapper Summary: ` + `\nSending Connections: ${conn.sending.length}` + - `\nReceiving Connections: ${conn.receiving.length}` + - conn.sending + `\nReceiving Connections: ${conn.receiving.length}${conn.sending .map((c) => `\n- ${c.targetAddress} - ${c.direction}\n\t(${c.status})`) - .join("") + - conn.receiving - .map((c) => `\n- ${c.targetAddress} - ${c.direction}\n\t(${c.status})`) - .join("") + .join('')}${conn.receiving.map((c) => `\n- ${c.targetAddress} - ${c.direction}\n\t(${c.status})`).join('')}` ); } } diff --git a/Tokenization/backend/wrapper/src/models/commands.model.ts b/Tokenization/backend/wrapper/src/models/commands.model.ts index 1bba14108..fd71fd9fb 100644 --- a/Tokenization/backend/wrapper/src/models/commands.model.ts +++ b/Tokenization/backend/wrapper/src/models/commands.model.ts @@ -12,7 +12,7 @@ * or submit itself to any jurisdiction. */ -import { DuplexMessageEvent } from "./message.model"; +import type { DuplexMessageEvent } from './message.model'; /** * Interface representing a Command for specific event. diff --git a/Tokenization/backend/wrapper/src/models/connection.model.ts b/Tokenization/backend/wrapper/src/models/connection.model.ts index 402397dc3..2bc8a6f98 100644 --- a/Tokenization/backend/wrapper/src/models/connection.model.ts +++ b/Tokenization/backend/wrapper/src/models/connection.model.ts @@ -29,18 +29,18 @@ */ export enum ConnectionStatus { // The connection is in the process of being established - CONNECTING = "CONNECTING", + CONNECTING = 'CONNECTING', // The connection has been successfully established - CONNECTED = "CONNECTED", + CONNECTED = 'CONNECTED', // The connection attempt failed due to authorization issues - // or token has expired/been revoked - UNAUTHORIZED = "UNAUTHORIZED", + // Or token has expired/been revoked + UNAUTHORIZED = 'UNAUTHORIZED', // The connection has been closed - CLOSED = "CLOSED", + CLOSED = 'CLOSED', // An error occurred with the connection - ERROR = "ERROR", + ERROR = 'ERROR', // The connection is attempting to re-establish after a disruption - RECONNECTING = "RECONNECTING", + RECONNECTING = 'RECONNECTING', // The connection is refreshing its authentication token - TOKEN_REFRESH = "TOKEN_REFRESH", + TOKEN_REFRESH = 'TOKEN_REFRESH', } diff --git a/Tokenization/backend/wrapper/src/models/message.model.ts b/Tokenization/backend/wrapper/src/models/message.model.ts index 3d86a937e..918f48505 100644 --- a/Tokenization/backend/wrapper/src/models/message.model.ts +++ b/Tokenization/backend/wrapper/src/models/message.model.ts @@ -23,10 +23,10 @@ * @property MESSAGE_EVENT_REVOKE_TOKEN: Event for revoking an existing token. */ export enum DuplexMessageEvent { - MESSAGE_EVENT_UNSPECIFIED = "MESSAGE_EVENT_UNSPECIFIED", - MESSAGE_EVENT_EMPTY = "MESSAGE_EVENT_EMPTY", - MESSAGE_EVENT_NEW_TOKEN = "MESSAGE_EVENT_NEW_TOKEN", - MESSAGE_EVENT_REVOKE_TOKEN = "MESSAGE_EVENT_REVOKE_TOKEN", + MESSAGE_EVENT_UNSPECIFIED = 'MESSAGE_EVENT_UNSPECIFIED', + MESSAGE_EVENT_EMPTY = 'MESSAGE_EVENT_EMPTY', + MESSAGE_EVENT_NEW_TOKEN = 'MESSAGE_EVENT_NEW_TOKEN', + MESSAGE_EVENT_REVOKE_TOKEN = 'MESSAGE_EVENT_REVOKE_TOKEN', } /** @@ -36,10 +36,10 @@ export enum DuplexMessageEvent { * @property DUPLEX: Indicates a connection that can both send and receive messages. */ export enum ConnectionDirection { - UNSPECIFIED = "UNSPECIFIED", - SENDING = "SENDING", - RECEIVING = "RECEIVING", - DUPLEX = "DUPLEX", + UNSPECIFIED = 'UNSPECIFIED', + SENDING = 'SENDING', + RECEIVING = 'RECEIVING', + DUPLEX = 'DUPLEX', } // ====================================== diff --git a/Tokenization/backend/wrapper/src/test/central/CentralSystemWrapper.test.ts b/Tokenization/backend/wrapper/src/test/central/CentralSystemWrapper.test.ts index 416fb2cc2..e31cf8f1d 100644 --- a/Tokenization/backend/wrapper/src/test/central/CentralSystemWrapper.test.ts +++ b/Tokenization/backend/wrapper/src/test/central/CentralSystemWrapper.test.ts @@ -23,31 +23,31 @@ const logger = { infoMessage: jest.fn(), }; -jest.mock("@aliceo2/web-ui", () => ({ +jest.mock('@aliceo2/web-ui', () => ({ LogManager: { getLogger: () => logger, }, })); -jest.mock("@grpc/proto-loader", () => ({ +jest.mock('@grpc/proto-loader', () => ({ loadSync: jest.fn(() => { return {}; }), })); -jest.mock("@grpc/grpc-js", () => { - const original = jest.requireActual("@grpc/grpc-js"); +jest.mock('@grpc/grpc-js', () => { + const original = jest.requireActual('@grpc/grpc-js'); return { ...original, Server: jest.fn(() => mockServerInstance), ServerCredentials: { - createInsecure: jest.fn(() => "mock-credentials"), + createInsecure: jest.fn(() => 'mock-credentials'), }, loadPackageDefinition: jest.fn(() => ({ webui: { tokenization: { CentralSystem: { - service: "mock-service", + service: 'mock-service', }, }, }, @@ -55,57 +55,50 @@ jest.mock("@grpc/grpc-js", () => { }; }); -import { CentralSystemWrapper } from "../../central/CentralSystemWrapper"; -import * as grpc from "@grpc/grpc-js"; +import { CentralSystemWrapper } from '../../central/CentralSystemWrapper'; +import * as grpc from '@grpc/grpc-js'; -describe("CentralSystemWrapper", () => { +describe('CentralSystemWrapper', () => { let wrapper: CentralSystemWrapper; beforeEach(() => { jest.clearAllMocks(); - wrapper = new CentralSystemWrapper("dummy.proto", 12345); + wrapper = new CentralSystemWrapper('dummy.proto', 12345); }); - test("should set up gRPC service and add it to the server", () => { - const testWrapper = new CentralSystemWrapper("dummy.proto", 12345); + test('should set up gRPC service and add it to the server', () => { + const testWrapper = new CentralSystemWrapper('dummy.proto', 12345); expect(grpc.Server).toHaveBeenCalled(); expect(grpc.loadPackageDefinition).toHaveBeenCalled(); expect(grpc.ServerCredentials.createInsecure).not.toHaveBeenCalled(); expect(testWrapper).toBeDefined(); }); - test("should call listen and bind the server", () => { + test('should call listen and bind the server', () => { mockBindAsync.mockImplementation((_addr, _creds, cb) => cb(null, 12345)); wrapper.listen(); - expect(mockBindAsync).toHaveBeenCalledWith( - "localhost:12345", - "mock-credentials", - expect.any(Function) - ); + expect(mockBindAsync).toHaveBeenCalledWith('localhost:12345', 'mock-credentials', expect.any(Function)); }); - test("should log error if bind fails", () => { - const error = new Error("bind failed"); + test('should log error if bind fails', () => { + const error = new Error('bind failed'); mockBindAsync.mockImplementation((_addr, _creds, cb) => cb(error, null)); wrapper.listen(); - expect(logger.infoMessage).toHaveBeenCalledWith( - "Server bind error:", - error - ); + expect(logger.infoMessage).toHaveBeenCalledWith('Server bind error:', error); }); - test("should handle client stream events", () => { - const logger = require("@aliceo2/web-ui").LogManager.getLogger(); + test('should handle client stream events', () => { + const logger = require('@aliceo2/web-ui').LogManager.getLogger(); const mockCall = { - getPeer: jest.fn(() => "client123"), + getPeer: jest.fn(() => 'client123'), on: jest.fn((event, cb) => { - if (event === "end") cb(); - if (event === "error") cb(new Error("stream error")); + if (event === 'end') cb(); + if (event === 'error') cb(new Error('stream error')); }), end: jest.fn(), }; @@ -113,21 +106,14 @@ describe("CentralSystemWrapper", () => { const handler = (wrapper as any).clientStreamHandler.bind(wrapper); handler(mockCall); - expect(mockCall.on).toHaveBeenCalledWith("data", expect.any(Function)); - expect(mockCall.on).toHaveBeenCalledWith("end", expect.any(Function)); - expect(mockCall.on).toHaveBeenCalledWith("error", expect.any(Function)); + expect(mockCall.on).toHaveBeenCalledWith('data', expect.any(Function)); + expect(mockCall.on).toHaveBeenCalledWith('end', expect.any(Function)); + expect(mockCall.on).toHaveBeenCalledWith('error', expect.any(Function)); expect(mockCall.end).toHaveBeenCalled(); - expect(logger.infoMessage).toHaveBeenCalledWith( - expect.stringContaining("Client client123") - ); - - expect(logger.infoMessage).toHaveBeenCalledWith( - "Client client123 ended stream." - ); - expect(logger.infoMessage).toHaveBeenCalledWith( - "Stream error from client client123:", - expect.any(Error) - ); + expect(logger.infoMessage).toHaveBeenCalledWith(expect.stringContaining('Client client123')); + + expect(logger.infoMessage).toHaveBeenCalledWith('Client client123 ended stream.'); + expect(logger.infoMessage).toHaveBeenCalledWith('Stream error from client client123:', expect.any(Error)); }); }); diff --git a/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts b/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts index cfdf493cd..683943527 100644 --- a/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts @@ -12,45 +12,35 @@ * or submit itself to any jurisdiction. */ -import { NewTokenCommand } from "../../../client/commands/newToken/newToken.command"; -import { NewTokenHandler } from "../../../client/commands/newToken/newToken.handler"; -import { Connection } from "../../../client/connection/Connection"; -import { ConnectionManager } from "../../../client/connectionManager/ConnectionManager"; -import { Command } from "models/commands.model"; -import { - ConnectionDirection, - DuplexMessageEvent, -} from "../../../models/message.model"; +import { NewTokenCommand } from '../../../client/commands/newToken/newToken.command'; +import { NewTokenHandler } from '../../../client/commands/newToken/newToken.handler'; +import { Connection } from '../../../client/connection/Connection'; +import { ConnectionManager } from '../../../client/connectionManager/ConnectionManager'; +import { Command } from 'models/commands.model'; +import { ConnectionDirection, DuplexMessageEvent } from '../../../models/message.model'; /** * Helper to create a new token command with given address, direction, and token. */ -function createEventMessage( - targetAddress: string, - connectionDirection: ConnectionDirection -): Command { +function createEventMessage(targetAddress: string, connectionDirection: ConnectionDirection): Command { return { event: DuplexMessageEvent.MESSAGE_EVENT_NEW_TOKEN, payload: { targetAddress, connectionDirection, - token: "test-token", + token: 'test-token', }, } as Command; } -describe("NewTokenHandler", () => { +describe('NewTokenHandler', () => { let manager: ConnectionManager; beforeEach(() => { manager = { sendingConnections: new Map(), receivingConnections: new Map(), - getConnectionByAddress: jest.fn(function ( - this: any, - address: string, - dir: ConnectionDirection - ) { + getConnectionByAddress: jest.fn(function (this: any, address: string, dir: ConnectionDirection) { if (dir === ConnectionDirection.SENDING) { return this.sendingConnections.get(address); } else if (dir === ConnectionDirection.RECEIVING) { @@ -58,12 +48,7 @@ describe("NewTokenHandler", () => { } return undefined; }), - createNewConnection: jest.fn(function ( - this: any, - address: string, - dir: ConnectionDirection, - token: string - ) { + createNewConnection: jest.fn(function (this: any, address: string, dir: ConnectionDirection, token: string) { const conn = new Connection(token, address, dir); if (dir === ConnectionDirection.SENDING) { this.sendingConnections.set(address, conn); @@ -75,75 +60,61 @@ describe("NewTokenHandler", () => { } as unknown as ConnectionManager; }); - it("should update token on existing SENDING connection", async () => { - const targetAddress = "peer-123"; - const conn = new Connection( - "old-token", - targetAddress, - ConnectionDirection.SENDING - ); + it('should update token on existing SENDING connection', async () => { + const targetAddress = 'peer-123'; + const conn = new Connection('old-token', targetAddress, ConnectionDirection.SENDING); (manager as any).sendingConnections.set(targetAddress, conn); const handler = new NewTokenHandler(manager); - const command = new NewTokenCommand( - createEventMessage(targetAddress, ConnectionDirection.SENDING).payload - ); + const command = new NewTokenCommand(createEventMessage(targetAddress, ConnectionDirection.SENDING).payload); await handler.handle(command); - expect(conn.token).toBe("test-token"); + expect(conn.token).toBe('test-token'); }); - it("should create new RECEIVING connection if not found", async () => { - const targetAddress = "peer-456"; + it('should create new RECEIVING connection if not found', async () => { + const targetAddress = 'peer-456'; const handler = new NewTokenHandler(manager); - const command = new NewTokenCommand( - createEventMessage(targetAddress, ConnectionDirection.RECEIVING).payload - ); + const command = new NewTokenCommand(createEventMessage(targetAddress, ConnectionDirection.RECEIVING).payload); await handler.handle(command); const conn = (manager as any).receivingConnections.get(targetAddress); expect(conn).toBeDefined(); - expect(conn.token).toBe("test-token"); + expect(conn.token).toBe('test-token'); }); - it("should handle DUPLEX direction by updating/creating both connections", async () => { - const targetAddress = "peer-789"; + it('should handle DUPLEX direction by updating/creating both connections', async () => { + const targetAddress = 'peer-789'; const handler = new NewTokenHandler(manager); - const command = new NewTokenCommand( - createEventMessage(targetAddress, ConnectionDirection.DUPLEX).payload - ); + const command = new NewTokenCommand(createEventMessage(targetAddress, ConnectionDirection.DUPLEX).payload); await handler.handle(command); const sendingConn = (manager as any).sendingConnections.get(targetAddress); - const receivingConn = (manager as any).receivingConnections.get( - targetAddress - ); + const receivingConn = (manager as any).receivingConnections.get(targetAddress); expect(sendingConn).toBeDefined(); expect(receivingConn).toBeDefined(); - expect(sendingConn.token).toBe("test-token"); - expect(receivingConn.token).toBe("test-token"); + expect(sendingConn.token).toBe('test-token'); + expect(receivingConn.token).toBe('test-token'); }); - it("should throw error when payload is missing required fields", async () => { + it('should throw error when payload is missing required fields', async () => { const invalidCommand = new NewTokenCommand({} as any); const handler = new NewTokenHandler(manager); - await expect(handler.handle(invalidCommand)).rejects.toThrow( - "Insufficient arguments. Expected: targetAddress, connectionDirection, token." - ); + await expect(handler.handle(invalidCommand)).rejects.toThrow('Insufficient arguments. Expected: targetAddress, connectionDirection, token.'); }); - it("should create command with correct event and payload", () => { + it('should create command with correct event and payload', () => { const payload = { - targetAddress: "peer-000", + targetAddress: 'peer-000', connectionDirection: ConnectionDirection.SENDING, - token: "sample-token", + token: 'sample-token', }; const command = new NewTokenCommand(payload); diff --git a/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts b/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts index 157090985..b7d68ea5e 100644 --- a/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts @@ -12,24 +12,21 @@ * or submit itself to any jurisdiction. */ -import { RevokeTokenCommand } from "../../../client/commands/revokeToken/revokeToken.command"; -import { RevokeTokenHandler } from "../../../client/commands/revokeToken/revokeToken.handler"; -import { Connection } from "../../../client/connection/Connection"; -import { ConnectionManager } from "../../../client/connectionManager/ConnectionManager"; -import { - ConnectionDirection, - DuplexMessageEvent, -} from "../../../models/message.model"; -import { ConnectionStatus } from "../../../models/connection.model"; -import { Command } from "models/commands.model"; - -describe("RevokeToken", () => { +import { RevokeTokenCommand } from '../../../client/commands/revokeToken/revokeToken.command'; +import { RevokeTokenHandler } from '../../../client/commands/revokeToken/revokeToken.handler'; +import { Connection } from '../../../client/connection/Connection'; +import { ConnectionManager } from '../../../client/connectionManager/ConnectionManager'; +import { ConnectionDirection, DuplexMessageEvent } from '../../../models/message.model'; +import { ConnectionStatus } from '../../../models/connection.model'; +import { Command } from 'models/commands.model'; + +describe('RevokeToken', () => { function createEventMessage(targetAddress: string) { return { event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, payload: { targetAddress: targetAddress, - token: "test-token", + token: 'test-token', }, } as Command; } @@ -41,84 +38,62 @@ describe("RevokeToken", () => { sendingConnections: new Map(), receivingConnections: new Map(), getConnectionByAddress: jest.fn(function (this: any, address: string) { - return ( - this.sendingConnections.get(address) || - this.receivingConnections.get(address) - ); + return this.sendingConnections.get(address) || this.receivingConnections.get(address); }), } as unknown as ConnectionManager; }); - it("should revoke token when connection found in sendingConnections", async () => { - const targetAddress = "peer-123"; - const conn = new Connection( - "valid-token", - targetAddress, - ConnectionDirection.SENDING - ); + it('should revoke token when connection found in sendingConnections', async () => { + const targetAddress = 'peer-123'; + const conn = new Connection('valid-token', targetAddress, ConnectionDirection.SENDING); (manager as any).sendingConnections!.set(targetAddress, conn); const handler = new RevokeTokenHandler(manager); - const command = new RevokeTokenCommand( - createEventMessage(targetAddress).payload - ); + const command = new RevokeTokenCommand(createEventMessage(targetAddress).payload); await handler.handle(command); - expect(conn.token).toBe(""); + expect(conn.token).toBe(''); expect(conn.status).toBe(ConnectionStatus.UNAUTHORIZED); }); - it("should revoke token when connection found in receivingConnections", async () => { - const targetAddress = "peer-456"; - const conn = new Connection( - "valid-token", - targetAddress, - ConnectionDirection.RECEIVING - ); + it('should revoke token when connection found in receivingConnections', async () => { + const targetAddress = 'peer-456'; + const conn = new Connection('valid-token', targetAddress, ConnectionDirection.RECEIVING); (manager as any).receivingConnections.set(targetAddress, conn); const handler = new RevokeTokenHandler(manager); - const command = new RevokeTokenCommand( - createEventMessage(targetAddress).payload - ); + const command = new RevokeTokenCommand(createEventMessage(targetAddress).payload); await handler.handle(command); - expect(conn.token).toBe(""); + expect(conn.token).toBe(''); expect(conn.status).toBe(ConnectionStatus.UNAUTHORIZED); }); - it("should do nothing when connection not found", async () => { - const targetAddress = "non-existent"; + it('should do nothing when connection not found', async () => { + const targetAddress = 'non-existent'; const handler = new RevokeTokenHandler(manager); - const command = new RevokeTokenCommand( - createEventMessage(targetAddress).payload - ); + const command = new RevokeTokenCommand(createEventMessage(targetAddress).payload); await expect(handler.handle(command)).resolves.toBeUndefined(); - expect(manager.getConnectionByAddress).toHaveBeenCalledWith( - targetAddress, - undefined - ); + expect(manager.getConnectionByAddress).toHaveBeenCalledWith(targetAddress, undefined); }); - it("should throw error when targetAddress is missing", async () => { + it('should throw error when targetAddress is missing', async () => { const invalidMessage = { event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, - revokeToken: { token: "test-token" }, + revokeToken: { token: 'test-token' }, }; const handler = new RevokeTokenHandler(manager); const command = new RevokeTokenCommand(invalidMessage as any); - await expect(handler.handle(command)).rejects.toThrow( - "Target address is required to revoke token." - ); + await expect(handler.handle(command)).rejects.toThrow('Target address is required to revoke token.'); }); - it("should create command with correct type and payload", () => { - const eventMessage = createEventMessage("peer-001"); + it('should create command with correct type and payload', () => { + const eventMessage = createEventMessage('peer-001'); const command = new RevokeTokenCommand(eventMessage.payload); expect(command.event).toBe(DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN); diff --git a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts index 7ce6394ee..f76fe6dc4 100644 --- a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts +++ b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts @@ -12,9 +12,9 @@ * or submit itself to any jurisdiction. */ -import * as grpc from "@grpc/grpc-js"; -import { ConnectionManager } from "../../../client/connectionManager/ConnectionManager"; -import { DuplexMessageEvent } from "../../../models/message.model"; +import * as grpc from '@grpc/grpc-js'; +import { ConnectionManager } from '../../../client/connectionManager/ConnectionManager'; +import { DuplexMessageEvent } from '../../../models/message.model'; // Mock duplex stream const mockStream = { @@ -32,17 +32,14 @@ const CentralSystemMock = jest.fn(() => mockClient); // Mock dispatcher const mockDispatch = jest.fn(); -jest.mock( - "../../../client/ConnectionManager/EventManagement/CentralCommandDispatcher", - () => ({ - CentralCommandDispatcher: jest.fn(() => ({ - dispatch: mockDispatch, - })), - }) -); +jest.mock('../../../client/ConnectionManager/EventManagement/CentralCommandDispatcher', () => ({ + CentralCommandDispatcher: jest.fn(() => ({ + dispatch: mockDispatch, + })), +})); // Mock logger -jest.mock("@aliceo2/web-ui", () => ({ +jest.mock('@aliceo2/web-ui', () => ({ LogManager: { getLogger: () => ({ infoMessage: jest.fn(), @@ -51,14 +48,14 @@ jest.mock("@aliceo2/web-ui", () => ({ })); // Mock gRPC proto loader and client -jest.mock("@grpc/proto-loader", () => ({ +jest.mock('@grpc/proto-loader', () => ({ loadSync: jest.fn(() => { return {}; }), })); -jest.mock("@grpc/grpc-js", () => { - const original = jest.requireActual("@grpc/grpc-js"); +jest.mock('@grpc/grpc-js', () => { + const original = jest.requireActual('@grpc/grpc-js'); return { ...original, credentials: { @@ -74,35 +71,32 @@ jest.mock("@grpc/grpc-js", () => { }; }); -describe("ConnectionManager", () => { +describe('ConnectionManager', () => { let conn: ConnectionManager; beforeEach(() => { jest.clearAllMocks(); - conn = new ConnectionManager("dummy.proto", "localhost:12345"); + conn = new ConnectionManager('dummy.proto', 'localhost:12345'); }); - test("should initialize client with correct address", () => { - const connManager = new ConnectionManager("dummy.proto", "localhost:12345"); + test('should initialize client with correct address', () => { + const connManager = new ConnectionManager('dummy.proto', 'localhost:12345'); expect(connManager).toBeDefined(); expect(grpc.loadPackageDefinition).toHaveBeenCalled(); - expect(CentralSystemMock).toHaveBeenCalledWith( - "localhost:12345", - undefined - ); + expect(CentralSystemMock).toHaveBeenCalledWith('localhost:12345', undefined); }); - test("connectToCentralSystem() should set up stream listeners", () => { - const connManager = new ConnectionManager("dummy.proto", "localhost:12345"); + test('connectToCentralSystem() should set up stream listeners', () => { + const connManager = new ConnectionManager('dummy.proto', 'localhost:12345'); connManager.connectToCentralSystem(); expect(mockClient.ClientStream).toHaveBeenCalled(); - expect(mockStream.on).toHaveBeenCalledWith("data", expect.any(Function)); - expect(mockStream.on).toHaveBeenCalledWith("end", expect.any(Function)); - expect(mockStream.on).toHaveBeenCalledWith("error", expect.any(Function)); + expect(mockStream.on).toHaveBeenCalledWith('data', expect.any(Function)); + expect(mockStream.on).toHaveBeenCalledWith('end', expect.any(Function)); + expect(mockStream.on).toHaveBeenCalledWith('error', expect.any(Function)); }); - test("disconnectFromCentralSystem() should end stream", () => { + test('disconnectFromCentralSystem() should end stream', () => { conn.connectToCentralSystem(); conn.disconnectFromCentralSystem(); @@ -112,9 +106,7 @@ describe("ConnectionManager", () => { test("should reconnect on stream 'end'", () => { jest.useFakeTimers(); conn.connectToCentralSystem(); - const onEnd = mockStream.on.mock.calls.find( - ([event]) => event === "end" - )?.[1]; + const onEnd = mockStream.on.mock.calls.find(([event]) => event === 'end')?.[1]; onEnd?.(); // simulate 'end' jest.advanceTimersByTime(2000); @@ -126,11 +118,9 @@ describe("ConnectionManager", () => { test("should reconnect on stream 'error'", () => { jest.useFakeTimers(); conn.connectToCentralSystem(); - const onError = mockStream.on.mock.calls.find( - ([event]) => event === "error" - )?.[1]; + const onError = mockStream.on.mock.calls.find(([event]) => event === 'error')?.[1]; - onError?.(new Error("Simulated error")); + onError?.(new Error('Simulated error')); jest.advanceTimersByTime(2000); expect(mockClient.ClientStream).toHaveBeenCalledTimes(2); @@ -139,16 +129,14 @@ describe("ConnectionManager", () => { test("should dispatch event when 'data' is received", () => { conn.connectToCentralSystem(); - const onData = mockStream.on.mock.calls.find( - ([event]) => event === "data" - )?.[1]; + const onData = mockStream.on.mock.calls.find(([event]) => event === 'data')?.[1]; const mockMessage = { event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, data: { revokeToken: { - token: "abc123", - targetAddress: "peer-123", + token: 'abc123', + targetAddress: 'peer-123', }, }, }; diff --git a/Tokenization/backend/wrapper/src/utils/types/webui.d.ts b/Tokenization/backend/wrapper/src/utils/types/webui.d.ts index 9432663e8..9dfad4fb3 100644 --- a/Tokenization/backend/wrapper/src/utils/types/webui.d.ts +++ b/Tokenization/backend/wrapper/src/utils/types/webui.d.ts @@ -12,7 +12,7 @@ * or submit itself to any jurisdiction. */ -declare module "@aliceo2/web-ui" { +declare module '@aliceo2/web-ui' { export const LogManager: { getLogger: (name: string) => { infoMessage: (...args: any[]) => void; From a253f5152cd30eb06a746264a365e35daa4ec4c7 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Wed, 12 Nov 2025 17:47:57 +0100 Subject: [PATCH 084/103] fix: fix files, unit tests and add tests for connection --- Tokenization/backend/proto/wrapper.proto | 3 - .../src/client/connection/Connection.ts | 58 ++- .../connectionManager/ConnectionManager.ts | 2 +- .../backend/wrapper/src/client/gRPCWrapper.ts | 4 +- .../wrapper/src/models/message.model.ts | 1 - .../test/central/CentralSystemWrapper.test.ts | 3 +- .../src/test/client/commands/newToken.test.ts | 9 +- .../test/client/commands/revokeToken.test.ts | 20 +- .../ConnectionManager.test..ts | 152 ------ .../ConnectionManager.test.ts | 484 +++++++++++------- .../src/test/connection/Connection.test.ts | 211 ++++++++ 11 files changed, 587 insertions(+), 360 deletions(-) delete mode 100644 Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts create mode 100644 Tokenization/backend/wrapper/src/test/connection/Connection.test.ts diff --git a/Tokenization/backend/proto/wrapper.proto b/Tokenization/backend/proto/wrapper.proto index 93c1ca852..116131b80 100644 --- a/Tokenization/backend/proto/wrapper.proto +++ b/Tokenization/backend/proto/wrapper.proto @@ -88,9 +88,6 @@ message HttpLikeResponse { // ====================================== enum MessageEvent { - // Unspecified event type - MESSAGE_EVENT_UNSPECIFIED = 0; - // Default value, represents an empty event MESSAGE_EVENT_EMPTY = 0; diff --git a/Tokenization/backend/wrapper/src/client/connection/Connection.ts b/Tokenization/backend/wrapper/src/client/connection/Connection.ts index 25247a8ed..3f27c31b5 100644 --- a/Tokenization/backend/wrapper/src/client/connection/Connection.ts +++ b/Tokenization/backend/wrapper/src/client/connection/Connection.ts @@ -13,7 +13,9 @@ */ import type { ConnectionDirection } from '../../models/message.model'; +import type { ConnectionHeaders, FetchOptions, FetchResponse } from '../../models/connection.model'; import { ConnectionStatus } from '../../models/connection.model'; +import * as grpc from '@grpc/grpc-js'; /** * @description This class represents a connection to a target client and manages sending messages to it. @@ -22,6 +24,7 @@ export class Connection { private _token: string; private _targetAddress: string; private _status: ConnectionStatus; + private _peerClient: any; public direction: ConnectionDirection; /** @@ -31,9 +34,10 @@ export class Connection { * @param targetAddress - The unique address of the target client. * @param direction - The direction of the connection (e.g., sending or receiving). */ - constructor(token: string, targetAddress: string, direction: ConnectionDirection) { + constructor(token: string, targetAddress: string, direction: ConnectionDirection, peerCtor: any) { this._token = token; this._targetAddress = targetAddress; + this._peerClient = new peerCtor(targetAddress, grpc.credentials.createInsecure()); this.direction = direction; this._status = ConnectionStatus.CONNECTED; @@ -72,6 +76,14 @@ export class Connection { return this._status; } + /** + * Sets the status of this connection. + * @param status The new status of the connection. + */ + public set status(status: ConnectionStatus) { + this._status = status; + } + /** * Returns target address for this Connection object * @returns Target address @@ -79,4 +91,48 @@ export class Connection { public get targetAddress(): string { return this._targetAddress; } + + /** + * "HTTP-like" fetch via gRPC protocol + * @returns Promise with peer's response + */ + public fetch(options: FetchOptions = {}): Promise { + if (!this._peerClient) { + return Promise.reject(new Error(`Peer client not attached for ${this.targetAddress}`)); + } + + // Build a request object + const method = (options.method ?? 'POST').toUpperCase(); + const path = options.path ?? '/'; + const headers: ConnectionHeaders = { ...(options.headers ?? {}) }; + + let bodyBuf: Buffer = Buffer.alloc(0); + const b = options.body; + if (b != null) { + if (Buffer.isBuffer(b)) bodyBuf = b; + else if (b instanceof Uint8Array) bodyBuf = Buffer.from(b); + else if (typeof b === 'string') bodyBuf = Buffer.from(b, 'utf8'); + else return Promise.reject(new Error('Body must be a string/Buffer/Uint8Array')); + } + + const req = { method, path, headers, body: bodyBuf }; + + // Return promise with response + return new Promise((resolve, reject) => { + this._peerClient.Fetch(req, (err: any, resp: any) => { + if (err) return reject(err); + + const resBody = resp?.body ? Buffer.from(resp.body) : Buffer.alloc(0); + const fetchResponse: FetchResponse = { + status: Number(resp?.status ?? 200), + headers: resp?.headers ?? {}, + body: resBody, + text: async () => resBody.toString('utf8'), + json: async () => JSON.parse(resBody.toString('utf8')), + }; + + resolve(fetchResponse); + }); + }); + } } diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts index 7b7a9a224..fe0949052 100644 --- a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts @@ -115,7 +115,7 @@ export class ConnectionManager { * @param token Optional token for connection */ createNewConnection(address: string, direction: ConnectionDirection, token?: string) { - const conn = new Connection(token ?? '', address, direction); + const conn = new Connection(token ?? '', address, direction, this._peerCtor); if (direction === ConnectionDirection.RECEIVING) { this._receivingConnections.set(address, conn); diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index c4974badd..3126a02d4 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -16,7 +16,7 @@ import { ConnectionManager } from './connectionManager/ConnectionManager'; import { RevokeTokenHandler } from './commands/revokeToken/revokeToken.handler'; import { ConnectionDirection, DuplexMessageEvent } from '../models/message.model'; import { NewTokenHandler } from './commands/newToken/newToken.handler'; -import type { Connection } from './Connection/Connection'; +import type { Connection } from './connection/Connection'; /** * @description Wrapper class for managing secure gRPC wrapper. @@ -66,7 +66,7 @@ export class gRPCWrapper { * Starts the Connection Manager stream connection with Central System */ public async connectToClient(address: string, token?: string): Promise { - return this._connectionManager.createNewConnection(address, ConnectionDirection.SENDING, token || ''); + return this._connectionManager.createNewConnection(address, ConnectionDirection.SENDING, token ?? ''); } /** diff --git a/Tokenization/backend/wrapper/src/models/message.model.ts b/Tokenization/backend/wrapper/src/models/message.model.ts index 918f48505..0eb67ec1e 100644 --- a/Tokenization/backend/wrapper/src/models/message.model.ts +++ b/Tokenization/backend/wrapper/src/models/message.model.ts @@ -23,7 +23,6 @@ * @property MESSAGE_EVENT_REVOKE_TOKEN: Event for revoking an existing token. */ export enum DuplexMessageEvent { - MESSAGE_EVENT_UNSPECIFIED = 'MESSAGE_EVENT_UNSPECIFIED', MESSAGE_EVENT_EMPTY = 'MESSAGE_EVENT_EMPTY', MESSAGE_EVENT_NEW_TOKEN = 'MESSAGE_EVENT_NEW_TOKEN', MESSAGE_EVENT_REVOKE_TOKEN = 'MESSAGE_EVENT_REVOKE_TOKEN', diff --git a/Tokenization/backend/wrapper/src/test/central/CentralSystemWrapper.test.ts b/Tokenization/backend/wrapper/src/test/central/CentralSystemWrapper.test.ts index 4c7476cbb..ee65ad072 100644 --- a/Tokenization/backend/wrapper/src/test/central/CentralSystemWrapper.test.ts +++ b/Tokenization/backend/wrapper/src/test/central/CentralSystemWrapper.test.ts @@ -71,11 +71,10 @@ describe('CentralSystemWrapper', () => { }); test('should set up gRPC service and add it to the server', () => { - const testWrapper = new CentralSystemWrapper('dummy.proto', 12345); expect(grpc.Server).toHaveBeenCalled(); expect(grpc.loadPackageDefinition).toHaveBeenCalled(); expect(grpc.ServerCredentials.createInsecure).not.toHaveBeenCalled(); - expect(testWrapper).toBeDefined(); + expect(wrapper).toBeDefined(); }); test('should call listen and bind the server', () => { diff --git a/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts b/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts index 176bb19af..fd5c23215 100644 --- a/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts @@ -18,6 +18,9 @@ import { Connection } from '../../../client/connection/Connection'; import { ConnectionManager } from '../../../client/connectionManager/ConnectionManager'; import { Command } from 'models/commands.model'; import { ConnectionDirection, DuplexMessageEvent } from '../../../models/message.model'; +import * as grpc from '@grpc/grpc-js'; +import * as protoLoader from '@grpc/proto-loader'; +import path from 'path'; /** * Helper to create a new token command with given address, direction, and token. @@ -36,7 +39,7 @@ function createEventMessage(targetAddress: string, connectionDirection: Connecti describe('NewTokenHandler', () => { let manager: ConnectionManager; - const protoPath = path.join(__dirname, '..', '..', '..', 'proto', 'wrapper.proto'); + const protoPath = path.join(__dirname, '..', '..', '..', '..', '..', 'proto', 'wrapper.proto'); const packageDef = protoLoader.loadSync(protoPath, { keepCase: true, longs: String, @@ -62,7 +65,7 @@ describe('NewTokenHandler', () => { return undefined; }), createNewConnection: jest.fn(function (this: any, address: string, dir: ConnectionDirection, token: string) { - const conn = new Connection(token, address, dir); + const conn = new Connection(token, address, dir, peerCtor); if (dir === ConnectionDirection.SENDING) { this.sendingConnections.set(address, conn); } else { @@ -75,7 +78,7 @@ describe('NewTokenHandler', () => { it('should update token on existing SENDING connection', async () => { const targetAddress = 'peer-123'; - const conn = new Connection('old-token', targetAddress, ConnectionDirection.SENDING); + const conn = new Connection('old-token', targetAddress, ConnectionDirection.SENDING, peerCtor); (manager as any).sendingConnections.set(targetAddress, conn); const handler = new NewTokenHandler(manager); diff --git a/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts b/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts index b7d68ea5e..3764d1724 100644 --- a/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts @@ -19,8 +19,24 @@ import { ConnectionManager } from '../../../client/connectionManager/ConnectionM import { ConnectionDirection, DuplexMessageEvent } from '../../../models/message.model'; import { ConnectionStatus } from '../../../models/connection.model'; import { Command } from 'models/commands.model'; +import * as grpc from '@grpc/grpc-js'; +import * as protoLoader from '@grpc/proto-loader'; +import path from 'path'; describe('RevokeToken', () => { + const protoPath = path.join(__dirname, '..', '..', '..', '..', '..', 'proto', 'wrapper.proto'); + const packageDef = protoLoader.loadSync(protoPath, { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + }); + + const proto = grpc.loadPackageDefinition(packageDef) as any; + const wrapper = proto.webui.tokenization; + const peerCtor = wrapper.Peer2Peer; + function createEventMessage(targetAddress: string) { return { event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, @@ -45,7 +61,7 @@ describe('RevokeToken', () => { it('should revoke token when connection found in sendingConnections', async () => { const targetAddress = 'peer-123'; - const conn = new Connection('valid-token', targetAddress, ConnectionDirection.SENDING); + const conn = new Connection('valid-token', targetAddress, ConnectionDirection.SENDING, peerCtor); (manager as any).sendingConnections!.set(targetAddress, conn); const handler = new RevokeTokenHandler(manager); @@ -59,7 +75,7 @@ describe('RevokeToken', () => { it('should revoke token when connection found in receivingConnections', async () => { const targetAddress = 'peer-456'; - const conn = new Connection('valid-token', targetAddress, ConnectionDirection.RECEIVING); + const conn = new Connection('valid-token', targetAddress, ConnectionDirection.RECEIVING, peerCtor); (manager as any).receivingConnections.set(targetAddress, conn); const handler = new RevokeTokenHandler(manager); diff --git a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts deleted file mode 100644 index d68c39a47..000000000 --- a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test..ts +++ /dev/null @@ -1,152 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -import * as grpc from '@grpc/grpc-js'; -import { ConnectionManager } from '../../../client/connectionManager/ConnectionManager'; -import { DuplexMessageEvent } from '../../../models/message.model'; - -// Mock duplex stream -const mockStream = { - on: jest.fn(), - end: jest.fn(), -}; - -// Mock gRPC client -const mockClient = { - ClientStream: jest.fn(() => mockStream), -}; - -// Mock CentralSystem constructor -const CentralSystemMock = jest.fn(() => mockClient); - -// Mock dispatcher -const mockDispatch = jest.fn(); -jest.mock('../../../client/ConnectionManager/EventManagement/CentralCommandDispatcher', () => ({ - CentralCommandDispatcher: jest.fn(() => ({ - dispatch: mockDispatch, - })), -})); - -// Mock logger -jest.mock( - '@aliceo2/web-ui', - () => ({ - LogManager: { - getLogger: () => ({ - infoMessage: jest.fn(), - }), - }, - }), - { virtual: true } -); - -// Mock gRPC proto loader and client -jest.mock('@grpc/proto-loader', () => ({ - loadSync: jest.fn(() => { - return {}; - }), -})); - -jest.mock('@grpc/grpc-js', () => { - const original = jest.requireActual('@grpc/grpc-js'); - return { - ...original, - credentials: { - createInsecure: jest.fn(), - }, - loadPackageDefinition: jest.fn(() => ({ - webui: { - tokenization: { - CentralSystem: CentralSystemMock, - }, - }, - })), - }; -}); - -describe('ConnectionManager', () => { - let conn: ConnectionManager; - - beforeEach(() => { - jest.clearAllMocks(); - conn = new ConnectionManager('dummy.proto', 'localhost:12345'); - }); - - test('should initialize client with correct address', () => { - const connManager = new ConnectionManager('dummy.proto', 'localhost:12345'); - expect(connManager).toBeDefined(); - expect(grpc.loadPackageDefinition).toHaveBeenCalled(); - expect(CentralSystemMock).toHaveBeenCalledWith('localhost:12345', undefined); - }); - - test('connectToCentralSystem() should set up stream listeners', () => { - const connManager = new ConnectionManager('dummy.proto', 'localhost:12345'); - connManager.connectToCentralSystem(); - - expect(mockClient.ClientStream).toHaveBeenCalled(); - expect(mockStream.on).toHaveBeenCalledWith('data', expect.any(Function)); - expect(mockStream.on).toHaveBeenCalledWith('end', expect.any(Function)); - expect(mockStream.on).toHaveBeenCalledWith('error', expect.any(Function)); - }); - - test('disconnectFromCentralSystem() should end stream', () => { - conn.connectToCentralSystem(); - conn.disconnectFromCentralSystem(); - - expect(mockStream.end).toHaveBeenCalled(); - }); - - test("should reconnect on stream 'end'", () => { - jest.useFakeTimers(); - conn.connectToCentralSystem(); - const onEnd = mockStream.on.mock.calls.find(([event]) => event === 'end')?.[1]; - - onEnd?.(); // simulate 'end' - jest.advanceTimersByTime(2000); - - expect(mockClient.ClientStream).toHaveBeenCalledTimes(2); - jest.useRealTimers(); - }); - - test("should reconnect on stream 'error'", () => { - jest.useFakeTimers(); - conn.connectToCentralSystem(); - const onError = mockStream.on.mock.calls.find(([event]) => event === 'error')?.[1]; - - onError?.(new Error('Simulated error')); - jest.advanceTimersByTime(2000); - - expect(mockClient.ClientStream).toHaveBeenCalledTimes(2); - jest.useRealTimers(); - }); - - test("should dispatch event when 'data' is received", () => { - conn.connectToCentralSystem(); - const onData = mockStream.on.mock.calls.find(([event]) => event === 'data')?.[1]; - - const mockMessage = { - event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, - data: { - revokeToken: { - token: 'abc123', - targetAddress: 'peer-123', - }, - }, - }; - - onData?.(mockMessage); - - expect(mockDispatch).toHaveBeenCalledWith(mockMessage); - }); -}); diff --git a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts index 5397e99fd..9d365f287 100644 --- a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts @@ -12,96 +12,64 @@ * or submit itself to any jurisdiction. */ -import * as grpc from "@grpc/grpc-js"; -import { ConnectionManager } from "../../../client/ConnectionManager/ConnectionManager"; -import { - ConnectionDirection, - DuplexMessageEvent, -} from "../../../models/message.model"; - -// Mock duplex stream -const mockStream = { - on: jest.fn(), - end: jest.fn(), -}; - -// Mock gRPC client -const mockClient = { - ClientStream: jest.fn(() => mockStream), -}; - -// Mock CentralSystem constructor -const CentralSystemMock = jest.fn(() => mockClient); - -// Mock dispatcher -const mockDispatch = jest.fn(); -jest.mock( - "../../../client/ConnectionManager/EventManagement/CentralCommandDispatcher", - () => ({ - CentralCommandDispatcher: jest.fn(() => ({ - dispatch: mockDispatch, - register: jest.fn(), - })), - }) -); +import * as grpc from '@grpc/grpc-js'; -// Mock logger -jest.mock( - "@aliceo2/web-ui", - () => ({ - LogManager: { - getLogger: () => ({ - infoMessage: jest.fn(), - debugMessage: jest.fn(), - errorMessage: jest.fn(), - }), - }, - }), - { virtual: true } -); +// Capture service impl registered on grpc.Server.addService +let capturedServerImpl: any | null = null; -// Mock gRPC proto loader and client -jest.mock("@grpc/proto-loader", () => ({ - loadSync: jest.fn(() => { - return {}; - }), +// Mock proto-loader +jest.mock('@grpc/proto-loader', () => ({ + loadSync: jest.fn(() => ({})), })); -let capturedServerImpl: any | null = null; +// CentralSystem client mock returned from loadPackageDefinition +const CentralSystemClientMock = jest.fn(); -jest.mock("@grpc/grpc-js", () => { - const original = jest.requireActual("@grpc/grpc-js"); - const Peer2PeerMock: any = jest.fn(() => ({ - Fetch: jest.fn(), - })); - // simulation of the service definition - Peer2PeerMock.service = { - Fetch: { - path: "/webui.tokenization.Peer2Peer/Fetch", - requestStream: false, - responseStream: false, - requestSerialize: (x: any) => x, - requestDeserialize: (x: any) => x, - responseSerialize: (x: any) => x, - responseDeserialize: (x: any) => x, - }, - }; +// Minimal Peer2Peer client ctor placeholder (only to pass into Connection) +const Peer2PeerCtorMock = jest.fn(); - // Mock server +// Mock @grpc/grpc-js +jest.mock('@grpc/grpc-js', () => { + const original = jest.requireActual('@grpc/grpc-js'); + + // Fake Server with hooks we can assert on const mockServer = { addService: jest.fn((_svc: any, impl: any) => { capturedServerImpl = impl; }), - bindAsync: jest.fn((_addr: any, _creds: any, cb: any) => cb(null)), + bindAsync: jest.fn((_addr: string, _creds: any, cb: any) => cb(null)), forceShutdown: jest.fn(), }; - - const mockServerCtor = jest.fn(() => mockServer); + const ServerCtor = jest.fn(() => mockServer); + + // loadPackageDefinition returns namespaced wrappers (CentralSystem + Peer2Peer) + const loadPackageDefinition = jest.fn(() => ({ + webui: { + tokenization: { + CentralSystem: CentralSystemClientMock, + Peer2Peer: Object.assign(Peer2PeerCtorMock, { + // Simulated service definition used by addService + service: { + Fetch: { + path: '/webui.tokenization.Peer2Peer/Fetch', + requestStream: false, + responseStream: false, + requestSerialize: (x: any) => x, + requestDeserialize: (x: any) => x, + responseSerialize: (x: any) => x, + responseDeserialize: (x: any) => x, + }, + }, + }), + }, + }, + })); return { ...original, + loadPackageDefinition, credentials: { - createInsecure: jest.fn(), + createInsecure: jest.fn(() => ({})), }, ServerCredentials: { createInsecure: jest.fn(() => ({})), @@ -110,26 +78,91 @@ jest.mock("@grpc/grpc-js", () => { ...original.status, INTERNAL: 13, }, - loadPackageDefinition: jest.fn(() => ({ - webui: { - tokenization: { - CentralSystem: CentralSystemMock, - Peer2Peer: Peer2PeerMock, - }, - }, - })), - Server: mockServerCtor, + Server: ServerCtor, }; }); -describe("ConnectionManager", () => { - let conn: ConnectionManager; +// Mock CentralCommandDispatcher +const dispatcherRegisterMock = jest.fn(); +jest.mock( + '../../../client/connectionManager/eventManagement/CentralCommandDispatcher', + () => ({ + CentralCommandDispatcher: jest.fn().mockImplementation(() => ({ + register: dispatcherRegisterMock, + })), + }), + { virtual: true } +); + +// Mock CentralConnection +const centralStartMock = jest.fn(); +const centralDisconnectMock = jest.fn(); +jest.mock( + '../../../client/connectionManager/CentralConnection', + () => ({ + CentralConnection: jest.fn().mockImplementation(() => ({ + start: centralStartMock, + disconnect: centralDisconnectMock, + })), + }), + { virtual: true } +); + +// Track Connection instances & allow status changes +type FakeConnInit = { token: string; address: string; direction: any; peerCtor: any }; +const createdConnections: any[] = []; +const connectionCtorMock = jest.fn().mockImplementation(function (this: any, token: string, address: string, direction: any, peerCtor: any) { + this._token = token; + this._address = address; + this.direction = direction; + this.status = undefined; + this.targetAddress = address; + this.token = token; + // keep basic API similar enough for the code under test + Object.defineProperty(this, 'status', { + get: () => this._status, + set: (v) => (this._status = v), + configurable: true, + }); + createdConnections.push({ token, address, direction, peerCtor, instance: this }); +}); +jest.mock( + '../../../client/connection/Connection', + () => ({ + Connection: connectionCtorMock, + }), + { virtual: true } +); + +// Mock logger +const infoMessageMock = jest.fn(); +const errorMessageMock = jest.fn(); +jest.mock( + '@aliceo2/web-ui', + () => ({ + LogManager: { + getLogger: () => ({ + infoMessage: infoMessageMock, + errorMessage: errorMessageMock, + debugMessage: jest.fn(), + }), + }, + }), + { virtual: true } +); + +// Now import the SUT (after mocks) +import { ConnectionManager } from '../../../client/connectionManager/ConnectionManager'; +import { ConnectionDirection } from '../../../models/message.model'; +import { ConnectionStatus } from '../../../models/connection.model'; +describe('ConnectionManager', () => { beforeEach(() => { jest.clearAllMocks(); capturedServerImpl = null; + createdConnections.length = 0; + // @ts-ignore global.fetch = jest.fn(); - conn = new ConnectionManager("dummy.proto", "localhost:12345"); }); afterAll(() => { @@ -137,181 +170,246 @@ describe("ConnectionManager", () => { delete global.fetch; }); - test("should initialize client with correct address", () => { - expect(conn).toBeDefined(); - expect(grpc.loadPackageDefinition).toHaveBeenCalled(); - expect(CentralSystemMock).toHaveBeenCalledWith( - "localhost:12345", - undefined - ); + test('constructor: loads proto, builds wrapper/peerCtor and CentralSystem client', () => { + const cm = new ConnectionManager('proto/file.proto', 'central:5555'); + expect(cm).toBeDefined(); + + // grpc.loadPackageDefinition called with proto-loader result + expect((grpc as any).loadPackageDefinition).toHaveBeenCalled(); + // CentralSystem client created with address + insecure creds + expect(CentralSystemClientMock).toHaveBeenCalledWith('central:5555', expect.any(Object)); + expect(grpc.credentials.createInsecure).toHaveBeenCalled(); }); - test("connectToCentralSystem() should set up stream listeners", () => { - conn.connectToCentralSystem(); + test('registerCommandHandlers: calls dispatcher.register for each item', () => { + const cm = new ConnectionManager('p.proto', 'c:1'); + dispatcherRegisterMock.mockClear(); + + const handlers = [ + { event: 1 as any, handler: { handle: jest.fn() } as any }, + { event: 2 as any, handler: { handle: jest.fn() } as any }, + ]; + + cm.registerCommandHandlers(handlers); - expect(mockClient.ClientStream).toHaveBeenCalled(); - expect(mockStream.on).toHaveBeenCalledWith("data", expect.any(Function)); - expect(mockStream.on).toHaveBeenCalledWith("end", expect.any(Function)); - expect(mockStream.on).toHaveBeenCalledWith("error", expect.any(Function)); + expect(dispatcherRegisterMock).toHaveBeenCalledTimes(2); + expect(dispatcherRegisterMock).toHaveBeenCalledWith(handlers[0].event, handlers[0].handler); + expect(dispatcherRegisterMock).toHaveBeenCalledWith(handlers[1].event, handlers[1].handler); }); - test("disconnectFromCentralSystem() should end stream", () => { - conn.connectToCentralSystem(); - conn.disconnectFromCentralSystem(); + test('connectToCentralSystem/disconnectFromCentralSystem delegate to CentralConnection', () => { + const cm = new ConnectionManager('p.proto', 'c:1'); + // @ts-ignore + cm['_peerCtor'] = Peer2PeerCtorMock; + cm.connectToCentralSystem(); + expect(centralStartMock).toHaveBeenCalled(); - expect(mockStream.end).toHaveBeenCalled(); + cm.disconnectFromCentralSystem(); + expect(centralDisconnectMock).toHaveBeenCalled(); }); - test("should reconnect on stream 'end'", () => { - jest.useFakeTimers(); - conn.connectToCentralSystem(); - const onEnd = mockStream.on.mock.calls.find( - ([event]) => event === "end" - )?.[1]; + test('createNewConnection: adds to sending map, sets CONNECTED, logs', () => { + const cm = new ConnectionManager('p.proto', 'c:1'); + // @ts-ignore + cm['_peerCtor'] = Peer2PeerCtorMock; + const conn = cm.createNewConnection('peer-A', ConnectionDirection.SENDING, 'tok123'); + + // Constructed with provided token, address, direction, uses peerCtor from wrapper + expect(connectionCtorMock).toHaveBeenCalledWith('tok123', 'peer-A', ConnectionDirection.SENDING, expect.any(Function)); + expect(conn.status).toBe(ConnectionStatus.CONNECTED); - onEnd?.(); // simulate 'end' - jest.advanceTimersByTime(2000); + // Exposed via connections getter + const { sending, receiving } = cm.connections; + expect(sending.length).toBe(1); + expect(receiving.length).toBe(0); - expect(mockClient.ClientStream).toHaveBeenCalledTimes(2); - jest.useRealTimers(); + // Log called + expect(infoMessageMock).toHaveBeenCalledWith(expect.stringContaining('Connection with peer-A has been estabilished')); }); - test("should reconnect on stream 'error'", () => { - jest.useFakeTimers(); - conn.connectToCentralSystem(); - const onError = mockStream.on.mock.calls.find( - ([event]) => event === "error" - )?.[1]; - - onError?.(new Error("Simulated error")); - jest.advanceTimersByTime(2000); + test('createNewConnection: adds to receiving map if direction is RECEIVING', () => { + const cm = new ConnectionManager('p.proto', 'c:1'); + cm.createNewConnection('peer-B', ConnectionDirection.RECEIVING); - expect(mockClient.ClientStream).toHaveBeenCalledTimes(2); - jest.useRealTimers(); + const { sending, receiving } = cm.connections; + expect(sending.length).toBe(0); + expect(receiving.length).toBe(1); }); - test("should dispatch event when 'data' is received", () => { - conn.connectToCentralSystem(); - const onData = mockStream.on.mock.calls.find( - ([event]) => event === "data" - )?.[1]; - - const mockMessage = { - event: DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN, - data: { - revokeToken: { - token: "abc123", - targetAddress: "peer-123", - }, - }, - }; + test('getConnectionByAddress: returns by direction; logs on invalid direction', () => { + const cm = new ConnectionManager('p.proto', 'c:1'); + const s = cm.createNewConnection('s-1', ConnectionDirection.SENDING); + const r = cm.createNewConnection('r-1', ConnectionDirection.RECEIVING); + + expect(cm.getConnectionByAddress('s-1', ConnectionDirection.SENDING)).toBe(s); + expect(cm.getConnectionByAddress('r-1', ConnectionDirection.RECEIVING)).toBe(r); - onData?.(mockMessage); + errorMessageMock.mockClear(); + const invalid = cm.getConnectionByAddress('x', 999 as any); + expect(invalid).toBeUndefined(); + expect(errorMessageMock).toHaveBeenCalledWith('Invalid connection direction: 999'); + }); + + test('connections getter: returns arrays (copies) of maps', () => { + const cm = new ConnectionManager('p.proto', 'c:1'); + cm.createNewConnection('a', ConnectionDirection.SENDING); + cm.createNewConnection('b', ConnectionDirection.RECEIVING); - expect(mockDispatch).toHaveBeenCalledWith(mockMessage); + const { sending, receiving } = cm.connections; + expect(Array.isArray(sending)).toBe(true); + expect(Array.isArray(receiving)).toBe(true); + expect(sending.length).toBe(1); + expect(receiving.length).toBe(1); }); - test("listenForPeers() should start server and register service", async () => { - await conn.listenForPeers(50055, "http://localhost:40041/api/"); + test('listenForPeers: creates server, registers service, binds & logs', async () => { + const cm = new ConnectionManager('p.proto', 'c:1'); + await cm.listenForPeers(50099, 'http://localhost:41000/api/'); - const serverCtor = (grpc.Server as any).mock; - expect(serverCtor).toBeDefined(); - expect(serverCtor.calls.length).toBeGreaterThan(0); + const ServerCtor = (grpc.Server as any).mock; + expect(ServerCtor).toBeDefined(); + expect(ServerCtor.calls.length).toBeGreaterThan(0); - const serverInstance = serverCtor.results[0].value; + const serverInstance = ServerCtor.results[0].value; expect(serverInstance.addService).toHaveBeenCalled(); - expect(serverInstance.bindAsync).toHaveBeenCalledWith( - "localhost:50055", - expect.anything(), - expect.any(Function) - ); + expect(serverInstance.bindAsync).toHaveBeenCalledWith('localhost:50099', expect.anything(), expect.any(Function)); + expect(infoMessageMock).toHaveBeenCalledWith('Peer server listening on localhost:50099'); - // implementacja metody Fetch została przechwycona + // Service impl captured expect(capturedServerImpl).toBeTruthy(); - expect(typeof capturedServerImpl.Fetch).toBe("function"); + expect(typeof capturedServerImpl.Fetch).toBe('function'); }); - test("p2p Fetch should register incoming receiving connection and forward request", async () => { - await conn.listenForPeers(50056, "http://localhost:40041/api/"); + test('listenForPeers: calling twice shuts previous server down', async () => { + const cm = new ConnectionManager('p.proto', 'c:1'); + await cm.listenForPeers(50100, 'http://localhost:41000/api/'); + const firstServer = (grpc.Server as any).mock.results[0].value; - // przygotuj dane wywołania + await cm.listenForPeers(50101, 'http://localhost:41000/api/'); + expect(firstServer.forceShutdown).toHaveBeenCalled(); + }); + + test('p2p Fetch: registers new incoming receiving connection, forwards to local API, maps response', async () => { + const cm = new ConnectionManager('p.proto', 'c:1'); + await cm.listenForPeers(50102, 'http://local/api/'); + + // Prepare incoming call and callback const call = { - getPeer: () => "client-42", + getPeer: () => 'client-42', request: { - method: "POST", - path: "echo", - headers: { "content-type": "application/json" }, + method: 'post', + path: 'echo', + headers: { 'content-type': 'application/json' }, body: Buffer.from(JSON.stringify({ ping: true })), }, } as any; - const callback = jest.fn(); - // @ts-ignore - mock global.fetch response + // Mock fetch response + // @ts-ignore global.fetch.mockResolvedValue({ status: 202, headers: { forEach: (fn: (v: string, k: string) => void) => { - fn("application/json", "content-type"); - fn("test", "x-extra"); + fn('application/json', 'content-type'); + fn('abc', 'x-extra'); }, }, arrayBuffer: async () => Buffer.from(JSON.stringify({ ok: 1 })), }); - const before = conn.getAllConnections().receiving.length; + const before = cm.connections.receiving.length; await capturedServerImpl.Fetch(call, callback); - expect(global.fetch).toHaveBeenCalledWith( - "http://localhost:40041/api/echo", - { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ ping: true }), - } - ); + // Forwarded correctly (note body converted to string) + expect(global.fetch).toHaveBeenCalledWith('http://local/api/echo', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ ping: true }), + }); - // callback with response from forwarded fetch + // Response mapped back to gRPC expect(callback).toHaveBeenCalledWith(null, { status: 202, - headers: { "content-type": "application/json", "x-extra": "test" }, + headers: { 'content-type': 'application/json', 'x-extra': 'abc' }, body: expect.any(Buffer), }); - // connection receiving should be registered - const after = conn.getAllConnections().receiving.length; + // Receiving connection was created & stored + const after = cm.connections.receiving.length; expect(after).toBeGreaterThan(before); - const rec = conn.getConnectionByAddress( - "client-42", - ConnectionDirection.RECEIVING - ); - expect(rec).toBeDefined(); + const found = cm.getConnectionByAddress('client-42', ConnectionDirection.RECEIVING); + expect(found).toBeDefined(); + expect(infoMessageMock).toHaveBeenCalledWith(expect.stringContaining('Incoming request from client-42')); + expect(infoMessageMock).toHaveBeenCalledWith(expect.stringContaining('New incoming connection registered for: client-42')); }); - test("p2p Fetch should return INTERNAL on forward error", async () => { - await conn.listenForPeers(50057, "http://localhost:40041/api/"); + test('p2p Fetch: uses existing receiving connection when present (no duplicate creation)', async () => { + const cm = new ConnectionManager('p.proto', 'c:1'); + await cm.listenForPeers(50103, 'http://local/api/'); + + // Pre-create receiving connection + cm.createNewConnection('client-77', ConnectionDirection.RECEIVING); + + // @ts-ignore + global.fetch.mockResolvedValue({ + status: 200, + headers: { forEach: (fn: any) => fn('text/plain', 'content-type') }, + arrayBuffer: async () => Buffer.from('ok'), + }); const call = { - getPeer: () => "client-error", - request: { - method: "GET", - path: "fail", - headers: {}, - }, + getPeer: () => 'client-77', + request: { method: 'get', path: 'pong', headers: {}, body: undefined }, } as any; const callback = jest.fn(); + const before = cm.connections.receiving.length; + await capturedServerImpl.Fetch(call, callback); + + // No new receiving connection added + const after = cm.connections.receiving.length; + expect(after).toBe(before); + + // Forwarded with GET and no body + expect(global.fetch).toHaveBeenCalledWith('http://local/api/pong', { + method: 'GET', + headers: {}, + body: undefined, + }); + + expect(callback).toHaveBeenCalledWith( + null, + expect.objectContaining({ + status: 200, + headers: { 'content-type': 'text/plain' }, + body: expect.any(Buffer), + }) + ); + }); + + test('p2p Fetch: on forward error returns INTERNAL and logs error', async () => { + const cm = new ConnectionManager('p.proto', 'c:1'); + await cm.listenForPeers(50104, 'http://local/api/'); + // @ts-ignore - global.fetch.mockRejectedValue(new Error("err")); + global.fetch.mockRejectedValue(new Error('boom')); + + const call = { + getPeer: () => 'err-client', + request: { method: 'get', path: 'fail', headers: {} }, + } as any; + const callback = jest.fn(); await capturedServerImpl.Fetch(call, callback); + expect(errorMessageMock).toHaveBeenCalledWith(expect.stringContaining('Error forwarding request')); expect(callback).toHaveBeenCalledWith( expect.objectContaining({ code: grpc.status.INTERNAL, - message: "err", + message: 'boom', }) ); }); diff --git a/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts b/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts new file mode 100644 index 000000000..8937e9d5e --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts @@ -0,0 +1,211 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { Connection } from '../../client/connection/Connection'; +import { ConnectionStatus } from '../../models/connection.model'; + +const FAKE_DIRECTION: any = 'SENDING'; + +let lastPeerClient: any; +const PeerCtorMock = jest.fn((_addr: string, _creds: any) => { + lastPeerClient = { + Fetch: jest.fn(), + }; + return lastPeerClient; +}); + +jest.mock( + '@grpc/grpc-js', + () => { + const original = jest.requireActual('@grpc/grpc-js'); + return { + ...original, + credentials: { + createInsecure: jest.fn(() => ({ insecure: true })), + }, + }; + }, + { virtual: true } +); + +import * as grpc from '@grpc/grpc-js'; + +describe('Connection', () => { + beforeEach(() => { + jest.clearAllMocks(); + lastPeerClient = undefined; + }); + + test('constructor should create connection and set base state correctly', () => { + const conn = new Connection('tok', 'peer:50051', FAKE_DIRECTION, PeerCtorMock); + + expect(grpc.credentials.createInsecure).toHaveBeenCalled(); + expect(PeerCtorMock).toHaveBeenCalledWith('peer:50051', { insecure: true }); + + expect(conn.token).toBe('tok'); + expect(conn.targetAddress).toBe('peer:50051'); + expect(conn.status).toBe(ConnectionStatus.CONNECTED); + expect(conn.direction).toBe(FAKE_DIRECTION); + }); + + test('getter/setter for token should work', () => { + const conn = new Connection('old', 'peer:1', FAKE_DIRECTION, PeerCtorMock); + expect(conn.token).toBe('old'); + conn.token = 'new-token'; + expect(conn.token).toBe('new-token'); + }); + + test('handleRevokeToken should clear token and status to UNAUTHORIZED', () => { + const conn = new Connection('secret', 'peer:x', FAKE_DIRECTION, PeerCtorMock); + conn.handleRevokeToken(); + expect(conn.token).toBe(''); + expect(conn.status).toBe(ConnectionStatus.UNAUTHORIZED); + }); + + test('getter/setter for status should work', () => { + const conn = new Connection('t', 'a', FAKE_DIRECTION, PeerCtorMock); + conn.status = ConnectionStatus.UNAUTHORIZED; + expect(conn.status).toBe(ConnectionStatus.UNAUTHORIZED); + conn.status = ConnectionStatus.CONNECTED; + expect(conn.status).toBe(ConnectionStatus.CONNECTED); + }); + + test('getter for targetAddress should work', () => { + const conn = new Connection('t', 'host:1234', FAKE_DIRECTION, PeerCtorMock); + expect(conn.targetAddress).toBe('host:1234'); + }); + + test('fetch should throw if peer client is not attached', async () => { + const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock); + // @ts-ignore + conn['_peerClient'] = undefined; + + await expect(conn.fetch()).rejects.toThrow('Peer client not attached for addr'); + }); + + test('fetch with defaults should work', async () => { + const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock); + + lastPeerClient.Fetch.mockImplementation((req: any, cb: any) => { + try { + expect(req).toEqual({ + method: 'POST', + path: '/', + headers: {}, + body: Buffer.alloc(0), + }); + cb(null, { status: 200, headers: {}, body: Buffer.alloc(0) }); + } catch (e) { + cb(e); + } + }); + + const resp = await conn.fetch(); + expect(resp.status).toBe(200); + }); + + test('fetch builds request correctly and returns response', async () => { + const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock); + const body = Buffer.from('abc'); + + lastPeerClient.Fetch.mockImplementation((req: any, cb: any) => { + try { + expect(req.method).toBe('PUT'); + expect(req.path).toBe('/api/a'); + expect(req.headers).toEqual({ 'x-a': '1' }); + expect(Buffer.isBuffer(req.body)).toBe(true); + expect(req.body.equals(body)).toBe(true); + cb(null, { + status: 201, + headers: { 'content-type': 'text/plain' }, + body: Buffer.from('ok'), + }); + } catch (e) { + cb(e); + } + }); + + const res = await conn.fetch({ method: 'put', path: '/api/a', headers: { 'x-a': '1' }, body }); + expect(res.status).toBe(201); + expect(await res.text()).toBe('ok'); + }); + + test('fetch should convert Uint8Array to Buffer', async () => { + const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock); + const body = new Uint8Array([1, 2, 3]); + + lastPeerClient.Fetch.mockImplementation((req: any, cb: any) => { + try { + expect(Buffer.isBuffer(req.body)).toBe(true); + expect(req.body.equals(Buffer.from([1, 2, 3]))).toBe(true); + cb(null, { status: 200, headers: {}, body: Buffer.alloc(0) }); + } catch (e) { + cb(e); + } + }); + + const res = await conn.fetch({ body }); + expect(res.status).toBe(200); + }); + + test('fetch should convert string to Buffer', async () => { + const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock); + const body = 'żółć & äöü'; // handling special chars + + lastPeerClient.Fetch.mockImplementation((req: any, cb: any) => { + try { + expect(req.body.equals(Buffer.from(body, 'utf8'))).toBe(true); + cb(null, { status: 200, headers: {}, body: Buffer.from('{"ok":true}') }); + } catch (e) { + cb(e); + } + }); + + const res = await conn.fetch({ method: 'post', path: '/p', headers: {}, body }); + expect(await res.json()).toEqual({ ok: true }); + }); + + test('fetch should reject if body is not allowed', async () => { + const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock); + // @ts-ignore + await expect(conn.fetch({ body: { not: 'allowed' } })).rejects.toThrow('Body must be a string/Buffer/Uint8Array'); + }); + + test('fetch should propagate errors from peer', async () => { + const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock); + const err = new Error('err'); + lastPeerClient.Fetch.mockImplementation((_req: any, cb: any) => cb(err)); + + await expect(conn.fetch({ method: 'GET', path: '/x' })).rejects.toThrow('err'); + }); + + test('fetch should map response', async () => { + const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock); + + const payload = { a: 1, b: 'x' }; + lastPeerClient.Fetch.mockImplementation((_req: any, cb: any) => + cb(null, { + headers: { 'x-k': 'v' }, + body: Buffer.from(JSON.stringify(payload)), + }) + ); + + const res = await conn.fetch({ method: 'GET' }); + expect(res.status).toBe(200); + expect(res.headers).toEqual({ 'x-k': 'v' }); + expect(Buffer.isBuffer(res.body)).toBe(true); + expect(await res.text()).toBe(JSON.stringify(payload)); + expect(await res.json()).toEqual(payload); + }); +}); From 866f59925857e6dfa2ae35078164b3bcbcb0a006 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Wed, 12 Nov 2025 17:51:31 +0100 Subject: [PATCH 085/103] fix: rebuild package lock --- .../backend/wrapper/package-lock.json | 225 +++++++++++++----- 1 file changed, 170 insertions(+), 55 deletions(-) diff --git a/Tokenization/backend/wrapper/package-lock.json b/Tokenization/backend/wrapper/package-lock.json index bee0eac41..d879ebae8 100644 --- a/Tokenization/backend/wrapper/package-lock.json +++ b/Tokenization/backend/wrapper/package-lock.json @@ -1479,13 +1479,6 @@ "@types/node": "*" } }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -1540,43 +1533,6 @@ "undici-types": "~7.8.0" } }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", - "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -1872,6 +1828,19 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -2587,7 +2556,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2804,6 +2772,12 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3105,6 +3079,15 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -3155,6 +3138,48 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3324,6 +3349,24 @@ "dev": true, "license": "ISC" }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4951,6 +4994,30 @@ "dev": true, "license": "MIT" }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5098,6 +5165,15 @@ "dev": true, "license": "MIT" }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5136,12 +5212,13 @@ "license": "MIT" }, "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "license": "MIT", - "engines": { - "node": ">=16" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/path-type": { @@ -5286,6 +5363,19 @@ "node": ">=12.0.0" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5369,18 +5459,34 @@ } }, "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", - "iconv-lite": "0.6.3", + "iconv-lite": "0.7.0", "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/react-is": { @@ -5974,6 +6080,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", From 3d832ba838e152dc1f932064ef6efaf48de71d2a Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 13 Nov 2025 17:05:32 +0100 Subject: [PATCH 086/103] fix: fix rebase --- .github/workflows/grpc-wrapper.yml | 33 ------------------- .../webapp/app/routes/configuration.tsx | 18 ---------- 2 files changed, 51 deletions(-) delete mode 100644 .github/workflows/grpc-wrapper.yml diff --git a/.github/workflows/grpc-wrapper.yml b/.github/workflows/grpc-wrapper.yml deleted file mode 100644 index 8b27019e2..000000000 --- a/.github/workflows/grpc-wrapper.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Grpc Wrapper - -on: - pull_request: - branches: ["**"] - paths: - - "Tokenization/backend/wrapper/**" - - ".github/workflows/grpc-wrapper.yml" - -concurrency: - group: wrapper-${{ github.ref }} - cancel-in-progress: true - -jobs: - test: - runs-on: ubuntu-latest - - defaults: - run: - working-directory: Tokenization/backend/wrapper - - steps: - - uses: actions/checkout@v4 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: "22.x" - - - name: Install dependencies - run: npm ci - - - name: Run Jest - run: npm run test diff --git a/Configuration/webapp/app/routes/configuration.tsx b/Configuration/webapp/app/routes/configuration.tsx index fc6270166..de774b133 100644 --- a/Configuration/webapp/app/routes/configuration.tsx +++ b/Configuration/webapp/app/routes/configuration.tsx @@ -12,23 +12,6 @@ * or submit itself to any jurisdiction. */ -<<<<<<<< HEAD:Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.command.ts -import type { Command } from '../../../models/commands.model'; -import type { TokenMessage } from '../../../models/message.model'; -import { DuplexMessageEvent } from '../../../models/message.model'; - -/** - * @description Command used to trigger token revocation for a specific connection. Handles structure logic. - */ -export class RevokeTokenCommand implements Command { - readonly event = DuplexMessageEvent.MESSAGE_EVENT_REVOKE_TOKEN; - /** - * Constructor for RevokeTokenCommand. - * @param {TokenMessage} payload - TokenMessage containing the address and direction of the connection to be revoked. - */ - constructor(public payload: TokenMessage) {} -} -======== const ConfigurationPage = () => (

Configuration Details

@@ -36,4 +19,3 @@ const ConfigurationPage = () => ( ); export default ConfigurationPage; ->>>>>>>> c705cef4faadf986bbca795b92f5e0d2297eec89:Configuration/webapp/app/routes/configuration.tsx From f0e464307e5562393dd6078ed65b9d6208aaf384 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 13 Nov 2025 17:17:28 +0100 Subject: [PATCH 087/103] fix: tests, remove express, fix comments in gRPCWrapper --- Tokenization/backend/proto/wrapper.proto | 3 - .../backend/wrapper/package-lock.json | 979 +----------------- Tokenization/backend/wrapper/package.json | 1 - .../backend/wrapper/src/client/gRPCWrapper.ts | 17 +- 4 files changed, 24 insertions(+), 976 deletions(-) diff --git a/Tokenization/backend/proto/wrapper.proto b/Tokenization/backend/proto/wrapper.proto index 93c1ca852..116131b80 100644 --- a/Tokenization/backend/proto/wrapper.proto +++ b/Tokenization/backend/proto/wrapper.proto @@ -88,9 +88,6 @@ message HttpLikeResponse { // ====================================== enum MessageEvent { - // Unspecified event type - MESSAGE_EVENT_UNSPECIFIED = 0; - // Default value, represents an empty event MESSAGE_EVENT_EMPTY = 0; diff --git a/Tokenization/backend/wrapper/package-lock.json b/Tokenization/backend/wrapper/package-lock.json index eb0c337b7..c0ecc6551 100644 --- a/Tokenization/backend/wrapper/package-lock.json +++ b/Tokenization/backend/wrapper/package-lock.json @@ -10,7 +10,6 @@ "dependencies": { "@grpc/grpc-js": "^1.13.4", "@grpc/proto-loader": "^0.7.15", - "express": "^5.1.0", "ts-node": "^10.9.2", "typescript": "^5.8.3" }, @@ -753,210 +752,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@es-joy/jsdoccomment": { - "version": "0.76.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.76.0.tgz", - "integrity": "sha512-g+RihtzFgGTx2WYCuTHbdOXJeAlGnROws0TeALx9ow/ZmOROOZkVg5wp/B44n0WJgI4SQFP1eWM2iRPlU2Y14w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.8", - "@typescript-eslint/types": "^8.46.0", - "comment-parser": "1.4.1", - "esquery": "^1.6.0", - "jsdoc-type-pratt-parser": "~6.10.0" - }, - "engines": { - "node": ">=20.11.0" - } - }, - "node_modules/@es-joy/resolve.exports": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@es-joy/resolve.exports/-/resolve.exports-1.2.0.tgz", - "integrity": "sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@eslint/js": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", - "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@grpc/grpc-js": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz", @@ -2032,19 +1827,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -2329,26 +2111,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2436,44 +2198,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2667,27 +2391,6 @@ "dev": true, "license": "MIT" }, - "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2695,24 +2398,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -2760,6 +2445,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2805,15 +2491,6 @@ "node": ">=0.10.0" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2856,26 +2533,6 @@ "node": ">=8" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -2918,15 +2575,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2937,36 +2585,6 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2976,12 +2594,6 @@ "node": ">=6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3283,15 +2895,6 @@ "node": ">=0.10.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -3342,48 +2945,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3501,23 +3062,6 @@ "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -3553,24 +3097,6 @@ "dev": true, "license": "ISC" }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3582,6 +3108,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3606,30 +3133,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -3640,19 +3143,6 @@ "node": ">=8.0.0" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -3737,24 +3227,12 @@ "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", + }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/graceful-fs": { @@ -3781,22 +3259,11 @@ "node": ">=8" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3829,31 +3296,6 @@ "dev": true, "license": "MIT" }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -3864,18 +3306,6 @@ "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==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3959,17 +3389,9 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, "license": "ISC" }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -4058,12 +3480,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -5013,36 +4429,6 @@ "tmpl": "1.0.5" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -5074,27 +4460,6 @@ "node": ">=8.6" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.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", @@ -5122,6 +4487,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT" }, "node_modules/mylas": { @@ -5145,15 +4511,6 @@ "dev": true, "license": "MIT" }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -5198,34 +4555,11 @@ "dev": true, "license": "MIT" }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -5369,15 +4703,6 @@ "dev": true, "license": "MIT" }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5415,16 +4740,6 @@ "dev": true, "license": "MIT" }, - "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -5567,19 +4882,6 @@ "node": ">=12.0.0" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5607,21 +4909,6 @@ ], "license": "MIT" }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/queue-lit": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", @@ -5653,46 +4940,6 @@ ], "license": "MIT" }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -5810,22 +5057,6 @@ "node": ">=0.10.0" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5850,32 +5081,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -5886,49 +5091,6 @@ "semver": "bin/semver.js" } }, - "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5952,78 +5114,6 @@ "node": ">=8" } }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -6124,15 +5214,6 @@ "node": ">=8" } }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -6284,15 +5365,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -6486,20 +5558,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -6543,15 +5601,6 @@ "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "license": "MIT" }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -6614,15 +5663,6 @@ "node": ">=10.12.0" } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -6680,6 +5720,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { diff --git a/Tokenization/backend/wrapper/package.json b/Tokenization/backend/wrapper/package.json index 0dafb1237..000944e17 100644 --- a/Tokenization/backend/wrapper/package.json +++ b/Tokenization/backend/wrapper/package.json @@ -26,7 +26,6 @@ "dependencies": { "@grpc/grpc-js": "^1.13.4", "@grpc/proto-loader": "^0.7.15", - "express": "^5.1.0", "ts-node": "^10.9.2", "typescript": "^5.8.3" } diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index 3126a02d4..0622afe48 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -56,21 +56,32 @@ export class gRPCWrapper { } /** - * Starts the Connection Manager stream connection with Central System + * Connects to the central system using the underlying ConnectionManager. + * + * @remarks + * This method starts the duplex stream connection with the central gRPC server. */ public connectToCentralSystem() { this._connectionManager.connectToCentralSystem(); } /** - * Starts the Connection Manager stream connection with Central System + * Establishes a new connection to a target client. + * + * @param address - The target address of the client. + * @param token - Optional authentication token for the connection. + * + * @returns A promise that resolves to the newly created connection ready to use for fetching data. */ public async connectToClient(address: string, token?: string): Promise { return this._connectionManager.createNewConnection(address, ConnectionDirection.SENDING, token ?? ''); } /** - * Starts the Connection Manager stream connection with Central System + * Starts a listener server for p2p connections. + * @param port The port number to bind the p2p server to. + * @param baseAPIPath Optional base API path to forward requests to e.g. '/api'. + * @returns A promise that resolves when the p2p listener server is started. */ public async listenForPeers(port: number, baseAPIPath?: string): Promise { return this._connectionManager.listenForPeers(port, baseAPIPath); From baa9511ef6cb0f91e17f7d74f5af0cc51a8331f0 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 13 Nov 2025 17:22:31 +0100 Subject: [PATCH 088/103] fix: tests --- .../ConnectionManager.test.ts | 25 +++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts index 9d365f287..dff46c2c6 100644 --- a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts @@ -17,22 +17,17 @@ import * as grpc from '@grpc/grpc-js'; // Capture service impl registered on grpc.Server.addService let capturedServerImpl: any | null = null; -// Mock proto-loader jest.mock('@grpc/proto-loader', () => ({ loadSync: jest.fn(() => ({})), })); -// CentralSystem client mock returned from loadPackageDefinition const CentralSystemClientMock = jest.fn(); - -// Minimal Peer2Peer client ctor placeholder (only to pass into Connection) const Peer2PeerCtorMock = jest.fn(); // Mock @grpc/grpc-js jest.mock('@grpc/grpc-js', () => { const original = jest.requireActual('@grpc/grpc-js'); - // Fake Server with hooks we can assert on const mockServer = { addService: jest.fn((_svc: any, impl: any) => { capturedServerImpl = impl; @@ -42,13 +37,11 @@ jest.mock('@grpc/grpc-js', () => { }; const ServerCtor = jest.fn(() => mockServer); - // loadPackageDefinition returns namespaced wrappers (CentralSystem + Peer2Peer) const loadPackageDefinition = jest.fn(() => ({ webui: { tokenization: { CentralSystem: CentralSystemClientMock, Peer2Peer: Object.assign(Peer2PeerCtorMock, { - // Simulated service definition used by addService service: { Fetch: { path: '/webui.tokenization.Peer2Peer/Fetch', @@ -108,8 +101,7 @@ jest.mock( { virtual: true } ); -// Track Connection instances & allow status changes -type FakeConnInit = { token: string; address: string; direction: any; peerCtor: any }; +// Track Connection instances and allow status changes const createdConnections: any[] = []; const connectionCtorMock = jest.fn().mockImplementation(function (this: any, token: string, address: string, direction: any, peerCtor: any) { this._token = token; @@ -118,7 +110,6 @@ const connectionCtorMock = jest.fn().mockImplementation(function (this: any, tok this.status = undefined; this.targetAddress = address; this.token = token; - // keep basic API similar enough for the code under test Object.defineProperty(this, 'status', { get: () => this._status, set: (v) => (this._status = v), @@ -134,7 +125,6 @@ jest.mock( { virtual: true } ); -// Mock logger const infoMessageMock = jest.fn(); const errorMessageMock = jest.fn(); jest.mock( @@ -151,7 +141,6 @@ jest.mock( { virtual: true } ); -// Now import the SUT (after mocks) import { ConnectionManager } from '../../../client/connectionManager/ConnectionManager'; import { ConnectionDirection } from '../../../models/message.model'; import { ConnectionStatus } from '../../../models/connection.model'; @@ -174,9 +163,7 @@ describe('ConnectionManager', () => { const cm = new ConnectionManager('proto/file.proto', 'central:5555'); expect(cm).toBeDefined(); - // grpc.loadPackageDefinition called with proto-loader result expect((grpc as any).loadPackageDefinition).toHaveBeenCalled(); - // CentralSystem client created with address + insecure creds expect(CentralSystemClientMock).toHaveBeenCalledWith('central:5555', expect.any(Object)); expect(grpc.credentials.createInsecure).toHaveBeenCalled(); }); @@ -214,7 +201,6 @@ describe('ConnectionManager', () => { cm['_peerCtor'] = Peer2PeerCtorMock; const conn = cm.createNewConnection('peer-A', ConnectionDirection.SENDING, 'tok123'); - // Constructed with provided token, address, direction, uses peerCtor from wrapper expect(connectionCtorMock).toHaveBeenCalledWith('tok123', 'peer-A', ConnectionDirection.SENDING, expect.any(Function)); expect(conn.status).toBe(ConnectionStatus.CONNECTED); @@ -223,7 +209,6 @@ describe('ConnectionManager', () => { expect(sending.length).toBe(1); expect(receiving.length).toBe(0); - // Log called expect(infoMessageMock).toHaveBeenCalledWith(expect.stringContaining('Connection with peer-A has been estabilished')); }); @@ -236,7 +221,7 @@ describe('ConnectionManager', () => { expect(receiving.length).toBe(1); }); - test('getConnectionByAddress: returns by direction; logs on invalid direction', () => { + test('getConnectionByAddress: returns by direction. Logs on invalid direction', () => { const cm = new ConnectionManager('p.proto', 'c:1'); const s = cm.createNewConnection('s-1', ConnectionDirection.SENDING); const r = cm.createNewConnection('r-1', ConnectionDirection.RECEIVING); @@ -321,7 +306,6 @@ describe('ConnectionManager', () => { const before = cm.connections.receiving.length; await capturedServerImpl.Fetch(call, callback); - // Forwarded correctly (note body converted to string) expect(global.fetch).toHaveBeenCalledWith('http://local/api/echo', { method: 'POST', headers: { 'content-type': 'application/json' }, @@ -349,7 +333,6 @@ describe('ConnectionManager', () => { const cm = new ConnectionManager('p.proto', 'c:1'); await cm.listenForPeers(50103, 'http://local/api/'); - // Pre-create receiving connection cm.createNewConnection('client-77', ConnectionDirection.RECEIVING); // @ts-ignore @@ -395,7 +378,7 @@ describe('ConnectionManager', () => { await cm.listenForPeers(50104, 'http://local/api/'); // @ts-ignore - global.fetch.mockRejectedValue(new Error('boom')); + global.fetch.mockRejectedValue(new Error('err')); const call = { getPeer: () => 'err-client', @@ -409,7 +392,7 @@ describe('ConnectionManager', () => { expect(callback).toHaveBeenCalledWith( expect.objectContaining({ code: grpc.status.INTERNAL, - message: 'boom', + message: 'err', }) ); }); From 6d658fb9e449066e71154b983f663d02221b42b4 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 13 Nov 2025 18:54:30 +0100 Subject: [PATCH 089/103] feat: move peer listening to separate method and util file --- .../connectionManager/ConnectionManager.ts | 49 +---------- .../src/utils/connection/peerListener.ts | 82 +++++++++++++++++++ 2 files changed, 85 insertions(+), 46 deletions(-) create mode 100644 Tokenization/backend/wrapper/src/utils/connection/peerListener.ts diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts index fe0949052..53b91db11 100644 --- a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts @@ -22,6 +22,7 @@ import type { Command, CommandHandler } from 'models/commands.model'; import type { DuplexMessageEvent } from '../../models/message.model'; import { ConnectionDirection } from '../../models/message.model'; import { ConnectionStatus } from '../../models/connection.model'; +import { peerListener } from '../../utils/connection/peerListener'; /** * @description Manages all the connection between clients and central system. @@ -170,52 +171,8 @@ export class ConnectionManager { this._peerServer = new grpc.Server(); this._peerServer.addService(this._wrapper.Peer2Peer.service, { - Fetch: async (call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => { - try { - const clientAddress = call.getPeer(); - this._logger.infoMessage(`Incoming request from ${clientAddress}`); - - let conn: Connection | undefined = this._receivingConnections.get(clientAddress); - - if (!conn) { - conn = new Connection('', clientAddress, ConnectionDirection.RECEIVING, this._peerCtor); - conn.status = ConnectionStatus.CONNECTED; - this._receivingConnections.set(clientAddress, conn); - this._logger.infoMessage(`New incoming connection registered for: ${clientAddress}`); - } - - // Create request to forward to local API endpoint - const method = String(call.request?.method ?? 'POST').toUpperCase(); - const url = this._baseAPIPath + (call.request?.path ?? ''); - const headers: { [key: string]: string } = call.request?.headers; - const body = call.request?.body ? Buffer.from(call.request.body).toString('utf-8') : undefined; - - this._logger.infoMessage(`Received payload from ${clientAddress}: \n${url}\n${JSON.stringify(headers)}\n${JSON.stringify(body)}\n`); - - const httpResp = await fetch(url, { - method, - headers: headers, - body, - }); - - const respHeaders: Record = {}; - httpResp.headers.forEach((v, k) => (respHeaders[k] = v)); - const resBody = Buffer.from(await httpResp.arrayBuffer()); - - callback(null, { - status: httpResp.status, - headers: respHeaders, - body: resBody, - }); - } catch (e: any) { - this._logger.errorMessage(`Error forwarding request: ${e ?? 'Uknown error'}`); - - callback({ - code: grpc.status.INTERNAL, - message: e?.message ?? 'forward error', - } as any); - } - }, + Fetch: async (call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => + peerListener(call, callback, this._logger, this._receivingConnections, this._peerCtor, this._baseAPIPath), }); await new Promise((resolve, reject) => { diff --git a/Tokenization/backend/wrapper/src/utils/connection/peerListener.ts b/Tokenization/backend/wrapper/src/utils/connection/peerListener.ts new file mode 100644 index 000000000..5fafb4f1d --- /dev/null +++ b/Tokenization/backend/wrapper/src/utils/connection/peerListener.ts @@ -0,0 +1,82 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ +import * as grpc from '@grpc/grpc-js'; +import { Connection } from '../../client/connection/Connection'; +import { ConnectionDirection } from '../../models/message.model'; +import { ConnectionStatus } from '../../models/connection.model'; + +/** + * Listens for incoming gRPC requests and forwards them to the local API endpoint. + * Creates a new incoming connection if one doesn't exist yet. + * + * @param call - The gRPC unary call object containing the request. + * @param callback - The callback function to be called with the response. + * @param logger - The logger object to write info and error messages. + * @param receivingConnections - The map of existing incoming connections. + * @param peerCtor - The constructor function for the peer client. + * @param baseAPIPath - The base path of the local API endpoint. + */ +export const peerListener = async ( + call: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData, + logger: any, + receivingConnections: Map, + peerCtor: any, + baseAPIPath: string +) => { + try { + const clientAddress = call.getPeer(); + logger.infoMessage(`Incoming request from ${clientAddress}`); + + let conn: Connection | undefined = receivingConnections.get(clientAddress); + + if (!conn) { + conn = new Connection('', clientAddress, ConnectionDirection.RECEIVING, peerCtor); + conn.status = ConnectionStatus.CONNECTED; + receivingConnections.set(clientAddress, conn); + logger.infoMessage(`New incoming connection registered for: ${clientAddress}`); + } + + // Create request to forward to local API endpoint + const method = String(call.request?.method ?? 'POST').toUpperCase(); + const url = baseAPIPath + (call.request?.path ?? ''); + const headers: { [key: string]: string } = call.request?.headers; + const body = call.request?.body ? Buffer.from(call.request.body).toString('utf-8') : undefined; + + logger.infoMessage(`Received payload from ${clientAddress}: \n${url}\n${JSON.stringify(headers)}\n${JSON.stringify(body)}\n`); + + const httpResp = await fetch(url, { + method, + headers: headers, + body, + }); + + const respHeaders: Record = {}; + httpResp.headers.forEach((v, k) => (respHeaders[k] = v)); + const resBody = Buffer.from(await httpResp.arrayBuffer()); + + callback(null, { + status: httpResp.status, + headers: respHeaders, + body: resBody, + }); + } catch (e: any) { + logger.errorMessage(`Error forwarding request: ${e ?? 'Uknown error'}`); + + callback({ + code: grpc.status.INTERNAL, + message: e?.message ?? 'forward error', + } as any); + } +}; From 16a2ec64c89179fe82e035af2a8ac7c394005bb5 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 13 Nov 2025 20:28:34 +0100 Subject: [PATCH 090/103] fix: remove additional description --- .../wrapper/src/client/connectionManager/ConnectionManager.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts index 53b91db11..fdbd20263 100644 --- a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts @@ -24,9 +24,6 @@ import { ConnectionDirection } from '../../models/message.model'; import { ConnectionStatus } from '../../models/connection.model'; import { peerListener } from '../../utils/connection/peerListener'; -/** - * @description Manages all the connection between clients and central system. - */ /** * Manages the lifecycle and connection logic for a gRPC client communicating with the central system. * From 3bbc6273a84d2c8ab666b3c85071e0f213d78975 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Tue, 18 Nov 2025 11:42:19 +0100 Subject: [PATCH 091/103] feat: rebase, write tests for reconnection schaduler and fix it --- .../connectionManager/CentralConnection.ts | 21 ++- .../connection/reconnectionScheduler.test.ts | 123 ++++++++++++++++++ .../{ => connection}/reconnectionScheduler.ts | 42 +++--- 3 files changed, 147 insertions(+), 39 deletions(-) create mode 100644 Tokenization/backend/wrapper/src/test/utils/connection/reconnectionScheduler.test.ts rename Tokenization/backend/wrapper/src/utils/{ => connection}/reconnectionScheduler.ts (63%) diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts b/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts index 2d9907de6..88f6bfb15 100644 --- a/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts +++ b/Tokenization/backend/wrapper/src/client/connectionManager/CentralConnection.ts @@ -16,6 +16,7 @@ import type * as grpc from '@grpc/grpc-js'; import { LogManager } from '@aliceo2/web-ui'; import type { CentralCommandDispatcher } from './eventManagement/CentralCommandDispatcher'; import type { DuplexMessageModel } from '../../models/message.model'; +import { ReconnectionScheduler } from '../../utils/connection/reconnectionScheduler'; /** * This class manages the duplex stream with the CentralSystem gRPC service. @@ -24,6 +25,11 @@ import type { DuplexMessageModel } from '../../models/message.model'; export class CentralConnection { private _logger = LogManager.getLogger('CentralConnection'); private _stream?: grpc.ClientDuplexStream; + private _reconnectionScheduler: ReconnectionScheduler = new ReconnectionScheduler( + () => this.connect(), + { initialDelay: 1000, maxDelay: 30000 }, + this._logger + ); /** * Constructor for the CentralConnection class. @@ -44,32 +50,23 @@ export class CentralConnection { this._stream?.on('data', (payload: DuplexMessageModel) => { this._logger.debugMessage(`Received payload: ${JSON.stringify(payload)}`); + this._reconnectionScheduler.reset(); this._dispatcher.dispatch(payload); }); this._stream?.on('end', () => { this._logger.infoMessage(`Stream ended, attempting to reconnect...`); this._stream = undefined; - this.scheduleReconnect(); + this._reconnectionScheduler.schedule(); }); this._stream?.on('error', (err: any) => { this._logger.infoMessage('Stream error:', err, ' attempting to reconnect...'); this._stream = undefined; - this.scheduleReconnect(); + this._reconnectionScheduler.schedule(); }); } - /** - * Schedules a reconnect with exponential backoff. - */ - private scheduleReconnect() { - setTimeout(() => { - this._logger.infoMessage(`Trying to reconnect...`); - this.connect(); - }, 2000); - } - /** * Starts the connection to the central system. */ diff --git a/Tokenization/backend/wrapper/src/test/utils/connection/reconnectionScheduler.test.ts b/Tokenization/backend/wrapper/src/test/utils/connection/reconnectionScheduler.test.ts new file mode 100644 index 000000000..8107267cd --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/utils/connection/reconnectionScheduler.test.ts @@ -0,0 +1,123 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { ReconnectionScheduler } from '../../../utils/connection/reconnectionScheduler'; + +describe('ReconnectionScheduler', () => { + let reconnectCallback: jest.Mock; + let logger: { infoMessage: jest.Mock; errorMessage?: jest.Mock }; + let scheduler: ReconnectionScheduler; + + beforeEach(() => { + jest.useFakeTimers(); + reconnectCallback = jest.fn(); + logger = { + infoMessage: jest.fn(), + errorMessage: jest.fn(), + }; + + scheduler = new ReconnectionScheduler( + reconnectCallback, + { + initialDelay: 1000, + maxDelay: 8000, + }, + logger as any + ); + }); + + afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); + }); + + test("schedule's first attempt should schedule and call reconnectCallback", () => { + scheduler.schedule(); + + expect(logger.infoMessage).toHaveBeenCalledWith('Recconection attempt #1: Sleep for 2000 ms.'); + + expect(reconnectCallback).not.toHaveBeenCalled(); + jest.advanceTimersByTime(1999); + expect(reconnectCallback).not.toHaveBeenCalled(); + jest.advanceTimersByTime(1); + expect(reconnectCallback).toHaveBeenCalledTimes(1); + }); + + test('Schedule attempts should be exponential', () => { + scheduler.schedule(); + jest.advanceTimersByTime(2000); + + scheduler.schedule(); + expect(logger.infoMessage).toHaveBeenLastCalledWith('Recconection attempt #2: Sleep for 4000 ms.'); + jest.advanceTimersByTime(4000); + expect(reconnectCallback).toHaveBeenCalledTimes(2); + }); + + test("schedule's delay should be limited by maxDelay", () => { + scheduler = new ReconnectionScheduler( + reconnectCallback, + { + initialDelay: 1000, + maxDelay: 3000, + }, + logger as any + ); + + scheduler.schedule(); + expect(logger.infoMessage).toHaveBeenLastCalledWith('Recconection attempt #1: Sleep for 2000 ms.'); + jest.advanceTimersByTime(2000); + + scheduler.schedule(); + expect(logger.infoMessage).toHaveBeenLastCalledWith('Recconection attempt #2: Sleep for 3000 ms.'); + jest.advanceTimersByTime(3000); + + scheduler.schedule(); + expect(logger.infoMessage).toHaveBeenLastCalledWith('Recconection attempt #3: Sleep for 3000 ms.'); + }); + + test('schedule() should not schedule again if it is scheduled', () => { + scheduler.schedule(); + scheduler.schedule(); + + expect(logger.infoMessage).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(100000); + expect(reconnectCallback).toHaveBeenCalledTimes(1); + }); + + test('reset() should clear timer, reset attemptCount and currentDelay', () => { + scheduler.schedule(); + + jest.advanceTimersByTime(500); + expect(reconnectCallback).not.toHaveBeenCalled(); + + scheduler.reset(); + + jest.advanceTimersByTime(100000); + expect(reconnectCallback).not.toHaveBeenCalled(); + + scheduler.schedule(); + expect(logger.infoMessage).toHaveBeenLastCalledWith('Recconection attempt #1: Sleep for 2000 ms.'); + }); + + test('reset() should ignore another reset due to isResseting variable', () => { + scheduler.schedule(); + scheduler.reset(); + scheduler.reset(); + scheduler.schedule(); + + jest.advanceTimersByTime(2000); + expect(reconnectCallback).toHaveBeenCalledTimes(1); + }); +}); diff --git a/Tokenization/backend/wrapper/src/utils/reconnectionScheduler.ts b/Tokenization/backend/wrapper/src/utils/connection/reconnectionScheduler.ts similarity index 63% rename from Tokenization/backend/wrapper/src/utils/reconnectionScheduler.ts rename to Tokenization/backend/wrapper/src/utils/connection/reconnectionScheduler.ts index d4f0f2504..4b03d434d 100644 --- a/Tokenization/backend/wrapper/src/utils/reconnectionScheduler.ts +++ b/Tokenization/backend/wrapper/src/utils/connection/reconnectionScheduler.ts @@ -18,7 +18,7 @@ export interface ReconnectionOptions { } /** - * @description Schedules reconnection attempts with exponential backoff. + * A scheduler that manages reconnection attempts with an exponential backoff. */ export class ReconnectionScheduler { private reconnectCallback: any; @@ -33,17 +33,15 @@ export class ReconnectionScheduler { private isScheduling: boolean = false; /** - * @param reconnectCallback Function to call for reconnection attempt - * @param options Configuration options for reconnection scheduling + * Creates a new instance of the ReconnectionScheduler. + * @param {any} reconnectCallback - The callback to be called when a reconnection attempt is scheduled. + * @param {ReconnectionOptions} [options] - Options for the reconnection schedule. + * @param {Logger} logger - The logger instance to be used for logging messages. */ - constructor( - reconnectCallback: any, - options: ReconnectionOptions = {}, - logger: Logger - ) { + constructor(reconnectCallback: any, options: ReconnectionOptions = {}, logger: Logger) { this.reconnectCallback = reconnectCallback; - this.initialDelay = options.initialDelay || 1000; - this.maxDelay = options.maxDelay || 30000; + this.initialDelay = options.initialDelay ?? 1000; + this.maxDelay = options.maxDelay ?? 30000; this.currentDelay = this.initialDelay; this.attemptCount = 0; @@ -53,7 +51,7 @@ export class ReconnectionScheduler { } /** - * @description Schedules the next reconnection attempt using exponential backoff. + * Schedules the next reconnection attempt using exponential backoff. */ schedule() { if (this.isScheduling) return; @@ -61,18 +59,14 @@ export class ReconnectionScheduler { this.isResetting = false; this.attemptCount++; - // exponential backoff calculation - let delay = this.initialDelay * Math.pow(2, this.attemptCount); + // Exponential backoff calculation + const delay = this.initialDelay * Math.pow(2, this.attemptCount); this.currentDelay = Math.min(this.maxDelay, delay); - this.logger.infoMessage( - `Recconection attempt #${ - this.attemptCount - }: Sleep for ${this.currentDelay.toFixed(0)} ms.` - ); + this.logger.infoMessage(`Recconection attempt #${this.attemptCount}: Sleep for ${this.currentDelay.toFixed(0)} ms.`); - // plan the reconnection attempt + // Plan the reconnection attempt this.timeoutId = setTimeout(() => { this.isScheduling = false; this.reconnectCallback(); @@ -80,21 +74,15 @@ export class ReconnectionScheduler { } /** - * @description Resets the scheduler to its initial state. + * Resets the scheduler to its initial state. */ reset() { if (this.isResetting) return; + this.isScheduling = false; this.isResetting = true; clearTimeout(this.timeoutId); this.attemptCount = 0; this.currentDelay = this.initialDelay; } - - /** - * @description stops the reconnection attempts. - */ - stop() { - clearTimeout(this.timeoutId); - } } From 3960bb19d033f3f8c2c3ef79651a82bf6b5521ab Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 20 Nov 2025 13:49:15 +0100 Subject: [PATCH 092/103] fix: rebase and fix rebase and unit tests --- .../src/central/CentralSystemWrapper.ts | 2 +- .../commands/newToken/newToken.handler.ts | 2 +- .../revokeToken/revokeToken.handler.ts | 2 +- .../src/client/connection/Connection.ts | 26 ++++++- .../connectionManager/ConnectionManager.ts | 67 ++++++++++++++++--- .../backend/wrapper/src/client/gRPCWrapper.ts | 13 ++-- .../src/test/client/commands/newToken.test.ts | 2 +- .../test/client/commands/revokeToken.test.ts | 7 +- .../ConnectionManager.test.ts | 49 +++++++------- .../src/test/connection/Connection.test.ts | 31 ++++----- .../src/utils/connection/peerListener.ts | 8 +-- 11 files changed, 141 insertions(+), 68 deletions(-) diff --git a/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts b/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts index 4466ae7fc..f120048ec 100644 --- a/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts +++ b/Tokenization/backend/wrapper/src/central/CentralSystemWrapper.ts @@ -128,7 +128,7 @@ export class CentralSystemWrapper { // Handle stream error event call.on('error', (err) => { - this._logger.infoMessage(`Stream error from client ${clientIp}:`, err); + this._logger.errorMessage(`Stream error from client ${clientIp}:`, err); this.cleanupClient(peer); }); } diff --git a/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts b/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts index 4fb7eb54b..9765bbadd 100644 --- a/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts +++ b/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts @@ -14,7 +14,7 @@ import type { CommandHandler } from '../../../models/commands.model'; import type { NewTokenCommand } from './newToken.command'; -import type { ConnectionManager } from '../../connectionManager/ConnectionManager'; +import type { ConnectionManager } from '../../ConnectionManager/ConnectionManager'; import { ConnectionDirection } from '../../../models/message.model'; /** diff --git a/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts b/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts index 2fa1a9640..bb806b2f2 100644 --- a/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts +++ b/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts @@ -14,7 +14,7 @@ import type { CommandHandler } from '../../../models/commands.model'; import type { RevokeTokenCommand } from './revokeToken.command'; -import type { ConnectionManager } from '../../connectionManager/ConnectionManager'; +import type { ConnectionManager } from '../../ConnectionManager/ConnectionManager'; /** * RevokeTokenHandler is responsible for handling the RevokeTokenCommand. diff --git a/Tokenization/backend/wrapper/src/client/connection/Connection.ts b/Tokenization/backend/wrapper/src/client/connection/Connection.ts index 3f27c31b5..38b09b19e 100644 --- a/Tokenization/backend/wrapper/src/client/connection/Connection.ts +++ b/Tokenization/backend/wrapper/src/client/connection/Connection.ts @@ -33,13 +33,35 @@ export class Connection { * @param token - The authentication token for the connection. * @param targetAddress - The unique address of the target client. * @param direction - The direction of the connection (e.g., sending or receiving). + * @param peerCtor - The constructor for the gRPC client to be used for communication. + * @param caCertPath - Path to the CA certificate file. + * @param clientCertPath - Path to the client certificate file. + * @param clientKeyPath - Path to the client key file. */ - constructor(token: string, targetAddress: string, direction: ConnectionDirection, peerCtor: any) { + constructor( + token: string, + targetAddress: string, + direction: ConnectionDirection, + peerCtor: any, + private readonly connectionCerts: { + caCert: NonSharedBuffer; + clientCert: NonSharedBuffer; + clientKey: NonSharedBuffer; + } + ) { this._token = token; this._targetAddress = targetAddress; - this._peerClient = new peerCtor(targetAddress, grpc.credentials.createInsecure()); this.direction = direction; + if (!connectionCerts.caCert || !connectionCerts.clientCert || !connectionCerts.clientKey) { + throw new Error('Connection certificates are required to create a Connection.'); + } + + // Create grpc credentials + const sslCreds = grpc.credentials.createSsl(this.connectionCerts.caCert, this.connectionCerts.clientKey, this.connectionCerts.clientCert); + + this._peerClient = new peerCtor(targetAddress, sslCreds); + this._status = ConnectionStatus.CONNECTED; } diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts index fdbd20263..665c3796f 100644 --- a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts @@ -14,15 +14,16 @@ import * as grpc from '@grpc/grpc-js'; import * as protoLoader from '@grpc/proto-loader'; +import * as fs from 'fs'; import { CentralConnection } from './CentralConnection'; import { CentralCommandDispatcher } from './eventManagement/CentralCommandDispatcher'; import { Connection } from '../connection/Connection'; import { LogManager } from '@aliceo2/web-ui'; -import type { Command, CommandHandler } from 'models/commands.model'; -import type { DuplexMessageEvent } from '../../models/message.model'; import { ConnectionDirection } from '../../models/message.model'; import { ConnectionStatus } from '../../models/connection.model'; import { peerListener } from '../../utils/connection/peerListener'; +import type { Command, CommandHandler } from 'models/commands.model'; +import type { DuplexMessageEvent } from '../../models/message.model'; /** * Manages the lifecycle and connection logic for a gRPC client communicating with the central system. @@ -49,6 +50,11 @@ export class ConnectionManager { private _peerServer: grpc.Server | undefined; private _baseAPIPath: string = ''; + // Client certificates + private _caCert: NonSharedBuffer; + private _clientCert: NonSharedBuffer; + private _clientKey: NonSharedBuffer; + /** * Initializes a new instance of the ConnectionManager class. * @@ -56,8 +62,11 @@ export class ConnectionManager { * * @param protoPath - The file path to the gRPC proto definition. * @param centralAddress - The address of the central gRPC server (default: "localhost:50051"). + * @param caCertPath - Path to the CA certificate file. + * @param clientCertPath - Path to the client certificate file. + * @param clientKeyPath - Path to the client key file. */ - constructor(protoPath: string, centralAddress: string = 'localhost:50051') { + constructor(protoPath: string, centralAddress: string = 'localhost:50051', caCertPath: string, clientCertPath: string, clientKeyPath: string) { const packageDef = protoLoader.loadSync(protoPath, { keepCase: true, longs: String, @@ -70,11 +79,18 @@ export class ConnectionManager { this._wrapper = proto.webui.tokenization; this._peerCtor = this._wrapper.Peer2Peer; - const client = new this._wrapper.CentralSystem(centralAddress, grpc.credentials.createInsecure()); + // Read certs + this._caCert = fs.readFileSync(caCertPath); + this._clientCert = fs.readFileSync(clientCertPath); + this._clientKey = fs.readFileSync(clientKeyPath); + + // Create grpc credentials + const sslCreds = grpc.credentials.createSsl(this._caCert, this._clientKey, this._clientCert); + const centralClient = new this._wrapper.CentralSystem(centralAddress, sslCreds); // Event dispatcher for central system events this._centralDispatcher = new CentralCommandDispatcher(); - this._centralConnection = new CentralConnection(client, this._centralDispatcher, centralAddress); + this._centralConnection = new CentralConnection(centralClient, this._centralDispatcher, centralAddress); } /** @@ -112,8 +128,28 @@ export class ConnectionManager { * @param direction Direction of connection * @param token Optional token for connection */ - createNewConnection(address: string, direction: ConnectionDirection, token?: string) { - const conn = new Connection(token ?? '', address, direction, this._peerCtor); + public async createNewConnection(address: string, direction: ConnectionDirection, token?: string) { + let conn: Connection | undefined; + + // Checks if connection already exists + conn = direction === ConnectionDirection.RECEIVING ? this._receivingConnections.get(address) : this._sendingConnections.get(address); + + // Return existing connection if found + if (conn) { + if (token) { + conn.token = token; + } + return conn; + } + + // Create new connection + conn = new Connection(token ?? '', address, direction, this._peerCtor, { + caCert: this._caCert, + clientCert: this._clientCert, + clientKey: this._clientKey, + }); + + conn.status = ConnectionStatus.CONNECTING; if (direction === ConnectionDirection.RECEIVING) { this._receivingConnections.set(address, conn); @@ -158,7 +194,7 @@ export class ConnectionManager { } /** Starts a listener server for p2p connections */ - public async listenForPeers(port: number, baseAPIPath?: string): Promise { + public async listenForPeers(port: number, listenerKey: NonSharedBuffer, listenerCert: NonSharedBuffer, baseAPIPath?: string): Promise { if (baseAPIPath) this._baseAPIPath = baseAPIPath; if (this._peerServer) { @@ -169,11 +205,22 @@ export class ConnectionManager { this._peerServer = new grpc.Server(); this._peerServer.addService(this._wrapper.Peer2Peer.service, { Fetch: async (call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => - peerListener(call, callback, this._logger, this._receivingConnections, this._peerCtor, this._baseAPIPath), + peerListener(call, callback, this._logger, this._receivingConnections, this.createNewConnection, this._baseAPIPath), }); + const sslCreds = grpc.ServerCredentials.createSsl( + this._caCert, + [ + { + private_key: listenerKey, + cert_chain: listenerCert, + }, + ], + true + ); + await new Promise((resolve, reject) => { - this._peerServer?.bindAsync(`localhost:${port}`, grpc.ServerCredentials.createInsecure(), (err) => (err ? reject(err) : resolve())); + this._peerServer?.bindAsync(`localhost:${port}`, sslCreds, (err) => (err ? reject(err) : resolve())); }); this._logger.infoMessage(`Peer server listening on localhost:${port}`); diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index 1cb7464da..822f4547e 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -12,14 +12,14 @@ * or submit itself to any jurisdiction. */ +import * as fs from 'fs'; import { ConnectionManager } from './ConnectionManager/ConnectionManager'; import { RevokeTokenHandler } from './commands/revokeToken/revokeToken.handler'; import { ConnectionDirection, DuplexMessageEvent } from '../models/message.model'; -import { Connection } from './connection/Connection'; import { NewTokenHandler } from './commands/newToken/newToken.handler'; -import { gRPCWrapperConfig } from '../models/config.model'; -import * as fs from 'fs'; import { LogManager } from '@aliceo2/web-ui'; +import type { gRPCWrapperConfig } from '../models/config.model'; +import type { Connection } from './connection/Connection'; /** * @description Wrapper class for managing secure gRPC wrapper. @@ -51,10 +51,9 @@ export class gRPCWrapper { if ( !config.protoPath || !config.centralAddress || - !config.clientCerts || - !config.clientCerts.caCertPath || - !config.clientCerts.certPath || - !config.clientCerts.keyPath + !config.clientCerts?.caCertPath || + !config.clientCerts?.certPath || + !config.clientCerts?.keyPath ) { throw new Error('Invalid gRPCWrapper configuration provided.'); } diff --git a/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts b/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts index 580d7f2b0..9ccdb9a9a 100644 --- a/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts @@ -12,7 +12,7 @@ * or submit itself to any jurisdiction. */ -import { NewTokenCommand } from '../../../client/Commands/newToken/newToken.command'; +import { NewTokenCommand } from '../../../client/commands/newToken/newToken.command'; import { NewTokenHandler } from '../../../client/commands/newToken/newToken.handler'; import { Connection } from '../../../client/connection/Connection'; import { ConnectionManager } from '../../../client/ConnectionManager/ConnectionManager'; diff --git a/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts b/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts index 3764d1724..05fbbe57e 100644 --- a/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts @@ -15,13 +15,14 @@ import { RevokeTokenCommand } from '../../../client/commands/revokeToken/revokeToken.command'; import { RevokeTokenHandler } from '../../../client/commands/revokeToken/revokeToken.handler'; import { Connection } from '../../../client/connection/Connection'; -import { ConnectionManager } from '../../../client/connectionManager/ConnectionManager'; +import { ConnectionManager } from '../../../client/ConnectionManager/ConnectionManager'; import { ConnectionDirection, DuplexMessageEvent } from '../../../models/message.model'; import { ConnectionStatus } from '../../../models/connection.model'; import { Command } from 'models/commands.model'; import * as grpc from '@grpc/grpc-js'; import * as protoLoader from '@grpc/proto-loader'; import path from 'path'; +import { getTestCerts } from '../../testCerts/testCerts'; describe('RevokeToken', () => { const protoPath = path.join(__dirname, '..', '..', '..', '..', '..', 'proto', 'wrapper.proto'); @@ -61,7 +62,7 @@ describe('RevokeToken', () => { it('should revoke token when connection found in sendingConnections', async () => { const targetAddress = 'peer-123'; - const conn = new Connection('valid-token', targetAddress, ConnectionDirection.SENDING, peerCtor); + const conn = new Connection('valid-token', targetAddress, ConnectionDirection.SENDING, peerCtor, getTestCerts()); (manager as any).sendingConnections!.set(targetAddress, conn); const handler = new RevokeTokenHandler(manager); @@ -75,7 +76,7 @@ describe('RevokeToken', () => { it('should revoke token when connection found in receivingConnections', async () => { const targetAddress = 'peer-456'; - const conn = new Connection('valid-token', targetAddress, ConnectionDirection.RECEIVING, peerCtor); + const conn = new Connection('valid-token', targetAddress, ConnectionDirection.RECEIVING, peerCtor, getTestCerts()); (manager as any).receivingConnections.set(targetAddress, conn); const handler = new RevokeTokenHandler(manager); diff --git a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts index dff46c2c6..da2cbbc9d 100644 --- a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts @@ -62,10 +62,10 @@ jest.mock('@grpc/grpc-js', () => { ...original, loadPackageDefinition, credentials: { - createInsecure: jest.fn(() => ({})), + createSsl: jest.fn(() => ({})), }, ServerCredentials: { - createInsecure: jest.fn(() => ({})), + createSsl: jest.fn(() => ({})), }, status: { ...original.status, @@ -141,11 +141,14 @@ jest.mock( { virtual: true } ); -import { ConnectionManager } from '../../../client/connectionManager/ConnectionManager'; +import { ConnectionManager } from '../../../client/ConnectionManager/ConnectionManager'; import { ConnectionDirection } from '../../../models/message.model'; import { ConnectionStatus } from '../../../models/connection.model'; +import { getTestCentralCertPaths, getTestCerts } from '../../../test/testCerts/testCerts'; describe('ConnectionManager', () => { + const { caCertPath, certPath, keyPath } = getTestCentralCertPaths(); + beforeEach(() => { jest.clearAllMocks(); capturedServerImpl = null; @@ -160,7 +163,7 @@ describe('ConnectionManager', () => { }); test('constructor: loads proto, builds wrapper/peerCtor and CentralSystem client', () => { - const cm = new ConnectionManager('proto/file.proto', 'central:5555'); + const cm = new ConnectionManager('proto/file.proto', 'central:5555', caCertPath, certPath, keyPath); expect(cm).toBeDefined(); expect((grpc as any).loadPackageDefinition).toHaveBeenCalled(); @@ -169,7 +172,7 @@ describe('ConnectionManager', () => { }); test('registerCommandHandlers: calls dispatcher.register for each item', () => { - const cm = new ConnectionManager('p.proto', 'c:1'); + const cm = new ConnectionManager('p.proto', 'c:1', caCertPath, certPath, keyPath); dispatcherRegisterMock.mockClear(); const handlers = [ @@ -185,7 +188,7 @@ describe('ConnectionManager', () => { }); test('connectToCentralSystem/disconnectFromCentralSystem delegate to CentralConnection', () => { - const cm = new ConnectionManager('p.proto', 'c:1'); + const cm = new ConnectionManager('p.proto', 'c:1', caCertPath, certPath, keyPath); // @ts-ignore cm['_peerCtor'] = Peer2PeerCtorMock; cm.connectToCentralSystem(); @@ -195,11 +198,11 @@ describe('ConnectionManager', () => { expect(centralDisconnectMock).toHaveBeenCalled(); }); - test('createNewConnection: adds to sending map, sets CONNECTED, logs', () => { - const cm = new ConnectionManager('p.proto', 'c:1'); + test('createNewConnection: adds to sending map, sets CONNECTED, logs', async () => { + const cm = new ConnectionManager('p.proto', 'c:1', caCertPath, certPath, keyPath); // @ts-ignore cm['_peerCtor'] = Peer2PeerCtorMock; - const conn = cm.createNewConnection('peer-A', ConnectionDirection.SENDING, 'tok123'); + const conn = await cm.createNewConnection('peer-A', ConnectionDirection.SENDING, 'tok123'); expect(connectionCtorMock).toHaveBeenCalledWith('tok123', 'peer-A', ConnectionDirection.SENDING, expect.any(Function)); expect(conn.status).toBe(ConnectionStatus.CONNECTED); @@ -213,7 +216,7 @@ describe('ConnectionManager', () => { }); test('createNewConnection: adds to receiving map if direction is RECEIVING', () => { - const cm = new ConnectionManager('p.proto', 'c:1'); + const cm = new ConnectionManager('p.proto', 'c:1', caCertPath, certPath, keyPath); cm.createNewConnection('peer-B', ConnectionDirection.RECEIVING); const { sending, receiving } = cm.connections; @@ -222,7 +225,7 @@ describe('ConnectionManager', () => { }); test('getConnectionByAddress: returns by direction. Logs on invalid direction', () => { - const cm = new ConnectionManager('p.proto', 'c:1'); + const cm = new ConnectionManager('p.proto', 'c:1', caCertPath, certPath, keyPath); const s = cm.createNewConnection('s-1', ConnectionDirection.SENDING); const r = cm.createNewConnection('r-1', ConnectionDirection.RECEIVING); @@ -236,7 +239,7 @@ describe('ConnectionManager', () => { }); test('connections getter: returns arrays (copies) of maps', () => { - const cm = new ConnectionManager('p.proto', 'c:1'); + const cm = new ConnectionManager('p.proto', 'c:1', caCertPath, certPath, keyPath); cm.createNewConnection('a', ConnectionDirection.SENDING); cm.createNewConnection('b', ConnectionDirection.RECEIVING); @@ -248,8 +251,8 @@ describe('ConnectionManager', () => { }); test('listenForPeers: creates server, registers service, binds & logs', async () => { - const cm = new ConnectionManager('p.proto', 'c:1'); - await cm.listenForPeers(50099, 'http://localhost:41000/api/'); + const cm = new ConnectionManager('p.proto', 'c:1', caCertPath, certPath, keyPath); + await cm.listenForPeers(50099, getTestCerts().clientKey, getTestCerts().clientCert, 'http://localhost:41000/api/'); const ServerCtor = (grpc.Server as any).mock; expect(ServerCtor).toBeDefined(); @@ -266,17 +269,17 @@ describe('ConnectionManager', () => { }); test('listenForPeers: calling twice shuts previous server down', async () => { - const cm = new ConnectionManager('p.proto', 'c:1'); - await cm.listenForPeers(50100, 'http://localhost:41000/api/'); + const cm = new ConnectionManager('p.proto', 'c:1', caCertPath, certPath, keyPath); + await cm.listenForPeers(50100, getTestCerts().clientKey, getTestCerts().clientCert, 'http://localhost:41000/api/'); const firstServer = (grpc.Server as any).mock.results[0].value; - await cm.listenForPeers(50101, 'http://localhost:41000/api/'); + await cm.listenForPeers(50101, getTestCerts().clientKey, getTestCerts().clientCert, 'http://localhost:41000/api/'); expect(firstServer.forceShutdown).toHaveBeenCalled(); }); test('p2p Fetch: registers new incoming receiving connection, forwards to local API, maps response', async () => { - const cm = new ConnectionManager('p.proto', 'c:1'); - await cm.listenForPeers(50102, 'http://local/api/'); + const cm = new ConnectionManager('p.proto', 'c:1', caCertPath, certPath, keyPath); + await cm.listenForPeers(50102, getTestCerts().clientKey, getTestCerts().clientCert, 'http://local/api/'); // Prepare incoming call and callback const call = { @@ -330,8 +333,8 @@ describe('ConnectionManager', () => { }); test('p2p Fetch: uses existing receiving connection when present (no duplicate creation)', async () => { - const cm = new ConnectionManager('p.proto', 'c:1'); - await cm.listenForPeers(50103, 'http://local/api/'); + const cm = new ConnectionManager('p.proto', 'c:1', caCertPath, certPath, keyPath); + await cm.listenForPeers(50103, getTestCerts().clientKey, getTestCerts().clientCert, 'http://local/api/'); cm.createNewConnection('client-77', ConnectionDirection.RECEIVING); @@ -374,8 +377,8 @@ describe('ConnectionManager', () => { }); test('p2p Fetch: on forward error returns INTERNAL and logs error', async () => { - const cm = new ConnectionManager('p.proto', 'c:1'); - await cm.listenForPeers(50104, 'http://local/api/'); + const cm = new ConnectionManager('p.proto', 'c:1', caCertPath, certPath, keyPath); + await cm.listenForPeers(50104, getTestCerts().clientKey, getTestCerts().clientCert, 'http://local/api/'); // @ts-ignore global.fetch.mockRejectedValue(new Error('err')); diff --git a/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts b/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts index 8937e9d5e..97f4556db 100644 --- a/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts +++ b/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts @@ -32,7 +32,7 @@ jest.mock( return { ...original, credentials: { - createInsecure: jest.fn(() => ({ insecure: true })), + createSsl: jest.fn(() => ({ insecure: true })), }, }; }, @@ -40,6 +40,7 @@ jest.mock( ); import * as grpc from '@grpc/grpc-js'; +import { getTestCerts } from '../testCerts/testCerts'; describe('Connection', () => { beforeEach(() => { @@ -48,9 +49,9 @@ describe('Connection', () => { }); test('constructor should create connection and set base state correctly', () => { - const conn = new Connection('tok', 'peer:50051', FAKE_DIRECTION, PeerCtorMock); + const conn = new Connection('tok', 'peer:50051', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); - expect(grpc.credentials.createInsecure).toHaveBeenCalled(); + expect(grpc.credentials.createSsl).toHaveBeenCalled(); expect(PeerCtorMock).toHaveBeenCalledWith('peer:50051', { insecure: true }); expect(conn.token).toBe('tok'); @@ -60,21 +61,21 @@ describe('Connection', () => { }); test('getter/setter for token should work', () => { - const conn = new Connection('old', 'peer:1', FAKE_DIRECTION, PeerCtorMock); + const conn = new Connection('old', 'peer:1', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); expect(conn.token).toBe('old'); conn.token = 'new-token'; expect(conn.token).toBe('new-token'); }); test('handleRevokeToken should clear token and status to UNAUTHORIZED', () => { - const conn = new Connection('secret', 'peer:x', FAKE_DIRECTION, PeerCtorMock); + const conn = new Connection('secret', 'peer:x', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); conn.handleRevokeToken(); expect(conn.token).toBe(''); expect(conn.status).toBe(ConnectionStatus.UNAUTHORIZED); }); test('getter/setter for status should work', () => { - const conn = new Connection('t', 'a', FAKE_DIRECTION, PeerCtorMock); + const conn = new Connection('t', 'a', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); conn.status = ConnectionStatus.UNAUTHORIZED; expect(conn.status).toBe(ConnectionStatus.UNAUTHORIZED); conn.status = ConnectionStatus.CONNECTED; @@ -82,12 +83,12 @@ describe('Connection', () => { }); test('getter for targetAddress should work', () => { - const conn = new Connection('t', 'host:1234', FAKE_DIRECTION, PeerCtorMock); + const conn = new Connection('t', 'host:1234', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); expect(conn.targetAddress).toBe('host:1234'); }); test('fetch should throw if peer client is not attached', async () => { - const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock); + const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); // @ts-ignore conn['_peerClient'] = undefined; @@ -95,7 +96,7 @@ describe('Connection', () => { }); test('fetch with defaults should work', async () => { - const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock); + const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); lastPeerClient.Fetch.mockImplementation((req: any, cb: any) => { try { @@ -116,7 +117,7 @@ describe('Connection', () => { }); test('fetch builds request correctly and returns response', async () => { - const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock); + const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); const body = Buffer.from('abc'); lastPeerClient.Fetch.mockImplementation((req: any, cb: any) => { @@ -142,7 +143,7 @@ describe('Connection', () => { }); test('fetch should convert Uint8Array to Buffer', async () => { - const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock); + const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); const body = new Uint8Array([1, 2, 3]); lastPeerClient.Fetch.mockImplementation((req: any, cb: any) => { @@ -160,7 +161,7 @@ describe('Connection', () => { }); test('fetch should convert string to Buffer', async () => { - const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock); + const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); const body = 'żółć & äöü'; // handling special chars lastPeerClient.Fetch.mockImplementation((req: any, cb: any) => { @@ -177,13 +178,13 @@ describe('Connection', () => { }); test('fetch should reject if body is not allowed', async () => { - const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock); + const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); // @ts-ignore await expect(conn.fetch({ body: { not: 'allowed' } })).rejects.toThrow('Body must be a string/Buffer/Uint8Array'); }); test('fetch should propagate errors from peer', async () => { - const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock); + const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); const err = new Error('err'); lastPeerClient.Fetch.mockImplementation((_req: any, cb: any) => cb(err)); @@ -191,7 +192,7 @@ describe('Connection', () => { }); test('fetch should map response', async () => { - const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock); + const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); const payload = { a: 1, b: 'x' }; lastPeerClient.Fetch.mockImplementation((_req: any, cb: any) => diff --git a/Tokenization/backend/wrapper/src/utils/connection/peerListener.ts b/Tokenization/backend/wrapper/src/utils/connection/peerListener.ts index 5fafb4f1d..d4c030fcc 100644 --- a/Tokenization/backend/wrapper/src/utils/connection/peerListener.ts +++ b/Tokenization/backend/wrapper/src/utils/connection/peerListener.ts @@ -12,9 +12,9 @@ * or submit itself to any jurisdiction. */ import * as grpc from '@grpc/grpc-js'; -import { Connection } from '../../client/connection/Connection'; import { ConnectionDirection } from '../../models/message.model'; import { ConnectionStatus } from '../../models/connection.model'; +import type { Connection } from '../../client/connection/Connection'; /** * Listens for incoming gRPC requests and forwards them to the local API endpoint. @@ -24,7 +24,7 @@ import { ConnectionStatus } from '../../models/connection.model'; * @param callback - The callback function to be called with the response. * @param logger - The logger object to write info and error messages. * @param receivingConnections - The map of existing incoming connections. - * @param peerCtor - The constructor function for the peer client. + * @param createNewConnection - Function to create a new Connection instance. * @param baseAPIPath - The base path of the local API endpoint. */ export const peerListener = async ( @@ -32,7 +32,7 @@ export const peerListener = async ( callback: grpc.sendUnaryData, logger: any, receivingConnections: Map, - peerCtor: any, + createNewConnection: (address: string, direction: ConnectionDirection, token?: string | undefined) => Promise, baseAPIPath: string ) => { try { @@ -42,7 +42,7 @@ export const peerListener = async ( let conn: Connection | undefined = receivingConnections.get(clientAddress); if (!conn) { - conn = new Connection('', clientAddress, ConnectionDirection.RECEIVING, peerCtor); + conn = await createNewConnection(clientAddress, ConnectionDirection.RECEIVING); conn.status = ConnectionStatus.CONNECTED; receivingConnections.set(clientAddress, conn); logger.infoMessage(`New incoming connection registered for: ${clientAddress}`); From 2526344091bdfc626b8ae12ac3abcd3a34b86a33 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 20 Nov 2025 20:48:04 +0100 Subject: [PATCH 093/103] fix: binding in peerListener, imports and unit tests --- .../commands/newToken/newToken.handler.ts | 2 +- .../revokeToken/revokeToken.handler.ts | 2 +- .../connectionManager/ConnectionManager.ts | 2 +- .../backend/wrapper/src/client/gRPCWrapper.ts | 2 +- .../src/test/client/commands/newToken.test.ts | 2 +- .../test/client/commands/revokeToken.test.ts | 2 +- .../ConnectionManager.test.ts | 28 +++++++++---------- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts b/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts index 9765bbadd..4fb7eb54b 100644 --- a/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts +++ b/Tokenization/backend/wrapper/src/client/commands/newToken/newToken.handler.ts @@ -14,7 +14,7 @@ import type { CommandHandler } from '../../../models/commands.model'; import type { NewTokenCommand } from './newToken.command'; -import type { ConnectionManager } from '../../ConnectionManager/ConnectionManager'; +import type { ConnectionManager } from '../../connectionManager/ConnectionManager'; import { ConnectionDirection } from '../../../models/message.model'; /** diff --git a/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts b/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts index bb806b2f2..2fa1a9640 100644 --- a/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts +++ b/Tokenization/backend/wrapper/src/client/commands/revokeToken/revokeToken.handler.ts @@ -14,7 +14,7 @@ import type { CommandHandler } from '../../../models/commands.model'; import type { RevokeTokenCommand } from './revokeToken.command'; -import type { ConnectionManager } from '../../ConnectionManager/ConnectionManager'; +import type { ConnectionManager } from '../../connectionManager/ConnectionManager'; /** * RevokeTokenHandler is responsible for handling the RevokeTokenCommand. diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts index 665c3796f..c73c6c9d0 100644 --- a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts @@ -205,7 +205,7 @@ export class ConnectionManager { this._peerServer = new grpc.Server(); this._peerServer.addService(this._wrapper.Peer2Peer.service, { Fetch: async (call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => - peerListener(call, callback, this._logger, this._receivingConnections, this.createNewConnection, this._baseAPIPath), + peerListener(call, callback, this._logger, this._receivingConnections, this.createNewConnection.bind(this), this._baseAPIPath), }); const sslCreds = grpc.ServerCredentials.createSsl( diff --git a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts index 822f4547e..d18b1db34 100644 --- a/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts +++ b/Tokenization/backend/wrapper/src/client/gRPCWrapper.ts @@ -13,7 +13,7 @@ */ import * as fs from 'fs'; -import { ConnectionManager } from './ConnectionManager/ConnectionManager'; +import { ConnectionManager } from './connectionManager/ConnectionManager'; import { RevokeTokenHandler } from './commands/revokeToken/revokeToken.handler'; import { ConnectionDirection, DuplexMessageEvent } from '../models/message.model'; import { NewTokenHandler } from './commands/newToken/newToken.handler'; diff --git a/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts b/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts index 9ccdb9a9a..ffec6ed77 100644 --- a/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/commands/newToken.test.ts @@ -15,7 +15,7 @@ import { NewTokenCommand } from '../../../client/commands/newToken/newToken.command'; import { NewTokenHandler } from '../../../client/commands/newToken/newToken.handler'; import { Connection } from '../../../client/connection/Connection'; -import { ConnectionManager } from '../../../client/ConnectionManager/ConnectionManager'; +import { ConnectionManager } from '../../../client/connectionManager/ConnectionManager'; import { Command } from 'models/commands.model'; import { ConnectionDirection, DuplexMessageEvent } from '../../../models/message.model'; import * as grpc from '@grpc/grpc-js'; diff --git a/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts b/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts index 05fbbe57e..38d076cd3 100644 --- a/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts @@ -15,7 +15,7 @@ import { RevokeTokenCommand } from '../../../client/commands/revokeToken/revokeToken.command'; import { RevokeTokenHandler } from '../../../client/commands/revokeToken/revokeToken.handler'; import { Connection } from '../../../client/connection/Connection'; -import { ConnectionManager } from '../../../client/ConnectionManager/ConnectionManager'; +import { ConnectionManager } from '../../../client/connectionManager/ConnectionManager'; import { ConnectionDirection, DuplexMessageEvent } from '../../../models/message.model'; import { ConnectionStatus } from '../../../models/connection.model'; import { Command } from 'models/commands.model'; diff --git a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts index da2cbbc9d..74b417f2c 100644 --- a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts @@ -141,7 +141,7 @@ jest.mock( { virtual: true } ); -import { ConnectionManager } from '../../../client/ConnectionManager/ConnectionManager'; +import { ConnectionManager } from '../../../client/connectionManager/ConnectionManager'; import { ConnectionDirection } from '../../../models/message.model'; import { ConnectionStatus } from '../../../models/connection.model'; import { getTestCentralCertPaths, getTestCerts } from '../../../test/testCerts/testCerts'; @@ -168,7 +168,7 @@ describe('ConnectionManager', () => { expect((grpc as any).loadPackageDefinition).toHaveBeenCalled(); expect(CentralSystemClientMock).toHaveBeenCalledWith('central:5555', expect.any(Object)); - expect(grpc.credentials.createInsecure).toHaveBeenCalled(); + expect(grpc.credentials.createSsl).toHaveBeenCalled(); }); test('registerCommandHandlers: calls dispatcher.register for each item', () => { @@ -204,7 +204,7 @@ describe('ConnectionManager', () => { cm['_peerCtor'] = Peer2PeerCtorMock; const conn = await cm.createNewConnection('peer-A', ConnectionDirection.SENDING, 'tok123'); - expect(connectionCtorMock).toHaveBeenCalledWith('tok123', 'peer-A', ConnectionDirection.SENDING, expect.any(Function)); + expect(connectionCtorMock).toHaveBeenCalledWith('tok123', 'peer-A', ConnectionDirection.SENDING, expect.any(Function), expect.any(Object)); expect(conn.status).toBe(ConnectionStatus.CONNECTED); // Exposed via connections getter @@ -215,19 +215,19 @@ describe('ConnectionManager', () => { expect(infoMessageMock).toHaveBeenCalledWith(expect.stringContaining('Connection with peer-A has been estabilished')); }); - test('createNewConnection: adds to receiving map if direction is RECEIVING', () => { + test('createNewConnection: adds to receiving map if direction is RECEIVING', async () => { const cm = new ConnectionManager('p.proto', 'c:1', caCertPath, certPath, keyPath); - cm.createNewConnection('peer-B', ConnectionDirection.RECEIVING); + await cm.createNewConnection('peer-B', ConnectionDirection.RECEIVING); const { sending, receiving } = cm.connections; expect(sending.length).toBe(0); expect(receiving.length).toBe(1); }); - test('getConnectionByAddress: returns by direction. Logs on invalid direction', () => { + test('getConnectionByAddress: returns by direction. Logs on invalid direction', async () => { const cm = new ConnectionManager('p.proto', 'c:1', caCertPath, certPath, keyPath); - const s = cm.createNewConnection('s-1', ConnectionDirection.SENDING); - const r = cm.createNewConnection('r-1', ConnectionDirection.RECEIVING); + const s = await cm.createNewConnection('s-1', ConnectionDirection.SENDING); + const r = await cm.createNewConnection('r-1', ConnectionDirection.RECEIVING); expect(cm.getConnectionByAddress('s-1', ConnectionDirection.SENDING)).toBe(s); expect(cm.getConnectionByAddress('r-1', ConnectionDirection.RECEIVING)).toBe(r); @@ -238,10 +238,10 @@ describe('ConnectionManager', () => { expect(errorMessageMock).toHaveBeenCalledWith('Invalid connection direction: 999'); }); - test('connections getter: returns arrays (copies) of maps', () => { + test('connections getter: returns arrays (copies) of maps', async () => { const cm = new ConnectionManager('p.proto', 'c:1', caCertPath, certPath, keyPath); - cm.createNewConnection('a', ConnectionDirection.SENDING); - cm.createNewConnection('b', ConnectionDirection.RECEIVING); + await cm.createNewConnection('a', ConnectionDirection.SENDING); + await cm.createNewConnection('b', ConnectionDirection.RECEIVING); const { sending, receiving } = cm.connections; expect(Array.isArray(sending)).toBe(true); @@ -286,7 +286,7 @@ describe('ConnectionManager', () => { getPeer: () => 'client-42', request: { method: 'post', - path: 'echo', + path: 'pong', headers: { 'content-type': 'application/json' }, body: Buffer.from(JSON.stringify({ ping: true })), }, @@ -309,7 +309,7 @@ describe('ConnectionManager', () => { const before = cm.connections.receiving.length; await capturedServerImpl.Fetch(call, callback); - expect(global.fetch).toHaveBeenCalledWith('http://local/api/echo', { + expect(global.fetch).toHaveBeenCalledWith('http://local/api/pong', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ ping: true }), @@ -336,7 +336,7 @@ describe('ConnectionManager', () => { const cm = new ConnectionManager('p.proto', 'c:1', caCertPath, certPath, keyPath); await cm.listenForPeers(50103, getTestCerts().clientKey, getTestCerts().clientCert, 'http://local/api/'); - cm.createNewConnection('client-77', ConnectionDirection.RECEIVING); + await cm.createNewConnection('client-77', ConnectionDirection.RECEIVING); // @ts-ignore global.fetch.mockResolvedValue({ From 0e256d661f7622290a0924a63d691a8ee7f73943 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 27 Nov 2025 14:01:50 +0100 Subject: [PATCH 094/103] fix: rebase --- Tokenization/backend/wrapper/src/client/connection/Connection.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/Tokenization/backend/wrapper/src/client/connection/Connection.ts b/Tokenization/backend/wrapper/src/client/connection/Connection.ts index d46deb453..38b09b19e 100644 --- a/Tokenization/backend/wrapper/src/client/connection/Connection.ts +++ b/Tokenization/backend/wrapper/src/client/connection/Connection.ts @@ -51,7 +51,6 @@ export class Connection { ) { this._token = token; this._targetAddress = targetAddress; - this._peerClient = new peerCtor(targetAddress, grpc.credentials.createInsecure()); this.direction = direction; if (!connectionCerts.caCert || !connectionCerts.clientCert || !connectionCerts.clientKey) { From 95c13cfc0a5fce0a0875afad46e0c59ebec0ac89 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 4 Dec 2025 21:15:00 +0100 Subject: [PATCH 095/103] fix: fix rebase --- .../backend/wrapper/models/config.model.ts | 29 ------- .../Interceptors/grpc.auth.interceptor.ts | 19 +++-- .../connectionManager/ConnectionManager.ts | 2 +- .../wrapper/src/models/message.model.ts | 4 +- .../grpc.auth.interceptor.test.ts | 5 +- .../test/client/commands/revokeToken.test.ts | 2 +- .../src/test/connection/Connection.test.ts | 78 +++++++++++++------ 7 files changed, 70 insertions(+), 69 deletions(-) delete mode 100644 Tokenization/backend/wrapper/models/config.model.ts diff --git a/Tokenization/backend/wrapper/models/config.model.ts b/Tokenization/backend/wrapper/models/config.model.ts deleted file mode 100644 index 5f4c82a0f..000000000 --- a/Tokenization/backend/wrapper/models/config.model.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -export interface CentralSystemConfig { - /** Path to the proto file defining the services. */ - protoPath: string; - /** Host/IP to bind the gRPC server on. Defaults to "0.0.0.0" which is docker-friendly. */ - host?: string; - /** Port to bind. Defaults to 50051. */ - port?: number; -} - -export interface gRPCWrapperConfig { - /** Path to the proto file defining the services. */ - protoPath: string; - /** Address of the CentralSystem server. */ - centralAddress: string; -} diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts index 13c32605f..bcba4db7f 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts @@ -135,7 +135,7 @@ export const gRPCAuthInterceptor = async ( // Decode and parse the JWT payload const payloadString = new TextDecoder().decode(jwtPayload); payload = JSON.parse(payloadString); - } catch (e: any) { + } catch { const error = { name: 'AuthenticationError', message: `JWS Verification error: Invalid signature`, @@ -173,7 +173,7 @@ export const gRPCAuthInterceptor = async ( * @param callback callback to return gRPC error if needed * @returns true if request method is allowed, false otherwise */ -export const isRequestAllowed = (tokenPayload: TokenPayload | undefined, request: any, callback: grpc.sendUnaryData): Boolean => { +export const isRequestAllowed = (tokenPayload: TokenPayload | undefined, request: any, callback: grpc.sendUnaryData): boolean => { const method = String(request?.method ?? 'POST').toUpperCase(); const isValidPayload = validateTokenPayload(tokenPayload, request.method); let isUnexpired; @@ -216,8 +216,8 @@ const validateTokenPayload = (tokenPayload: TokenPayload | undefined, method: st typeof tokenPayload.jti !== 'string' || Object.keys(tokenPayload.iat).length === 0 || Object.keys(tokenPayload.exp).length === 0 || - !tokenPayload.iat.hasOwnProperty(method) || - !tokenPayload.exp.hasOwnProperty(method) + !Object.prototype.hasOwnProperty.call(tokenPayload.iat, method) || + !Object.prototype.hasOwnProperty.call(tokenPayload.exp, method) ) { return false; } @@ -231,7 +231,7 @@ const validateTokenPayload = (tokenPayload: TokenPayload | undefined, method: st * @param exp expiration timestamp for the specific method * @returns true if permission is still valid, false if expired */ -export const isPermissionUnexpired = (iat: number, exp: number): Boolean => { +export const isPermissionUnexpired = (iat: number, exp: number): boolean => { const nowInSeconds = Math.floor(Date.now() / 1000); if (nowInSeconds >= exp) { @@ -252,7 +252,7 @@ export const isPermissionUnexpired = (iat: number, exp: number): Boolean => { * @param callback callback to return gRPC error if needed * @returns true if serial numbers match, false otherwise */ -export const isSerialNumberMatching = (tokenPayload: TokenPayload | undefined, peerCert: any, callback: grpc.sendUnaryData): Boolean => { +export const isSerialNumberMatching = (tokenPayload: TokenPayload | undefined, peerCert: any, callback: grpc.sendUnaryData): boolean => { const clientSN = normalizeSerial(peerCert?.serialNumber); const tokenSN = normalizeSerial(tokenPayload?.sub); @@ -273,10 +273,9 @@ export const isSerialNumberMatching = (tokenPayload: TokenPayload | undefined, p * @param sn serial number string possibly containing colons or being null/undefined * @returns normalized serial number string */ -const normalizeSerial = (sn?: string | null): string => { +const normalizeSerial = (sn?: string | null): string => // Node retrieves serial number as hex string, without leading 0x and with possible colons so we need to normalize it - return (sn || '').replace(/[^0-9a-f]/gi, '').toUpperCase(); -}; + (sn ?? '').replace(/[^0-9a-f]/gi, '').toUpperCase(); /** * Retrieves the peer certificate from the gRPC call object. @@ -286,5 +285,5 @@ const normalizeSerial = (sn?: string | null): string => { export const getPeerCertFromCall = (call: any) => { const session = call?.call?.stream?.session; const sock = session?.socket as any; - return sock?.getPeerCertificate(true); // whole certificate info from TLS socket + return sock?.getPeerCertificate(true); // Whole certificate info from TLS socket }; diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts index 2716d268d..3abec22a9 100644 --- a/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts +++ b/Tokenization/backend/wrapper/src/client/connectionManager/ConnectionManager.ts @@ -23,7 +23,7 @@ import type { DuplexMessageEvent } from '../../models/message.model'; import { ConnectionDirection } from '../../models/message.model'; import { ConnectionStatus } from '../../models/connection.model'; import type { SecurityContext } from '../../utils/security/SecurityContext'; -import { peerListener } from 'utils/connection/peerListener'; +import { peerListener } from '../../utils/connection/peerListener'; /** * @description Manages all the connection between clients and central system. diff --git a/Tokenization/backend/wrapper/src/models/message.model.ts b/Tokenization/backend/wrapper/src/models/message.model.ts index a1819ceb1..947fa8ce5 100644 --- a/Tokenization/backend/wrapper/src/models/message.model.ts +++ b/Tokenization/backend/wrapper/src/models/message.model.ts @@ -35,7 +35,6 @@ export enum DuplexMessageEvent { * @property DUPLEX: Indicates a connection that can both send and receive messages. */ export enum ConnectionDirection { - UNSPECIFIED = 'UNSPECIFIED', SENDING = 'SENDING', RECEIVING = 'RECEIVING', DUPLEX = 'DUPLEX', @@ -70,6 +69,5 @@ export interface TokenMessage { */ export interface DuplexMessageModel { event: DuplexMessageEvent; - newToken?: TokenMessage; - revokeToken?: TokenMessage; + payload: TokenMessage; } diff --git a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts index bfde0b007..b3feb7f80 100644 --- a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts @@ -18,7 +18,7 @@ import * as interceptor from '../../../../client/connectionManager/Interceptors/ // Connection class mock jest.mock( - '../../../../client/Connection/Connection', + '../../../../client/connection/Connection', () => { return { Connection: jest.fn().mockImplementation((jweToken: string, address: string, direction: any) => { @@ -149,7 +149,8 @@ describe('gRPCAuthInterceptor', () => { expect(result.conn).toBe(existingConn); expect(isRequestAllowedSpy).toHaveBeenCalledTimes(1); expect(isSerialNumberMatchingSpy).toHaveBeenCalledTimes(1); - expect(jose.compactDecrypt as jest.Mock).not.toHaveBeenCalled(); + expect(jose.compactDecrypt as jest.Mock).toHaveBeenCalledTimes(1); + expect(jose.compactDecrypt as jest.Mock).toHaveBeenCalledWith(VALID_JWE, 'mock_priv_key'); }); it('should reject if connection exists but is BLOCKED', async () => { diff --git a/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts b/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts index 5afa427b7..b5c8fb75e 100644 --- a/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/commands/revokeToken.test.ts @@ -40,7 +40,7 @@ jest.mock( ); describe('RevokeToken', () => { - const protoPath = path.join(__dirname, '..', '..', '..', 'proto', 'wrapper.proto'); + const protoPath = path.join(__dirname, '..', '..', '..', '..', '..', 'proto', 'wrapper.proto'); const packageDef = protoLoader.loadSync(protoPath, { keepCase: true, longs: String, diff --git a/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts b/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts index 97f4556db..212c1c402 100644 --- a/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts +++ b/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts @@ -14,6 +14,8 @@ import { Connection } from '../../client/connection/Connection'; import { ConnectionStatus } from '../../models/connection.model'; +// opcjonalnie można też użyć prawdziwego enumu: +// import { ConnectionDirection } from '../../models/message.model'; const FAKE_DIRECTION: any = 'SENDING'; @@ -49,9 +51,11 @@ describe('Connection', () => { }); test('constructor should create connection and set base state correctly', () => { - const conn = new Connection('tok', 'peer:50051', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); + const certs = getTestCerts(); + const conn = new Connection('tok', 'peer:50051', FAKE_DIRECTION); + conn.createSslTunnel(PeerCtorMock, certs); - expect(grpc.credentials.createSsl).toHaveBeenCalled(); + expect(grpc.credentials.createSsl).toHaveBeenCalledWith(certs.caCert, certs.clientKey, certs.clientCert); expect(PeerCtorMock).toHaveBeenCalledWith('peer:50051', { insecure: true }); expect(conn.token).toBe('tok'); @@ -61,21 +65,21 @@ describe('Connection', () => { }); test('getter/setter for token should work', () => { - const conn = new Connection('old', 'peer:1', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); + const conn = new Connection('old', 'peer:1', FAKE_DIRECTION); expect(conn.token).toBe('old'); conn.token = 'new-token'; expect(conn.token).toBe('new-token'); }); test('handleRevokeToken should clear token and status to UNAUTHORIZED', () => { - const conn = new Connection('secret', 'peer:x', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); + const conn = new Connection('secret', 'peer:x', FAKE_DIRECTION); conn.handleRevokeToken(); expect(conn.token).toBe(''); expect(conn.status).toBe(ConnectionStatus.UNAUTHORIZED); }); test('getter/setter for status should work', () => { - const conn = new Connection('t', 'a', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); + const conn = new Connection('t', 'a', FAKE_DIRECTION); conn.status = ConnectionStatus.UNAUTHORIZED; expect(conn.status).toBe(ConnectionStatus.UNAUTHORIZED); conn.status = ConnectionStatus.CONNECTED; @@ -83,12 +87,13 @@ describe('Connection', () => { }); test('getter for targetAddress should work', () => { - const conn = new Connection('t', 'host:1234', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); + const conn = new Connection('t', 'host:1234', FAKE_DIRECTION); expect(conn.targetAddress).toBe('host:1234'); }); test('fetch should throw if peer client is not attached', async () => { - const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); + const conn = new Connection('t', 'addr', FAKE_DIRECTION); + // peerClient celowo nie jest ustawiany (brak createSslTunnel/attachGrpcClient) // @ts-ignore conn['_peerClient'] = undefined; @@ -96,9 +101,11 @@ describe('Connection', () => { }); test('fetch with defaults should work', async () => { - const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); + const certs = getTestCerts(); + const conn = new Connection('t', 'addr', FAKE_DIRECTION); + conn.createSslTunnel(PeerCtorMock, certs); - lastPeerClient.Fetch.mockImplementation((req: any, cb: any) => { + lastPeerClient.Fetch.mockImplementation((req: any, metadata: any, cb: any) => { try { expect(req).toEqual({ method: 'POST', @@ -106,6 +113,10 @@ describe('Connection', () => { headers: {}, body: Buffer.alloc(0), }); + + expect(metadata).toBeInstanceOf(grpc.Metadata); + expect(metadata.get('jwetoken')).toEqual(['t']); + cb(null, { status: 200, headers: {}, body: Buffer.alloc(0) }); } catch (e) { cb(e); @@ -117,16 +128,23 @@ describe('Connection', () => { }); test('fetch builds request correctly and returns response', async () => { - const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); + const certs = getTestCerts(); + const conn = new Connection('t', 'addr', FAKE_DIRECTION); + conn.createSslTunnel(PeerCtorMock, certs); + const body = Buffer.from('abc'); - lastPeerClient.Fetch.mockImplementation((req: any, cb: any) => { + lastPeerClient.Fetch.mockImplementation((req: any, metadata: any, cb: any) => { try { expect(req.method).toBe('PUT'); expect(req.path).toBe('/api/a'); expect(req.headers).toEqual({ 'x-a': '1' }); expect(Buffer.isBuffer(req.body)).toBe(true); expect(req.body.equals(body)).toBe(true); + + expect(metadata).toBeInstanceOf(grpc.Metadata); + expect(metadata.get('jwetoken')).toEqual(['t']); + cb(null, { status: 201, headers: { 'content-type': 'text/plain' }, @@ -143,10 +161,13 @@ describe('Connection', () => { }); test('fetch should convert Uint8Array to Buffer', async () => { - const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); + const certs = getTestCerts(); + const conn = new Connection('t', 'addr', FAKE_DIRECTION); + conn.createSslTunnel(PeerCtorMock, certs); + const body = new Uint8Array([1, 2, 3]); - lastPeerClient.Fetch.mockImplementation((req: any, cb: any) => { + lastPeerClient.Fetch.mockImplementation((req: any, _metadata: any, cb: any) => { try { expect(Buffer.isBuffer(req.body)).toBe(true); expect(req.body.equals(Buffer.from([1, 2, 3]))).toBe(true); @@ -161,10 +182,13 @@ describe('Connection', () => { }); test('fetch should convert string to Buffer', async () => { - const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); - const body = 'żółć & äöü'; // handling special chars + const certs = getTestCerts(); + const conn = new Connection('t', 'addr', FAKE_DIRECTION); + conn.createSslTunnel(PeerCtorMock, certs); + + const body = 'żółć & äöü'; - lastPeerClient.Fetch.mockImplementation((req: any, cb: any) => { + lastPeerClient.Fetch.mockImplementation((req: any, _metadata: any, cb: any) => { try { expect(req.body.equals(Buffer.from(body, 'utf8'))).toBe(true); cb(null, { status: 200, headers: {}, body: Buffer.from('{"ok":true}') }); @@ -178,24 +202,32 @@ describe('Connection', () => { }); test('fetch should reject if body is not allowed', async () => { - const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); - // @ts-ignore - await expect(conn.fetch({ body: { not: 'allowed' } })).rejects.toThrow('Body must be a string/Buffer/Uint8Array'); + const conn = new Connection('t', 'addr', FAKE_DIRECTION); + conn.createSslTunnel(PeerCtorMock, getTestCerts()); + await expect( + // @ts-ignore + conn.fetch({ body: { not: 'allowed' } }) + ).rejects.toThrow('Body must be a string/Buffer/Uint8Array'); }); test('fetch should propagate errors from peer', async () => { - const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); + const certs = getTestCerts(); + const conn = new Connection('t', 'addr', FAKE_DIRECTION); + conn.createSslTunnel(PeerCtorMock, certs); + const err = new Error('err'); - lastPeerClient.Fetch.mockImplementation((_req: any, cb: any) => cb(err)); + lastPeerClient.Fetch.mockImplementation((_req: any, _metadata: any, cb: any) => cb(err)); await expect(conn.fetch({ method: 'GET', path: '/x' })).rejects.toThrow('err'); }); test('fetch should map response', async () => { - const conn = new Connection('t', 'addr', FAKE_DIRECTION, PeerCtorMock, getTestCerts()); + const certs = getTestCerts(); + const conn = new Connection('t', 'addr', FAKE_DIRECTION); + conn.createSslTunnel(PeerCtorMock, certs); const payload = { a: 1, b: 'x' }; - lastPeerClient.Fetch.mockImplementation((_req: any, cb: any) => + lastPeerClient.Fetch.mockImplementation((_req: any, _metadata: any, cb: any) => cb(null, { headers: { 'x-k': 'v' }, body: Buffer.from(JSON.stringify(payload)), From e6b20e90e8e5f829e8285288e04fd00fc66904f7 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 4 Dec 2025 23:00:35 +0100 Subject: [PATCH 096/103] fix: rebase and coverage of branches --- .../Interceptors/grpc.auth.interceptor.ts | 26 +- .../grpc.auth.interceptor.test.ts | 340 +++++++++++++++++- .../ConnectionManager.test.ts | 183 +++++++++- .../src/utils/connection/peerListener.ts | 2 +- 4 files changed, 535 insertions(+), 16 deletions(-) diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts b/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts index bcba4db7f..c3a353eb2 100644 --- a/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts +++ b/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts @@ -175,20 +175,20 @@ export const gRPCAuthInterceptor = async ( */ export const isRequestAllowed = (tokenPayload: TokenPayload | undefined, request: any, callback: grpc.sendUnaryData): boolean => { const method = String(request?.method ?? 'POST').toUpperCase(); - const isValidPayload = validateTokenPayload(tokenPayload, request.method); - let isUnexpired; + const isValidPayload = validateTokenPayload(tokenPayload, method); + let isExpired = false; if (isValidPayload) { - isUnexpired = isPermissionUnexpired(tokenPayload.iat[method], tokenPayload.exp[method]); + isExpired = isPermissionExpired(tokenPayload.iat[method], tokenPayload.exp[method]); } - if (!isValidPayload || !isUnexpired) { + if (!isValidPayload || isExpired) { const error = { name: 'AuthorizationError', - code: isUnexpired ? grpc.status.PERMISSION_DENIED : grpc.status.UNAUTHENTICATED, - message: isUnexpired - ? `Request of type ${method} is not allowed by the token policy.` - : `Request of type ${method}, permission has expired.`, + code: isExpired ? grpc.status.UNAUTHENTICATED : grpc.status.PERMISSION_DENIED, + message: isExpired + ? `Request of type ${method}, permission has expired.` + : `Request of type ${method} is not allowed by the token policy.`, } as any; callback(error, null); @@ -229,20 +229,20 @@ const validateTokenPayload = (tokenPayload: TokenPayload | undefined, method: st * Checks if the permissions granted in the token have expired. * @param iat issued-at timestamp for the specific method * @param exp expiration timestamp for the specific method - * @returns true if permission is still valid, false if expired + * @returns true if permission is expired */ -export const isPermissionUnexpired = (iat: number, exp: number): boolean => { +export const isPermissionExpired = (iat: number, exp: number): boolean => { const nowInSeconds = Math.floor(Date.now() / 1000); if (nowInSeconds >= exp) { - return false; + return true; } if (iat > nowInSeconds) { - return false; + return true; } - return true; + return false; }; /** diff --git a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts index b3feb7f80..fb8d62c6e 100644 --- a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts @@ -14,7 +14,7 @@ import * as grpc from '@grpc/grpc-js'; import * as jose from 'jose'; -import * as interceptor from '../../../../client/connectionManager/Interceptors/grpc.auth.interceptor'; +import * as interceptor from '../../../../client/connectionManager/interceptors/grpc.auth.interceptor'; // Connection class mock jest.mock( @@ -294,4 +294,342 @@ describe('gRPCAuthInterceptor', () => { expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ code: grpc.status.PERMISSION_DENIED }), null); expect(isSerialNumberMatchingSpy).toHaveBeenCalledTimes(1); }); + it('should reject if existing connection has request not allowed', async () => { + const existingConn = new (Connection as jest.Mock)(VALID_JWE, MOCK_ADDRESS, ConnectionDirection.RECEIVING); + existingConn.getToken.mockReturnValue(VALID_JWE); + mockClientConnections.set(MOCK_ADDRESS, existingConn); + + // mock request not allowed + isRequestAllowedSpy.mockImplementation((_p, _r, cb) => { + cb( + { + name: 'AuthorizationError', + code: grpc.status.PERMISSION_DENIED, + message: 'Request of type POST is not allowed by the token policy.', + } as any, + null + ); + return false; + }); + + const result = await interceptor.gRPCAuthInterceptor(mockCall, mockCallback, mockClientConnections as any, mockSecurityContext); + + expect(result.isAuthenticated).toBe(false); + expect(result.conn).toBe(existingConn); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Request of type POST is not allowed by the token policy.', + }), + null + ); + }); + + it('should re-authenticate when existing connection has different token', async () => { + const existingConn = new (Connection as jest.Mock)('OLD.TOKEN', MOCK_ADDRESS, ConnectionDirection.RECEIVING); + existingConn.getToken.mockReturnValue('OLD.TOKEN'); + mockClientConnections.set(MOCK_ADDRESS, existingConn); + + (mockCall.metadata.getMap as unknown as jest.Mock).mockReturnValue({ + jwetoken: 'NEW.TOKEN', + }); + + const result = await interceptor.gRPCAuthInterceptor(mockCall, mockCallback, mockClientConnections as any, mockSecurityContext); + + expect(result.isAuthenticated).toBe(true); + expect(existingConn.handleSuccessfulAuth).toHaveBeenCalledWith(DECRYPTED_PAYLOAD); + expect(jose.compactDecrypt as jest.Mock).toHaveBeenCalledTimes(1); + expect(jose.compactVerify as jest.Mock).toHaveBeenCalledTimes(1); + }); + + it('should fail if JWS has incorrect signing algorithm', async () => { + (jose.compactVerify as jest.Mock).mockResolvedValue({ + payload: Buffer.from(JSON.stringify(DECRYPTED_PAYLOAD)), + protectedHeader: { alg: 'RS256' }, // Wrong algorithm + }); + + const result = await interceptor.gRPCAuthInterceptor(mockCall, mockCallback, mockClientConnections as any, mockSecurityContext); + + const created = getCreatedConn(); + expect(result.isAuthenticated).toBe(false); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Incorrect signing algorithm for JWS.', + code: grpc.status.UNAUTHENTICATED, + }), + null + ); + }); +}); + +describe('isRequestAllowed', () => { + const mockCallback = jest.fn(); + + beforeEach(() => { + jest.restoreAllMocks(); + jest.clearAllMocks(); + }); + + it('should return true for valid payload with unexpired permission', () => { + const now = Math.floor(Date.now() / 1000); + const payload: TokenPayload = { + sub: 'AABBCC', + aud: 'test-audience', + iss: 'test-issuer', + jti: 'test-jti', + iat: { POST: now - 100 }, + exp: { POST: now + 3600 }, + } as any; + + const request = { method: 'POST' }; + const result = interceptor.isRequestAllowed(payload, request, mockCallback); + + expect(result).toBe(true); + expect(mockCallback).not.toHaveBeenCalled(); + }); + + it('should return false for expired permission', () => { + const now = Math.floor(Date.now() / 1000); + const payload: TokenPayload = { + sub: 'AABBCC', + aud: 'test-audience', + iss: 'test-issuer', + jti: 'test-jti', + iat: { POST: now - 7200 }, + exp: { POST: now - 3600 }, // Expired 1 hour ago + } as any; + + const request = { method: 'POST' }; + const result = interceptor.isRequestAllowed(payload, request, mockCallback); + + expect(result).toBe(false); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + code: grpc.status.UNAUTHENTICATED, + message: 'Request of type POST, permission has expired.', + }), + null + ); + }); + + it('should return false for method not in token permissions', () => { + const now = Math.floor(Date.now() / 1000); + const payload: TokenPayload = { + sub: 'AABBCC', + aud: 'test-audience', + iss: 'test-issuer', + jti: 'test-jti', + iat: { POST: now - 100 }, + exp: { POST: now + 3600 }, + } as any; + + const request = { method: 'DELETE' }; // Not in permissions + const result = interceptor.isRequestAllowed(payload, request, mockCallback); + + expect(result).toBe(false); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + code: grpc.status.PERMISSION_DENIED, + message: 'Request of type DELETE is not allowed by the token policy.', + }), + null + ); + }); + + it('should handle missing request method with default POST', () => { + const now = Math.floor(Date.now() / 1000); + const payload: TokenPayload = { + sub: 'AABBCC', + aud: 'test-audience', + iss: 'test-issuer', + jti: 'test-jti', + iat: { POST: now - 100 }, + exp: { POST: now + 3600 }, + } as any; + + const request = {}; // No method + const result = interceptor.isRequestAllowed(payload, request, mockCallback); + + expect(result).toBe(true); + }); + + it('should return false for invalid payload structure (missing iat)', () => { + const now = Math.floor(Date.now() / 1000); + const payload: any = { + sub: 'AABBCC', + aud: 'test-audience', + iss: 'test-issuer', + jti: 'test-jti', + exp: { POST: now + 3600 }, + // iat missing + }; + + const request = { method: 'POST' }; + const result = interceptor.isRequestAllowed(payload, request, mockCallback); + + expect(result).toBe(false); + }); + + it('should return false for invalid payload structure (empty iat)', () => { + const now = Math.floor(Date.now() / 1000); + const payload: any = { + sub: 'AABBCC', + aud: 'test-audience', + iss: 'test-issuer', + jti: 'test-jti', + iat: {}, // Empty + exp: { POST: now + 3600 }, + }; + + const request = { method: 'POST' }; + const result = interceptor.isRequestAllowed(payload, request, mockCallback); + + expect(result).toBe(false); + }); +}); + +describe('isPermissionExpired', () => { + it('should return false for valid unexpired permission', () => { + const now = Math.floor(Date.now() / 1000); + const iat = now - 100; + const exp = now + 3600; + + const result = interceptor.isPermissionExpired(iat, exp); + + expect(result).toBe(false); + }); + + it('should return true when permission has expired', () => { + const now = Math.floor(Date.now() / 1000); + const iat = now - 7200; + const exp = now - 3600; // Expired 1 hour ago + + const result = interceptor.isPermissionExpired(iat, exp); + + expect(result).toBe(true); + }); + + it('should return true when iat is in the future', () => { + const now = Math.floor(Date.now() / 1000); + const iat = now + 100; // Issued in the future + const exp = now + 3600; + + const result = interceptor.isPermissionExpired(iat, exp); + + expect(result).toBe(true); + }); +}); + +describe('isSerialNumberMatching', () => { + const mockCallback = jest.fn(); + + beforeEach(() => { + jest.restoreAllMocks(); + jest.clearAllMocks(); + }); + + it('should return true when serial numbers match', () => { + const payload: TokenPayload = { + sub: 'AABBCCDDEE', + } as any; + const peerCert = { serialNumber: 'AA:BB:CC:DD:EE' }; + + const result = interceptor.isSerialNumberMatching(payload, peerCert, mockCallback); + + expect(result).toBe(true); + expect(mockCallback).not.toHaveBeenCalled(); + }); + + it('should return true when serial numbers match (different formats)', () => { + const payload: TokenPayload = { + sub: 'aabbccddee', + } as any; + const peerCert = { serialNumber: 'AA:BB:CC:DD:EE' }; + + const result = interceptor.isSerialNumberMatching(payload, peerCert, mockCallback); + + expect(result).toBe(true); + }); + + it('should return false when serial numbers do not match', () => { + const payload: TokenPayload = { + sub: 'AABBCCDDEE', + } as any; + const peerCert = { serialNumber: '11:22:33:44:55' }; + + const result = interceptor.isSerialNumberMatching(payload, peerCert, mockCallback); + + expect(result).toBe(false); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + code: grpc.status.PERMISSION_DENIED, + message: 'Serial number mismatch (mTLS binding failure).', + }), + null + ); + }); + + it('should return false when peerCert is null', () => { + const payload: TokenPayload = { + sub: 'AABBCCDDEE', + } as any; + + const result = interceptor.isSerialNumberMatching(payload, null, mockCallback); + + expect(result).toBe(false); + expect(mockCallback).toHaveBeenCalled(); + }); + + it('should return false when payload is undefined', () => { + const peerCert = { serialNumber: 'AA:BB:CC:DD:EE' }; + + const result = interceptor.isSerialNumberMatching(undefined, peerCert, mockCallback); + + expect(result).toBe(false); + }); + + it('should normalize serial numbers with special characters', () => { + const payload: TokenPayload = { + sub: 'AA-BB-CC-DD-EE', + } as any; + const peerCert = { serialNumber: 'AA:BB:CC:DD:EE' }; + + const result = interceptor.isSerialNumberMatching(payload, peerCert, mockCallback); + + expect(result).toBe(true); + }); +}); + +describe('getPeerCertFromCall', () => { + beforeEach(() => { + jest.restoreAllMocks(); + jest.clearAllMocks(); + }); + + it('should return peer certificate from call', () => { + const mockCert = { serialNumber: 'AABBCC', subject: 'CN=test' }; + const mockCall = { + call: { + stream: { + session: { + socket: { + getPeerCertificate: jest.fn().mockReturnValue(mockCert), + }, + }, + }, + }, + }; + + const result = interceptor.getPeerCertFromCall(mockCall); + + expect(result).toBe(mockCert); + expect(mockCall.call.stream.session.socket.getPeerCertificate).toHaveBeenCalledWith(true); + }); + + it('should handle missing call structure gracefully', () => { + const mockCall = {}; + + const result = interceptor.getPeerCertFromCall(mockCall); + + expect(result).toBeUndefined(); + }); }); diff --git a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts index 8c7daf8eb..41dae91c5 100644 --- a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts @@ -32,7 +32,7 @@ const mockClient = { const CentralSystemMock = jest.fn(() => mockClient); // Mock gRPC auth interceptor -jest.mock('../../../client/ConnectionManager/Interceptors/grpc.auth.interceptor', () => ({ +jest.mock('../../../client/connectionManager/interceptors/grpc.auth.interceptor', () => ({ gRPCAuthInterceptor: jest.fn((call, callback) => { return Promise.resolve({ isAuthenticated: true, @@ -307,4 +307,185 @@ describe('ConnectionManager', () => { }) ); }); + + describe('createNewConnection', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('should create new RECEIVING connection', async () => { + const address = 'peer-receiving-1'; + const token = 'test-token-123'; + + const connection = await conn.createNewConnection(address, ConnectionDirection.RECEIVING, token); + + expect(connection).toBeDefined(); + expect(connection.targetAddress).toBe(address); + expect(connection.direction).toBe(ConnectionDirection.RECEIVING); + expect(connection.token).toBe(token); + }); + + test('should create new SENDING connection with SSL tunnel', async () => { + const address = 'peer-sending-1'; + const token = 'test-token-456'; + + const connection = await conn.createNewConnection(address, ConnectionDirection.SENDING, token); + + expect(connection).toBeDefined(); + expect(connection.targetAddress).toBe(address); + expect(connection.direction).toBe(ConnectionDirection.SENDING); + expect(connection.token).toBe(token); + }); + + test('should return existing connection if already exists (RECEIVING)', async () => { + const address = 'peer-existing-receiving'; + const token1 = 'token-1'; + const token2 = 'token-2'; + + const conn1 = await conn.createNewConnection(address, ConnectionDirection.RECEIVING, token1); + const conn2 = await conn.createNewConnection(address, ConnectionDirection.RECEIVING, token2); + + expect(conn1).toBe(conn2); + expect(conn2.token).toBe(token2); // Token should be updated + }); + + test('should return existing connection if already exists (SENDING)', async () => { + const address = 'peer-existing-sending'; + const token1 = 'token-1'; + const token2 = 'token-2'; + + const conn1 = await conn.createNewConnection(address, ConnectionDirection.SENDING, token1); + const conn2 = await conn.createNewConnection(address, ConnectionDirection.SENDING, token2); + + expect(conn1).toBe(conn2); + expect(conn2.token).toBe(token2); // Token should be updated + }); + + test('should return existing connection without updating token if no token provided', async () => { + const address = 'peer-no-token-update'; + const token1 = 'token-1'; + + const conn1 = await conn.createNewConnection(address, ConnectionDirection.RECEIVING, token1); + const conn2 = await conn.createNewConnection(address, ConnectionDirection.RECEIVING); + + expect(conn1).toBe(conn2); + expect(conn2.token).toBe(token1); // Token should remain unchanged + }); + + test('should create connection without token', async () => { + const address = 'peer-no-token'; + + const connection = await conn.createNewConnection(address, ConnectionDirection.RECEIVING); + + expect(connection).toBeDefined(); + expect(connection.token).toBe(''); + }); + }); + + describe('getConnectionByAddress', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('should get SENDING connection by address', async () => { + const address = 'peer-send-get'; + await conn.createNewConnection(address, ConnectionDirection.SENDING, 'token'); + + const connection = conn.getConnectionByAddress(address, ConnectionDirection.SENDING); + + expect(connection).toBeDefined(); + expect(connection?.targetAddress).toBe(address); + }); + + test('should get RECEIVING connection by address', async () => { + const address = 'peer-receive-get'; + await conn.createNewConnection(address, ConnectionDirection.RECEIVING, 'token'); + + const connection = conn.getConnectionByAddress(address, ConnectionDirection.RECEIVING); + + expect(connection).toBeDefined(); + expect(connection?.targetAddress).toBe(address); + }); + + test('should return undefined for non-existent connection', () => { + const connection = conn.getConnectionByAddress('non-existent', ConnectionDirection.SENDING); + + expect(connection).toBeUndefined(); + }); + }); + + describe('getConnectionBySerialNumber', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('should find connection by serial number in receiving connections', async () => { + const address = 'peer-sn-receiving'; + const serialNumber = 'SN-12345'; + + const connection = await conn.createNewConnection(address, ConnectionDirection.RECEIVING, 'token'); + connection.serialNumber = serialNumber; + + const found = conn.getConnectionBySerialNumber(serialNumber); + + expect(found).toBe(connection); + expect(found?.serialNumber).toBe(serialNumber); + }); + + test('should find connection by serial number in sending connections', async () => { + const address = 'peer-sn-sending'; + const serialNumber = 'SN-67890'; + + const connection = await conn.createNewConnection(address, ConnectionDirection.SENDING, 'token'); + connection.serialNumber = serialNumber; + + const found = conn.getConnectionBySerialNumber(serialNumber); + + expect(found).toBe(connection); + expect(found?.serialNumber).toBe(serialNumber); + }); + + test('should return undefined if serial number not found', () => { + const found = conn.getConnectionBySerialNumber('non-existent-sn'); + + expect(found).toBeUndefined(); + }); + }); + + describe('listenForPeers edge cases', () => { + beforeEach(() => { + jest.clearAllMocks(); + capturedServerImpl = null; + }); + + test('should not start listener if clientListenerCert is missing', async () => { + const securityContextNoCert = new SecurityContext(MOCK_CERT, MOCK_CERT, MOCK_CERT, MOCK_CERT, undefined as any); + const connNoCert = new ConnectionManager('dummy.proto', 'localhost:12345', securityContextNoCert); + + await connNoCert.listenForPeers(50058); + + const serverCtor = (grpc.Server as any).mock; + // Server should not be created + expect(serverCtor.calls.length).toBe(0); + }); + + test('should shutdown existing server before starting new one', async () => { + await conn.listenForPeers(50059, 'http://localhost:40041/api/'); + + const serverCtor = (grpc.Server as any).mock; + const firstServer = serverCtor.results[0].value; + + // Start listener again + await conn.listenForPeers(50060, 'http://localhost:40041/api/'); + + expect(firstServer.forceShutdown).toHaveBeenCalled(); + }); + + test('should use default baseAPIPath if not provided', async () => { + await conn.listenForPeers(50061); + + expect(capturedServerImpl).toBeTruthy(); + expect(typeof capturedServerImpl.Fetch).toBe('function'); + }); + }); }); diff --git a/Tokenization/backend/wrapper/src/utils/connection/peerListener.ts b/Tokenization/backend/wrapper/src/utils/connection/peerListener.ts index ee73b3e82..18a3238e0 100644 --- a/Tokenization/backend/wrapper/src/utils/connection/peerListener.ts +++ b/Tokenization/backend/wrapper/src/utils/connection/peerListener.ts @@ -15,7 +15,7 @@ import * as grpc from '@grpc/grpc-js'; import type { ConnectionDirection } from '../../models/message.model'; import { ConnectionStatus } from '../../models/connection.model'; import type { Connection } from '../../client/connection/Connection'; -import { gRPCAuthInterceptor } from '../../client/connectionManager/Interceptors/grpc.auth.interceptor'; +import { gRPCAuthInterceptor } from '../../client/connectionManager/interceptors/grpc.auth.interceptor'; import type { SecurityContext } from '../security/SecurityContext'; /** From 722fe438503682fab5dcff3f16b076ff75018c81 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 4 Dec 2025 23:02:29 +0100 Subject: [PATCH 097/103] fix: package-lock reinstall for pipeline --- .../backend/wrapper/package-lock.json | 775 +++++++++++++++++- 1 file changed, 745 insertions(+), 30 deletions(-) diff --git a/Tokenization/backend/wrapper/package-lock.json b/Tokenization/backend/wrapper/package-lock.json index 5b761ab79..83324580a 100644 --- a/Tokenization/backend/wrapper/package-lock.json +++ b/Tokenization/backend/wrapper/package-lock.json @@ -19,7 +19,9 @@ "@stylistic/eslint-plugin-js": "^4.4.1", "@stylistic/eslint-plugin-ts": "^4.4.1", "@types/jest": "^29.5.14", - "@types/jsonwebtoken": "^9.0.10", + "eslint": "^9.39.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-jsdoc": "^61.1.12", "jest": "^29.7.0", "ts-jest": "^29.4.0", "tsc-alias": "^1.8.16", @@ -1516,28 +1518,10 @@ "pretty-format": "^29.0.0" } }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.10", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", - "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*", - "@types/node": "*" - } - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, "license": "MIT" }, @@ -1845,6 +1829,19 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -2129,6 +2126,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/body-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2216,6 +2237,44 @@ "dev": true, "license": "MIT" }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2409,6 +2468,28 @@ "dev": true, "license": "MIT" }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2416,6 +2497,24 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -2463,7 +2562,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2509,6 +2607,15 @@ "node": ">=0.10.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2551,6 +2658,26 @@ "node": ">=8" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -2593,6 +2720,15 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2603,6 +2739,36 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2612,6 +2778,12 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2913,6 +3085,15 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -2963,6 +3144,49 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3080,6 +3304,27 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -3115,6 +3360,24 @@ "dev": true, "license": "ISC" }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3126,7 +3389,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3151,6 +3413,30 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -3161,6 +3447,19 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -3253,6 +3552,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3277,11 +3588,22 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3314,6 +3636,26 @@ "dev": true, "license": "MIT" }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -3324,6 +3666,22 @@ "node": ">=10.17.0" } }, + "node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3407,9 +3765,17 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -3498,6 +3864,12 @@ "node": ">=0.12.0" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -4456,6 +4828,36 @@ "tmpl": "1.0.5" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -4487,6 +4889,31 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -4514,7 +4941,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/mylas": { @@ -4538,6 +4964,15 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -4582,11 +5017,34 @@ "dev": true, "license": "MIT" }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -4730,6 +5188,15 @@ "dev": true, "license": "MIT" }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4767,6 +5234,16 @@ "dev": true, "license": "MIT" }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -4909,6 +5386,19 @@ "node": ">=12.0.0" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4936,6 +5426,21 @@ ], "license": "MIT" }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-lit": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", @@ -4967,6 +5472,30 @@ ], "license": "MIT" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -5084,6 +5613,22 @@ "node": ">=0.10.0" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5108,6 +5653,12 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -5118,6 +5669,49 @@ "semver": "bin/semver.js" } }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5141,6 +5735,78 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -5241,6 +5907,15 @@ "node": ">=8" } }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -5392,6 +6067,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -5585,6 +6269,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -5628,6 +6326,15 @@ "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "license": "MIT" }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -5690,6 +6397,15 @@ "node": ">=10.12.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -5747,7 +6463,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { From eeb52071574bd076332e47b018a73f0339137f62 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 4 Dec 2025 23:07:28 +0100 Subject: [PATCH 098/103] fix: remove unused var --- .../ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts index fb8d62c6e..70d2b86f7 100644 --- a/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/ConnectionManager/Interceptors/grpc.auth.interceptor.test.ts @@ -349,7 +349,6 @@ describe('gRPCAuthInterceptor', () => { const result = await interceptor.gRPCAuthInterceptor(mockCall, mockCallback, mockClientConnections as any, mockSecurityContext); - const created = getCreatedConn(); expect(result.isAuthenticated).toBe(false); expect(mockCallback).toHaveBeenCalledWith( expect.objectContaining({ From e35533b20c7ca236526808054b545988bc8f088f Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 4 Dec 2025 23:14:16 +0100 Subject: [PATCH 099/103] fix: tests --- .../interceptors/grpc.auth.interceptor.ts | 289 ++++++++ .../grpc.auth.interceptor.test.ts | 634 ++++++++++++++++++ 2 files changed, 923 insertions(+) create mode 100644 Tokenization/backend/wrapper/src/client/connectionManager/interceptors/grpc.auth.interceptor.ts create mode 100644 Tokenization/backend/wrapper/src/test/client/connectionManager/interceptors/grpc.auth.interceptor.test.ts diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/interceptors/grpc.auth.interceptor.ts b/Tokenization/backend/wrapper/src/client/connectionManager/interceptors/grpc.auth.interceptor.ts new file mode 100644 index 000000000..c3a353eb2 --- /dev/null +++ b/Tokenization/backend/wrapper/src/client/connectionManager/interceptors/grpc.auth.interceptor.ts @@ -0,0 +1,289 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import * as grpc from '@grpc/grpc-js'; +import { Connection } from '../../connection/Connection'; +import { importPKCS8, importJWK, compactDecrypt, compactVerify } from 'jose'; +import type { TokenPayload } from '../../../models/connection.model'; +import { ConnectionStatus } from '../../../models/connection.model'; +import { ConnectionDirection } from '../../../models/message.model'; +import type { SecurityContext } from '../../../utils/security/SecurityContext'; + +/** + * Interceptor function responsible for JWE decryption, JWS verification, + * certificate serial number matching (mTLS binding), and basic authorization. + */ +export const gRPCAuthInterceptor = async ( + call: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData, + clientConnections: Map, + securityContext: SecurityContext +): Promise<{ isAuthenticated: boolean; conn: Connection | null }> => { + const metadata = call.metadata.getMap(); + const jweToken = metadata.jwetoken as string; + const clientAddress = call.getPeer(); + let conn = clientConnections.get(clientAddress); + const peerCert = getPeerCertFromCall(call); + + // Check if token exists + if (!jweToken) { + const error = { + name: 'AuthenticationError', + message: 'No token provided', + code: grpc.status.UNAUTHENTICATED, + }; + callback(error, null); + return { isAuthenticated: false, conn: null }; + } + + // Check if connection exists + if (conn) { + // Check if connection is blocked + if (conn.status === ConnectionStatus.BLOCKED) { + const error = { + name: 'AuthenticationError', + message: 'Connection is blocked. Contact administrator.', + code: grpc.status.UNAUTHENTICATED, + }; + callback(error, null); + return { isAuthenticated: false, conn }; + } + + if (conn.token === jweToken) { + // Check for allowed requests and serial number match if token is the same + if (!isRequestAllowed(conn.cachedTokenPayload, call.request, callback)) { + return { isAuthenticated: false, conn }; + } + + if (!isSerialNumberMatching(conn.cachedTokenPayload, peerCert, callback)) { + conn.handleFailedAuth(); + return { isAuthenticated: false, conn }; + } + + return { isAuthenticated: true, conn }; + } + } else { + conn = new Connection(jweToken, clientAddress, ConnectionDirection.RECEIVING); + clientConnections.set(clientAddress, conn); + } + + // New connection - need to authenticate + // JWE decryption (RSA-OAEP-256) -> JWS (Plaintext) + let privateKey: any; + let jwsToken: string; + try { + // Importing RSA private key for decryption + privateKey = await importPKCS8(securityContext.clientPrivateKey.toString('utf-8'), 'RSA-OAEP-256'); + + const { plaintext } = await compactDecrypt(jweToken, privateKey); + jwsToken = new TextDecoder().decode(plaintext).trim(); + } catch (e) { + void e; + const error = { + name: 'AuthenticationError', + message: 'Incorrect token provided (JWE Decryption failed)', + code: grpc.status.UNAUTHENTICATED, + }; + + // TODO: Consider logging or informing a central security system about potential attack/misconfiguration. + callback(error, null); + conn.handleFailedAuth(); + return { isAuthenticated: false, conn }; + } + + // Verify JWS (With signature) and payload extraction + let pub: any; + let payload: TokenPayload; + + try { + // Convert a raw Base64 Ed25519 public key to JWK format + const jwk = { + kty: 'OKP', + crv: 'Ed25519', + x: Buffer.from(securityContext.JWS_PUBLIC_KEY, 'base64').toString('base64url'), + }; + + // Importing the Ed25519 public key for verification - using "EdDSA" algorithm + pub = await importJWK(jwk, 'EdDSA'); + + // Compact verify - verify with key and decode the JWS token in one step + const { payload: jwtPayload, protectedHeader } = await compactVerify(jwsToken, pub); + + // Additional check to ensure correct signing algorithm was used + if (protectedHeader.alg !== 'EdDSA' && protectedHeader.alg !== 'Ed25519') { + const error = { + name: 'AuthenticationError', + message: 'Incorrect signing algorithm for JWS.', + code: grpc.status.UNAUTHENTICATED, + }; + + callback(error, null); + return { isAuthenticated: false, conn }; + } + + // Decode and parse the JWT payload + const payloadString = new TextDecoder().decode(jwtPayload); + payload = JSON.parse(payloadString); + } catch { + const error = { + name: 'AuthenticationError', + message: `JWS Verification error: Invalid signature`, + code: grpc.status.PERMISSION_DENIED, + }; + // TODO: Consider logging or informing a central security system about failed verification. + callback(error, null); + + conn.handleFailedAuth(); + return { isAuthenticated: false, conn }; + } + + // Binding mTLS check and authorization + // Connection tunnel verification with serialNumber (mTLS SN vs Token SN) + if (!isSerialNumberMatching(payload, peerCert, callback)) { + conn.handleFailedAuth(); + return { isAuthenticated: false, conn }; + } + + // Validate permission for request method (Authorization check) + if (!isRequestAllowed(payload, call.request, callback)) { + return { isAuthenticated: false, conn }; + } + + // Authentication and Authorization successful + // Update Connection state with SN and status + conn.handleSuccessfulAuth(payload); + return { isAuthenticated: true, conn }; +}; + +/** + * Checks if the request method is allowed based on the token permissions. + * @param tokenPayload payload extracted from the token + * @param request gRPC request object containing method information + * @param callback callback to return gRPC error if needed + * @returns true if request method is allowed, false otherwise + */ +export const isRequestAllowed = (tokenPayload: TokenPayload | undefined, request: any, callback: grpc.sendUnaryData): boolean => { + const method = String(request?.method ?? 'POST').toUpperCase(); + const isValidPayload = validateTokenPayload(tokenPayload, method); + let isExpired = false; + + if (isValidPayload) { + isExpired = isPermissionExpired(tokenPayload.iat[method], tokenPayload.exp[method]); + } + + if (!isValidPayload || isExpired) { + const error = { + name: 'AuthorizationError', + code: isExpired ? grpc.status.UNAUTHENTICATED : grpc.status.PERMISSION_DENIED, + message: isExpired + ? `Request of type ${method}, permission has expired.` + : `Request of type ${method} is not allowed by the token policy.`, + } as any; + + callback(error, null); + return false; + } + + return true; +}; + +/** + * Validates the structure and types of the token payload. + * @returns true if token payload is valid, false otherwise + */ +const validateTokenPayload = (tokenPayload: TokenPayload | undefined, method: string): tokenPayload is TokenPayload => { + if (!tokenPayload) { + return false; + } + + if ( + typeof tokenPayload.iat !== 'object' || + typeof tokenPayload.exp !== 'object' || + typeof tokenPayload.sub !== 'string' || + typeof tokenPayload.aud !== 'string' || + typeof tokenPayload.iss !== 'string' || + typeof tokenPayload.jti !== 'string' || + Object.keys(tokenPayload.iat).length === 0 || + Object.keys(tokenPayload.exp).length === 0 || + !Object.prototype.hasOwnProperty.call(tokenPayload.iat, method) || + !Object.prototype.hasOwnProperty.call(tokenPayload.exp, method) + ) { + return false; + } + + return true; +}; + +/** + * Checks if the permissions granted in the token have expired. + * @param iat issued-at timestamp for the specific method + * @param exp expiration timestamp for the specific method + * @returns true if permission is expired + */ +export const isPermissionExpired = (iat: number, exp: number): boolean => { + const nowInSeconds = Math.floor(Date.now() / 1000); + + if (nowInSeconds >= exp) { + return true; + } + + if (iat > nowInSeconds) { + return true; + } + + return false; +}; + +/** + * Checks if the serial number from the peer certificate matches the one in the token payload. + * @param tokenPayload payload extracted from the token + * @param peerCert certificate object retrieved from the gRPC call + * @param callback callback to return gRPC error if needed + * @returns true if serial numbers match, false otherwise + */ +export const isSerialNumberMatching = (tokenPayload: TokenPayload | undefined, peerCert: any, callback: grpc.sendUnaryData): boolean => { + const clientSN = normalizeSerial(peerCert?.serialNumber); + const tokenSN = normalizeSerial(tokenPayload?.sub); + + if (!clientSN || clientSN !== tokenSN) { + const error = { + name: 'AuthenticationError', + code: grpc.status.PERMISSION_DENIED, + message: 'Serial number mismatch (mTLS binding failure).', + } as any; + callback(error, null); + return false; + } + return true; +}; + +/** + * Normalizes a certificate serial number by removing colons and converting to uppercase. + * @param sn serial number string possibly containing colons or being null/undefined + * @returns normalized serial number string + */ +const normalizeSerial = (sn?: string | null): string => + // Node retrieves serial number as hex string, without leading 0x and with possible colons so we need to normalize it + (sn ?? '').replace(/[^0-9a-f]/gi, '').toUpperCase(); + +/** + * Retrieves the peer certificate from the gRPC call object. + * @param call gRPC call object + * @returns peer certificate object from the gRPC call + */ +export const getPeerCertFromCall = (call: any) => { + const session = call?.call?.stream?.session; + const sock = session?.socket as any; + return sock?.getPeerCertificate(true); // Whole certificate info from TLS socket +}; diff --git a/Tokenization/backend/wrapper/src/test/client/connectionManager/interceptors/grpc.auth.interceptor.test.ts b/Tokenization/backend/wrapper/src/test/client/connectionManager/interceptors/grpc.auth.interceptor.test.ts new file mode 100644 index 000000000..70d2b86f7 --- /dev/null +++ b/Tokenization/backend/wrapper/src/test/client/connectionManager/interceptors/grpc.auth.interceptor.test.ts @@ -0,0 +1,634 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import * as grpc from '@grpc/grpc-js'; +import * as jose from 'jose'; +import * as interceptor from '../../../../client/connectionManager/interceptors/grpc.auth.interceptor'; + +// Connection class mock +jest.mock( + '../../../../client/connection/Connection', + () => { + return { + Connection: jest.fn().mockImplementation((jweToken: string, address: string, direction: any) => { + return { + jweToken, + address, + direction, + status: 1, + payload: { subSerialNumber: 'AABBCC', perm: { POST: true } }, + getStatus: jest.fn(function () { + return this.status; + }), + getToken: jest.fn(function () { + return this.jweToken; + }), + getCachedTokenPayload: jest.fn(function () { + return this.payload; + }), + handleFailedAuth: jest.fn(), + handleSuccessfulAuth: jest.fn(function (p: any) { + this.payload = p; + this.status = 1; + }), + }; + }), + }; + }, + { virtual: true } +); + +import { Connection } from '../../../../client/connection/Connection'; + +jest.mock('jose', () => ({ + importPKCS8: jest.fn(), + importJWK: jest.fn(), + compactDecrypt: jest.fn(), + compactVerify: jest.fn(), +})); + +import { ConnectionStatus, TokenPayload } from '../../../../models/connection.model'; +import { SecurityContext } from '../../../../utils/security/SecurityContext'; +import { ConnectionDirection } from '../../../../models/message.model'; + +const mockSecurityContext = { + clientPrivateKey: Buffer.from('mock_private_key_rsa'), + JWS_PUBLIC_KEY: 'mock_public_key_ed25519', +} as unknown as SecurityContext; + +let isRequestAllowedSpy: jest.SpyInstance; +let isSerialNumberMatchingSpy: jest.SpyInstance; +let getPeerCertFromCallSpy: jest.SpyInstance; + +const mockCall = { + metadata: { getMap: jest.fn(() => ({})) }, + getPeer: jest.fn(() => 'ipv4:127.0.0.1:12345'), + request: { method: 'POST' }, +} as unknown as grpc.ServerUnaryCall; + +const mockCallback = jest.fn(); +const mockClientConnections = new Map(); + +describe('gRPCAuthInterceptor', () => { + const MOCK_ADDRESS = 'ipv4:127.0.0.1:12345'; + const VALID_JWE = 'valid.jwe.token'; + const VALID_JWS = 'valid.jws.token'; + const DECRYPTED_PAYLOAD: TokenPayload = { + subSerialNumber: 'DDEEFF', + perm: { POST: true, GET: false }, + } as any; + + beforeEach(() => { + jest.clearAllMocks(); + mockClientConnections.clear(); + + (mockCall.metadata.getMap as unknown as jest.Mock).mockReturnValue({ + jwetoken: VALID_JWE, + }); + (mockCall.getPeer as unknown as jest.Mock).mockReturnValue(MOCK_ADDRESS); + (mockCall as any).request = { method: 'POST' }; + + (jose.importPKCS8 as jest.Mock).mockResolvedValue('mock_priv_key'); + (jose.compactDecrypt as jest.Mock).mockResolvedValue({ + plaintext: Buffer.from(VALID_JWS), + }); + (jose.importJWK as jest.Mock).mockResolvedValue('mock_pub_key'); + (jose.compactVerify as jest.Mock).mockResolvedValue({ + payload: Buffer.from(JSON.stringify(DECRYPTED_PAYLOAD)), + protectedHeader: { alg: 'EdDSA' }, + }); + + // mocks of internal functions + isRequestAllowedSpy = jest.spyOn(interceptor, 'isRequestAllowed').mockImplementation((_p, _r, _cb) => true); + + isSerialNumberMatchingSpy = jest.spyOn(interceptor, 'isSerialNumberMatching').mockImplementation((_p, _pc, _cb) => true); + + getPeerCertFromCallSpy = jest.spyOn(interceptor, 'getPeerCertFromCall').mockReturnValue({ serialNumber: 'DDEEFF' }); + }); + + const getCreatedConn = () => { + const instances = (Connection as jest.Mock).mock?.instances ?? []; + return instances.find((i: any) => i.address === MOCK_ADDRESS) ?? mockClientConnections.get(MOCK_ADDRESS); + }; + + it('should fail if no JWE token is provided in the metadata', async () => { + (mockCall.metadata.getMap as unknown as jest.Mock).mockReturnValue({}); + + const result = await interceptor.gRPCAuthInterceptor(mockCall, mockCallback, mockClientConnections as any, mockSecurityContext); + + expect(result.isAuthenticated).toBe(false); + expect(result.conn).toBe(null); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + code: grpc.status.UNAUTHENTICATED, + message: 'No token provided', + }), + null + ); + }); + + it("should authenticate instantly if connection exists and token hasn't changed", async () => { + const existingConn = new (Connection as jest.Mock)(VALID_JWE, MOCK_ADDRESS, ConnectionDirection.RECEIVING); + existingConn.getToken.mockReturnValue(VALID_JWE); + mockClientConnections.set(MOCK_ADDRESS, existingConn); + + const result = await interceptor.gRPCAuthInterceptor(mockCall, mockCallback, mockClientConnections as any, mockSecurityContext); + + expect(result.isAuthenticated).toBe(true); + expect(result.conn).toBe(existingConn); + expect(isRequestAllowedSpy).toHaveBeenCalledTimes(1); + expect(isSerialNumberMatchingSpy).toHaveBeenCalledTimes(1); + expect(jose.compactDecrypt as jest.Mock).toHaveBeenCalledTimes(1); + expect(jose.compactDecrypt as jest.Mock).toHaveBeenCalledWith(VALID_JWE, 'mock_priv_key'); + }); + + it('should reject if connection exists but is BLOCKED', async () => { + const existingConn = new (Connection as jest.Mock)(VALID_JWE, MOCK_ADDRESS, ConnectionDirection.RECEIVING); + existingConn.status = ConnectionStatus.BLOCKED; + mockClientConnections.set(MOCK_ADDRESS, existingConn); + + const result = await interceptor.gRPCAuthInterceptor(mockCall, mockCallback, mockClientConnections as any, mockSecurityContext); + + expect(result.isAuthenticated).toBe(false); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + code: grpc.status.UNAUTHENTICATED, + message: 'Connection is blocked. Contact administrator.', + }), + null + ); + }); + + it('should reject existing connection on serial number mismatch', async () => { + const existingConn = new (Connection as jest.Mock)(VALID_JWE, MOCK_ADDRESS, ConnectionDirection.RECEIVING); + existingConn.getToken.mockReturnValue(VALID_JWE); + mockClientConnections.set(MOCK_ADDRESS, existingConn); + + // mock serial number mismatch + isSerialNumberMatchingSpy.mockImplementation((_p, _pc, cb) => { + cb( + { + name: 'AuthenticationError', + code: grpc.status.PERMISSION_DENIED, + message: 'Serial number mismatch (mTLS binding failure).', + } as any, + null + ); + return false; + }); + + const result = await interceptor.gRPCAuthInterceptor(mockCall, mockCallback, mockClientConnections as any, mockSecurityContext); + + expect(result.isAuthenticated).toBe(false); + expect(existingConn.handleFailedAuth).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Serial number mismatch (mTLS binding failure).', + }), + null + ); + }); + + it('should successfully authenticate a NEW connection', async () => { + (mockCall.metadata.getMap as unknown as jest.Mock).mockReturnValue({ + jwetoken: 'NEW.JWE.TOKEN', + }); + + const result = await interceptor.gRPCAuthInterceptor(mockCall, mockCallback, mockClientConnections as any, mockSecurityContext); + + const created = getCreatedConn(); + expect(result.isAuthenticated).toBe(true); + expect(created).toBeDefined(); + expect(created!.handleSuccessfulAuth).toHaveBeenCalledWith(DECRYPTED_PAYLOAD); + expect(jose.compactDecrypt as jest.Mock).toHaveBeenCalledTimes(1); + expect(jose.compactVerify as jest.Mock).toHaveBeenCalledTimes(1); + expect(isSerialNumberMatchingSpy).toHaveBeenCalledTimes(1); + expect(isRequestAllowedSpy).toHaveBeenCalledTimes(1); + }); + + it('should fail if JWE decryption fails', async () => { + (jose.compactDecrypt as jest.Mock).mockRejectedValue(new Error('Decryption failed')); + + const result = await interceptor.gRPCAuthInterceptor(mockCall, mockCallback, mockClientConnections as any, mockSecurityContext); + + const created = getCreatedConn(); + expect(result.isAuthenticated).toBe(false); + expect(created!.handleFailedAuth).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Incorrect token provided (JWE Decryption failed)', + }), + null + ); + }); + + it('should fail if JWS verification fails', async () => { + (jose.compactVerify as jest.Mock).mockRejectedValue(new Error('Invalid signature')); + + const result = await interceptor.gRPCAuthInterceptor(mockCall, mockCallback, mockClientConnections as any, mockSecurityContext); + + const created = getCreatedConn(); + expect(result.isAuthenticated).toBe(false); + expect(created!.handleFailedAuth).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'JWS Verification error: Invalid signature', + }), + null + ); + }); + + it('should fail if mTLS serial number mismatch occurs after decryption', async () => { + isSerialNumberMatchingSpy.mockImplementation((_p, _pc, cb) => { + cb( + { + name: 'AuthenticationError', + code: grpc.status.PERMISSION_DENIED, + message: 'Serial number mismatch (mTLS binding failure).', + } as any, + null + ); + return false; + }); + + const result = await interceptor.gRPCAuthInterceptor(mockCall, mockCallback, mockClientConnections as any, mockSecurityContext); + + const created = getCreatedConn(); + expect(result.isAuthenticated).toBe(false); + expect(created!.handleFailedAuth).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Serial number mismatch (mTLS binding failure).', + }), + null + ); + }); + + it('should fail if request authorization check fails', async () => { + isRequestAllowedSpy.mockImplementation((_p, _r, cb) => { + cb( + { + name: 'AuthorizationError', + code: grpc.status.PERMISSION_DENIED, + message: 'Request of type POST is not allowed by the token policy.', + } as any, + null + ); + return false; + }); + + const result = await interceptor.gRPCAuthInterceptor(mockCall, mockCallback, mockClientConnections as any, mockSecurityContext); + + expect(result.isAuthenticated).toBe(false); + expect(mockCallback).toHaveBeenCalledWith(expect.objectContaining({ code: grpc.status.PERMISSION_DENIED }), null); + expect(isSerialNumberMatchingSpy).toHaveBeenCalledTimes(1); + }); + it('should reject if existing connection has request not allowed', async () => { + const existingConn = new (Connection as jest.Mock)(VALID_JWE, MOCK_ADDRESS, ConnectionDirection.RECEIVING); + existingConn.getToken.mockReturnValue(VALID_JWE); + mockClientConnections.set(MOCK_ADDRESS, existingConn); + + // mock request not allowed + isRequestAllowedSpy.mockImplementation((_p, _r, cb) => { + cb( + { + name: 'AuthorizationError', + code: grpc.status.PERMISSION_DENIED, + message: 'Request of type POST is not allowed by the token policy.', + } as any, + null + ); + return false; + }); + + const result = await interceptor.gRPCAuthInterceptor(mockCall, mockCallback, mockClientConnections as any, mockSecurityContext); + + expect(result.isAuthenticated).toBe(false); + expect(result.conn).toBe(existingConn); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Request of type POST is not allowed by the token policy.', + }), + null + ); + }); + + it('should re-authenticate when existing connection has different token', async () => { + const existingConn = new (Connection as jest.Mock)('OLD.TOKEN', MOCK_ADDRESS, ConnectionDirection.RECEIVING); + existingConn.getToken.mockReturnValue('OLD.TOKEN'); + mockClientConnections.set(MOCK_ADDRESS, existingConn); + + (mockCall.metadata.getMap as unknown as jest.Mock).mockReturnValue({ + jwetoken: 'NEW.TOKEN', + }); + + const result = await interceptor.gRPCAuthInterceptor(mockCall, mockCallback, mockClientConnections as any, mockSecurityContext); + + expect(result.isAuthenticated).toBe(true); + expect(existingConn.handleSuccessfulAuth).toHaveBeenCalledWith(DECRYPTED_PAYLOAD); + expect(jose.compactDecrypt as jest.Mock).toHaveBeenCalledTimes(1); + expect(jose.compactVerify as jest.Mock).toHaveBeenCalledTimes(1); + }); + + it('should fail if JWS has incorrect signing algorithm', async () => { + (jose.compactVerify as jest.Mock).mockResolvedValue({ + payload: Buffer.from(JSON.stringify(DECRYPTED_PAYLOAD)), + protectedHeader: { alg: 'RS256' }, // Wrong algorithm + }); + + const result = await interceptor.gRPCAuthInterceptor(mockCall, mockCallback, mockClientConnections as any, mockSecurityContext); + + expect(result.isAuthenticated).toBe(false); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Incorrect signing algorithm for JWS.', + code: grpc.status.UNAUTHENTICATED, + }), + null + ); + }); +}); + +describe('isRequestAllowed', () => { + const mockCallback = jest.fn(); + + beforeEach(() => { + jest.restoreAllMocks(); + jest.clearAllMocks(); + }); + + it('should return true for valid payload with unexpired permission', () => { + const now = Math.floor(Date.now() / 1000); + const payload: TokenPayload = { + sub: 'AABBCC', + aud: 'test-audience', + iss: 'test-issuer', + jti: 'test-jti', + iat: { POST: now - 100 }, + exp: { POST: now + 3600 }, + } as any; + + const request = { method: 'POST' }; + const result = interceptor.isRequestAllowed(payload, request, mockCallback); + + expect(result).toBe(true); + expect(mockCallback).not.toHaveBeenCalled(); + }); + + it('should return false for expired permission', () => { + const now = Math.floor(Date.now() / 1000); + const payload: TokenPayload = { + sub: 'AABBCC', + aud: 'test-audience', + iss: 'test-issuer', + jti: 'test-jti', + iat: { POST: now - 7200 }, + exp: { POST: now - 3600 }, // Expired 1 hour ago + } as any; + + const request = { method: 'POST' }; + const result = interceptor.isRequestAllowed(payload, request, mockCallback); + + expect(result).toBe(false); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + code: grpc.status.UNAUTHENTICATED, + message: 'Request of type POST, permission has expired.', + }), + null + ); + }); + + it('should return false for method not in token permissions', () => { + const now = Math.floor(Date.now() / 1000); + const payload: TokenPayload = { + sub: 'AABBCC', + aud: 'test-audience', + iss: 'test-issuer', + jti: 'test-jti', + iat: { POST: now - 100 }, + exp: { POST: now + 3600 }, + } as any; + + const request = { method: 'DELETE' }; // Not in permissions + const result = interceptor.isRequestAllowed(payload, request, mockCallback); + + expect(result).toBe(false); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + code: grpc.status.PERMISSION_DENIED, + message: 'Request of type DELETE is not allowed by the token policy.', + }), + null + ); + }); + + it('should handle missing request method with default POST', () => { + const now = Math.floor(Date.now() / 1000); + const payload: TokenPayload = { + sub: 'AABBCC', + aud: 'test-audience', + iss: 'test-issuer', + jti: 'test-jti', + iat: { POST: now - 100 }, + exp: { POST: now + 3600 }, + } as any; + + const request = {}; // No method + const result = interceptor.isRequestAllowed(payload, request, mockCallback); + + expect(result).toBe(true); + }); + + it('should return false for invalid payload structure (missing iat)', () => { + const now = Math.floor(Date.now() / 1000); + const payload: any = { + sub: 'AABBCC', + aud: 'test-audience', + iss: 'test-issuer', + jti: 'test-jti', + exp: { POST: now + 3600 }, + // iat missing + }; + + const request = { method: 'POST' }; + const result = interceptor.isRequestAllowed(payload, request, mockCallback); + + expect(result).toBe(false); + }); + + it('should return false for invalid payload structure (empty iat)', () => { + const now = Math.floor(Date.now() / 1000); + const payload: any = { + sub: 'AABBCC', + aud: 'test-audience', + iss: 'test-issuer', + jti: 'test-jti', + iat: {}, // Empty + exp: { POST: now + 3600 }, + }; + + const request = { method: 'POST' }; + const result = interceptor.isRequestAllowed(payload, request, mockCallback); + + expect(result).toBe(false); + }); +}); + +describe('isPermissionExpired', () => { + it('should return false for valid unexpired permission', () => { + const now = Math.floor(Date.now() / 1000); + const iat = now - 100; + const exp = now + 3600; + + const result = interceptor.isPermissionExpired(iat, exp); + + expect(result).toBe(false); + }); + + it('should return true when permission has expired', () => { + const now = Math.floor(Date.now() / 1000); + const iat = now - 7200; + const exp = now - 3600; // Expired 1 hour ago + + const result = interceptor.isPermissionExpired(iat, exp); + + expect(result).toBe(true); + }); + + it('should return true when iat is in the future', () => { + const now = Math.floor(Date.now() / 1000); + const iat = now + 100; // Issued in the future + const exp = now + 3600; + + const result = interceptor.isPermissionExpired(iat, exp); + + expect(result).toBe(true); + }); +}); + +describe('isSerialNumberMatching', () => { + const mockCallback = jest.fn(); + + beforeEach(() => { + jest.restoreAllMocks(); + jest.clearAllMocks(); + }); + + it('should return true when serial numbers match', () => { + const payload: TokenPayload = { + sub: 'AABBCCDDEE', + } as any; + const peerCert = { serialNumber: 'AA:BB:CC:DD:EE' }; + + const result = interceptor.isSerialNumberMatching(payload, peerCert, mockCallback); + + expect(result).toBe(true); + expect(mockCallback).not.toHaveBeenCalled(); + }); + + it('should return true when serial numbers match (different formats)', () => { + const payload: TokenPayload = { + sub: 'aabbccddee', + } as any; + const peerCert = { serialNumber: 'AA:BB:CC:DD:EE' }; + + const result = interceptor.isSerialNumberMatching(payload, peerCert, mockCallback); + + expect(result).toBe(true); + }); + + it('should return false when serial numbers do not match', () => { + const payload: TokenPayload = { + sub: 'AABBCCDDEE', + } as any; + const peerCert = { serialNumber: '11:22:33:44:55' }; + + const result = interceptor.isSerialNumberMatching(payload, peerCert, mockCallback); + + expect(result).toBe(false); + expect(mockCallback).toHaveBeenCalledWith( + expect.objectContaining({ + code: grpc.status.PERMISSION_DENIED, + message: 'Serial number mismatch (mTLS binding failure).', + }), + null + ); + }); + + it('should return false when peerCert is null', () => { + const payload: TokenPayload = { + sub: 'AABBCCDDEE', + } as any; + + const result = interceptor.isSerialNumberMatching(payload, null, mockCallback); + + expect(result).toBe(false); + expect(mockCallback).toHaveBeenCalled(); + }); + + it('should return false when payload is undefined', () => { + const peerCert = { serialNumber: 'AA:BB:CC:DD:EE' }; + + const result = interceptor.isSerialNumberMatching(undefined, peerCert, mockCallback); + + expect(result).toBe(false); + }); + + it('should normalize serial numbers with special characters', () => { + const payload: TokenPayload = { + sub: 'AA-BB-CC-DD-EE', + } as any; + const peerCert = { serialNumber: 'AA:BB:CC:DD:EE' }; + + const result = interceptor.isSerialNumberMatching(payload, peerCert, mockCallback); + + expect(result).toBe(true); + }); +}); + +describe('getPeerCertFromCall', () => { + beforeEach(() => { + jest.restoreAllMocks(); + jest.clearAllMocks(); + }); + + it('should return peer certificate from call', () => { + const mockCert = { serialNumber: 'AABBCC', subject: 'CN=test' }; + const mockCall = { + call: { + stream: { + session: { + socket: { + getPeerCertificate: jest.fn().mockReturnValue(mockCert), + }, + }, + }, + }, + }; + + const result = interceptor.getPeerCertFromCall(mockCall); + + expect(result).toBe(mockCert); + expect(mockCall.call.stream.session.socket.getPeerCertificate).toHaveBeenCalledWith(true); + }); + + it('should handle missing call structure gracefully', () => { + const mockCall = {}; + + const result = interceptor.getPeerCertFromCall(mockCall); + + expect(result).toBeUndefined(); + }); +}); From bc520d2f93d88731c9c9905a48be4a3e72324a80 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 4 Dec 2025 23:17:41 +0100 Subject: [PATCH 100/103] fix: temp rename --- .../interceptors/grpc.auth.interceptor.ts | 289 ------------------ .../grpc.auth.interceptor.ts | 0 .../src/test/connection/Connection.test.ts | 8 + 3 files changed, 8 insertions(+), 289 deletions(-) delete mode 100644 Tokenization/backend/wrapper/src/client/connectionManager/interceptors/grpc.auth.interceptor.ts rename Tokenization/backend/wrapper/src/client/{ConnectionManager/Interceptors => connectionManager/interceptors_tmp}/grpc.auth.interceptor.ts (100%) diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/interceptors/grpc.auth.interceptor.ts b/Tokenization/backend/wrapper/src/client/connectionManager/interceptors/grpc.auth.interceptor.ts deleted file mode 100644 index c3a353eb2..000000000 --- a/Tokenization/backend/wrapper/src/client/connectionManager/interceptors/grpc.auth.interceptor.ts +++ /dev/null @@ -1,289 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -import * as grpc from '@grpc/grpc-js'; -import { Connection } from '../../connection/Connection'; -import { importPKCS8, importJWK, compactDecrypt, compactVerify } from 'jose'; -import type { TokenPayload } from '../../../models/connection.model'; -import { ConnectionStatus } from '../../../models/connection.model'; -import { ConnectionDirection } from '../../../models/message.model'; -import type { SecurityContext } from '../../../utils/security/SecurityContext'; - -/** - * Interceptor function responsible for JWE decryption, JWS verification, - * certificate serial number matching (mTLS binding), and basic authorization. - */ -export const gRPCAuthInterceptor = async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - clientConnections: Map, - securityContext: SecurityContext -): Promise<{ isAuthenticated: boolean; conn: Connection | null }> => { - const metadata = call.metadata.getMap(); - const jweToken = metadata.jwetoken as string; - const clientAddress = call.getPeer(); - let conn = clientConnections.get(clientAddress); - const peerCert = getPeerCertFromCall(call); - - // Check if token exists - if (!jweToken) { - const error = { - name: 'AuthenticationError', - message: 'No token provided', - code: grpc.status.UNAUTHENTICATED, - }; - callback(error, null); - return { isAuthenticated: false, conn: null }; - } - - // Check if connection exists - if (conn) { - // Check if connection is blocked - if (conn.status === ConnectionStatus.BLOCKED) { - const error = { - name: 'AuthenticationError', - message: 'Connection is blocked. Contact administrator.', - code: grpc.status.UNAUTHENTICATED, - }; - callback(error, null); - return { isAuthenticated: false, conn }; - } - - if (conn.token === jweToken) { - // Check for allowed requests and serial number match if token is the same - if (!isRequestAllowed(conn.cachedTokenPayload, call.request, callback)) { - return { isAuthenticated: false, conn }; - } - - if (!isSerialNumberMatching(conn.cachedTokenPayload, peerCert, callback)) { - conn.handleFailedAuth(); - return { isAuthenticated: false, conn }; - } - - return { isAuthenticated: true, conn }; - } - } else { - conn = new Connection(jweToken, clientAddress, ConnectionDirection.RECEIVING); - clientConnections.set(clientAddress, conn); - } - - // New connection - need to authenticate - // JWE decryption (RSA-OAEP-256) -> JWS (Plaintext) - let privateKey: any; - let jwsToken: string; - try { - // Importing RSA private key for decryption - privateKey = await importPKCS8(securityContext.clientPrivateKey.toString('utf-8'), 'RSA-OAEP-256'); - - const { plaintext } = await compactDecrypt(jweToken, privateKey); - jwsToken = new TextDecoder().decode(plaintext).trim(); - } catch (e) { - void e; - const error = { - name: 'AuthenticationError', - message: 'Incorrect token provided (JWE Decryption failed)', - code: grpc.status.UNAUTHENTICATED, - }; - - // TODO: Consider logging or informing a central security system about potential attack/misconfiguration. - callback(error, null); - conn.handleFailedAuth(); - return { isAuthenticated: false, conn }; - } - - // Verify JWS (With signature) and payload extraction - let pub: any; - let payload: TokenPayload; - - try { - // Convert a raw Base64 Ed25519 public key to JWK format - const jwk = { - kty: 'OKP', - crv: 'Ed25519', - x: Buffer.from(securityContext.JWS_PUBLIC_KEY, 'base64').toString('base64url'), - }; - - // Importing the Ed25519 public key for verification - using "EdDSA" algorithm - pub = await importJWK(jwk, 'EdDSA'); - - // Compact verify - verify with key and decode the JWS token in one step - const { payload: jwtPayload, protectedHeader } = await compactVerify(jwsToken, pub); - - // Additional check to ensure correct signing algorithm was used - if (protectedHeader.alg !== 'EdDSA' && protectedHeader.alg !== 'Ed25519') { - const error = { - name: 'AuthenticationError', - message: 'Incorrect signing algorithm for JWS.', - code: grpc.status.UNAUTHENTICATED, - }; - - callback(error, null); - return { isAuthenticated: false, conn }; - } - - // Decode and parse the JWT payload - const payloadString = new TextDecoder().decode(jwtPayload); - payload = JSON.parse(payloadString); - } catch { - const error = { - name: 'AuthenticationError', - message: `JWS Verification error: Invalid signature`, - code: grpc.status.PERMISSION_DENIED, - }; - // TODO: Consider logging or informing a central security system about failed verification. - callback(error, null); - - conn.handleFailedAuth(); - return { isAuthenticated: false, conn }; - } - - // Binding mTLS check and authorization - // Connection tunnel verification with serialNumber (mTLS SN vs Token SN) - if (!isSerialNumberMatching(payload, peerCert, callback)) { - conn.handleFailedAuth(); - return { isAuthenticated: false, conn }; - } - - // Validate permission for request method (Authorization check) - if (!isRequestAllowed(payload, call.request, callback)) { - return { isAuthenticated: false, conn }; - } - - // Authentication and Authorization successful - // Update Connection state with SN and status - conn.handleSuccessfulAuth(payload); - return { isAuthenticated: true, conn }; -}; - -/** - * Checks if the request method is allowed based on the token permissions. - * @param tokenPayload payload extracted from the token - * @param request gRPC request object containing method information - * @param callback callback to return gRPC error if needed - * @returns true if request method is allowed, false otherwise - */ -export const isRequestAllowed = (tokenPayload: TokenPayload | undefined, request: any, callback: grpc.sendUnaryData): boolean => { - const method = String(request?.method ?? 'POST').toUpperCase(); - const isValidPayload = validateTokenPayload(tokenPayload, method); - let isExpired = false; - - if (isValidPayload) { - isExpired = isPermissionExpired(tokenPayload.iat[method], tokenPayload.exp[method]); - } - - if (!isValidPayload || isExpired) { - const error = { - name: 'AuthorizationError', - code: isExpired ? grpc.status.UNAUTHENTICATED : grpc.status.PERMISSION_DENIED, - message: isExpired - ? `Request of type ${method}, permission has expired.` - : `Request of type ${method} is not allowed by the token policy.`, - } as any; - - callback(error, null); - return false; - } - - return true; -}; - -/** - * Validates the structure and types of the token payload. - * @returns true if token payload is valid, false otherwise - */ -const validateTokenPayload = (tokenPayload: TokenPayload | undefined, method: string): tokenPayload is TokenPayload => { - if (!tokenPayload) { - return false; - } - - if ( - typeof tokenPayload.iat !== 'object' || - typeof tokenPayload.exp !== 'object' || - typeof tokenPayload.sub !== 'string' || - typeof tokenPayload.aud !== 'string' || - typeof tokenPayload.iss !== 'string' || - typeof tokenPayload.jti !== 'string' || - Object.keys(tokenPayload.iat).length === 0 || - Object.keys(tokenPayload.exp).length === 0 || - !Object.prototype.hasOwnProperty.call(tokenPayload.iat, method) || - !Object.prototype.hasOwnProperty.call(tokenPayload.exp, method) - ) { - return false; - } - - return true; -}; - -/** - * Checks if the permissions granted in the token have expired. - * @param iat issued-at timestamp for the specific method - * @param exp expiration timestamp for the specific method - * @returns true if permission is expired - */ -export const isPermissionExpired = (iat: number, exp: number): boolean => { - const nowInSeconds = Math.floor(Date.now() / 1000); - - if (nowInSeconds >= exp) { - return true; - } - - if (iat > nowInSeconds) { - return true; - } - - return false; -}; - -/** - * Checks if the serial number from the peer certificate matches the one in the token payload. - * @param tokenPayload payload extracted from the token - * @param peerCert certificate object retrieved from the gRPC call - * @param callback callback to return gRPC error if needed - * @returns true if serial numbers match, false otherwise - */ -export const isSerialNumberMatching = (tokenPayload: TokenPayload | undefined, peerCert: any, callback: grpc.sendUnaryData): boolean => { - const clientSN = normalizeSerial(peerCert?.serialNumber); - const tokenSN = normalizeSerial(tokenPayload?.sub); - - if (!clientSN || clientSN !== tokenSN) { - const error = { - name: 'AuthenticationError', - code: grpc.status.PERMISSION_DENIED, - message: 'Serial number mismatch (mTLS binding failure).', - } as any; - callback(error, null); - return false; - } - return true; -}; - -/** - * Normalizes a certificate serial number by removing colons and converting to uppercase. - * @param sn serial number string possibly containing colons or being null/undefined - * @returns normalized serial number string - */ -const normalizeSerial = (sn?: string | null): string => - // Node retrieves serial number as hex string, without leading 0x and with possible colons so we need to normalize it - (sn ?? '').replace(/[^0-9a-f]/gi, '').toUpperCase(); - -/** - * Retrieves the peer certificate from the gRPC call object. - * @param call gRPC call object - * @returns peer certificate object from the gRPC call - */ -export const getPeerCertFromCall = (call: any) => { - const session = call?.call?.stream?.session; - const sock = session?.socket as any; - return sock?.getPeerCertificate(true); // Whole certificate info from TLS socket -}; diff --git a/Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts b/Tokenization/backend/wrapper/src/client/connectionManager/interceptors_tmp/grpc.auth.interceptor.ts similarity index 100% rename from Tokenization/backend/wrapper/src/client/ConnectionManager/Interceptors/grpc.auth.interceptor.ts rename to Tokenization/backend/wrapper/src/client/connectionManager/interceptors_tmp/grpc.auth.interceptor.ts diff --git a/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts b/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts index 212c1c402..f6074fd73 100644 --- a/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts +++ b/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts @@ -27,6 +27,11 @@ const PeerCtorMock = jest.fn((_addr: string, _creds: any) => { return lastPeerClient; }); +const logger = { + infoMessage: jest.fn(), + errorMessage: jest.fn(), +}; + jest.mock( '@grpc/grpc-js', () => { @@ -36,6 +41,9 @@ jest.mock( credentials: { createSsl: jest.fn(() => ({ insecure: true })), }, + LogManager: { + getLogger: () => logger, + }, }; }, { virtual: true } From d2234c4e9f981aea71ab9c617f14592bdcdf2587 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 4 Dec 2025 23:18:01 +0100 Subject: [PATCH 101/103] fix: rename back --- .../{interceptors_tmp => interceptors}/grpc.auth.interceptor.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Tokenization/backend/wrapper/src/client/connectionManager/{interceptors_tmp => interceptors}/grpc.auth.interceptor.ts (100%) diff --git a/Tokenization/backend/wrapper/src/client/connectionManager/interceptors_tmp/grpc.auth.interceptor.ts b/Tokenization/backend/wrapper/src/client/connectionManager/interceptors/grpc.auth.interceptor.ts similarity index 100% rename from Tokenization/backend/wrapper/src/client/connectionManager/interceptors_tmp/grpc.auth.interceptor.ts rename to Tokenization/backend/wrapper/src/client/connectionManager/interceptors/grpc.auth.interceptor.ts From 5348b12a2e3069b2904d76d0e7c2abb2d05d634a Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 4 Dec 2025 23:33:37 +0100 Subject: [PATCH 102/103] fix: unit test --- .../ConnectionManager.test.ts | 2 +- .../src/test/connection/Connection.test.ts | 21 ++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts index 41dae91c5..051d46521 100644 --- a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts @@ -48,7 +48,7 @@ jest.mock('../../../client/connectionManager/interceptors/grpc.auth.interceptor' // Mock dispatcher const mockDispatch = jest.fn(); -jest.mock('../../../client/ConnectionManager/EventManagement/CentralCommandDispatcher', () => ({ +jest.mock('../../../client/connectionManager/EventManagement/CentralCommandDispatcher', () => ({ CentralCommandDispatcher: jest.fn(() => ({ dispatch: mockDispatch, register: jest.fn(), diff --git a/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts b/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts index f6074fd73..69b328940 100644 --- a/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts +++ b/Tokenization/backend/wrapper/src/test/connection/Connection.test.ts @@ -27,10 +27,20 @@ const PeerCtorMock = jest.fn((_addr: string, _creds: any) => { return lastPeerClient; }); -const logger = { - infoMessage: jest.fn(), - errorMessage: jest.fn(), -}; +jest.mock( + '@aliceo2/web-ui', + () => ({ + LogManager: { + getLogger: () => ({ + infoMessage: jest.fn(), + debugMessage: jest.fn(), + warnMessage: jest.fn(), + errorMessage: jest.fn(), + }), + }, + }), + { virtual: true } +); jest.mock( '@grpc/grpc-js', @@ -41,9 +51,6 @@ jest.mock( credentials: { createSsl: jest.fn(() => ({ insecure: true })), }, - LogManager: { - getLogger: () => logger, - }, }; }, { virtual: true } From b4ac84ec5ed0dd0a973ef9743f1cb094bb638467 Mon Sep 17 00:00:00 2001 From: Maksymilian Walicki Date: Thu, 4 Dec 2025 23:36:30 +0100 Subject: [PATCH 103/103] fix: imports --- .../src/test/client/connectionManager/ConnectionManager.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts index 051d46521..637f22982 100644 --- a/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts +++ b/Tokenization/backend/wrapper/src/test/client/connectionManager/ConnectionManager.test.ts @@ -48,7 +48,7 @@ jest.mock('../../../client/connectionManager/interceptors/grpc.auth.interceptor' // Mock dispatcher const mockDispatch = jest.fn(); -jest.mock('../../../client/connectionManager/EventManagement/CentralCommandDispatcher', () => ({ +jest.mock('../../../client/connectionManager/eventManagement/CentralCommandDispatcher', () => ({ CentralCommandDispatcher: jest.fn(() => ({ dispatch: mockDispatch, register: jest.fn(),