diff --git a/.github/workflows/sparrowproxy-selfhost.yml b/.github/workflows/sparrowproxy-selfhost.yml new file mode 100644 index 0000000..5808a33 --- /dev/null +++ b/.github/workflows/sparrowproxy-selfhost.yml @@ -0,0 +1,57 @@ +name: Self-host Docker Image +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + environment: self-host + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Read version from package.json + id: version + run: | + if ! command -v jq >/dev/null 2>&1; then + echo "jq not found"; exit 1 + fi + V=$(jq -r '.version' package.json) + if [ -z "$V" ] || [ "$V" = "null" ]; then + echo "Version not found in package.json"; exit 1 + fi + if ! [[ "$V" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Version $V is not valid semver (expected X.Y.Z)"; exit 1 + fi + echo "full=$V" >> "$GITHUB_OUTPUT" + + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Build and push (multi-arch) + run: | + set -euo pipefail + VERSION='${{ steps.version.outputs.full }}' + echo "Building image tag: $VERSION" + docker buildx build \ + --platform linux/amd64,linux/arm64 \ + -t $DOCKER_IMAGE_NAME:$VERSION \ + -t sparrowapi/sparrow-proxy:latest \ + --push . diff --git a/package.json b/package.json index 2971335..4fa8845 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sparrow-proxy", - "version": "0.0.1", + "version": "2.33.0", "description": "", "author": "", "private": true, @@ -24,14 +24,20 @@ "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", + "@nestjs/platform-fastify": "^11.1.6", "@nestjs/platform-socket.io": "^10.4.8", "@nestjs/platform-ws": "^10.4.8", "@nestjs/swagger": "^8.0.7", "@nestjs/websockets": "^10.4.8", "@types/ws": "^8.5.13", "axios": "^1.7.7", + "class-transformer": "0.5.1", + "class-transformer-validator": "^0.9.1", + "class-validator": "^0.14.0", + "fastify": "4.28.1", "form-data": "^4.0.1", "ipaddr.js": "^2.2.0", + "json5": "^2.2.3", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "socket.io-client": "^4.8.1" diff --git a/src/Types/http-client.ts b/src/Types/http-client.ts new file mode 100644 index 0000000..2f4d6e0 --- /dev/null +++ b/src/Types/http-client.ts @@ -0,0 +1,12 @@ +export interface HttpClientResponseInterface { + status: "success" | "error"; + isSuccessful: boolean; + message: string; + data: T; +} + +export interface HttpClientBackendResponseInterface { + data: T; + message: string; + statusCode: number; +} diff --git a/src/enum/httpRequest.enum.ts b/src/enum/httpRequest.enum.ts new file mode 100644 index 0000000..485347d --- /dev/null +++ b/src/enum/httpRequest.enum.ts @@ -0,0 +1,33 @@ +export interface KeyWrapper { + key: string; +} + +export interface ValueWrapper { + value: string; +} + +export enum RequestDataTypeEnum { + JSON = "JSON", + XML = "XML", + HTML = "HTML", + TEXT = "Text", + JAVASCRIPT = "JavaScript", + IMAGE = "Image", +} + +export enum ResponseStatusCode { + OK = "200 OK", + CREATED = "201 Created", + ACCEPTED = "202 Accepted", + NO_CONTENT = "204 No Content", + BAD_REQUEST = "400 Bad Request", + UNAUTHORIZED = "401 Unauthorized", + FORBIDDEN = "403 Forbidden", + NOT_FOUND = "404 Not Found", + METHOD_NOT_ALLOWED = "405 Method Not Allowed", + INTERNAL_SERVER_ERROR = "500 Internal Server Error", + SERVICE_UNAVAILABLE = "503 Service Unavailable", + ERROR = "Not Found", +} + +export interface KeyValue extends KeyWrapper, ValueWrapper {} \ No newline at end of file diff --git a/src/enum/httpResponseFormat.ts b/src/enum/httpResponseFormat.ts new file mode 100644 index 0000000..cefcfc0 --- /dev/null +++ b/src/enum/httpResponseFormat.ts @@ -0,0 +1,25 @@ +import type { HttpClientResponseInterface } from "src/Types/http-client"; + +export const success = (data: T): HttpClientResponseInterface => { + return { + status: "success", + isSuccessful: true, + message: "", + data, + }; +}; + +export const error = ( + error: string, + data?: T, + tabId: string = "", +): HttpClientResponseInterface => { + return { + status: "error", + isSuccessful: false, + message: error, + data, + }; +}; + + diff --git a/src/enum/testflow.enum.ts b/src/enum/testflow.enum.ts new file mode 100644 index 0000000..9ce3254 --- /dev/null +++ b/src/enum/testflow.enum.ts @@ -0,0 +1,59 @@ +export enum BodyModeEnum { + "none" = "none", + "application/json" = "application/json", + "application/xml" = "application/xml", + "application/yaml" = "application/yaml", + "application/x-www-form-urlencoded" = "application/x-www-form-urlencoded", + "multipart/form-data" = "multipart/form-data", + "application/javascript" = "application/javascript", + "text/plain" = "text/plain", + "text/html" = "text/html", +} + +export enum AuthModeEnum { + "No Auth" = "No Auth", + "Inherit Auth" = "Inherit Auth", + "API Key" = "API Key", + "Bearer Token" = "Bearer Token", + "Basic Auth" = "Basic Auth", +} + + +export enum AddTo { + Header = "Header", + QueryParameter = "Query Parameter", +} + +export class Auth { + bearerToken?: string; + basicAuth?: { + username: string; + password: string; + }; + apiKey?: { + authKey: string; + authValue: string | unknown; + addTo: AddTo; + }; +} + +export type TFKeyValueStoreType = { + key: string; + value: string; + checked?: boolean; +}; + +export interface TFAPIResponseType { + body?: string; + headers?: object; + status?: string; +} + +export enum RequestDataTypeEnum { + JSON = "JSON", + XML = "XML", + HTML = "HTML", + TEXT = "Text", + JAVASCRIPT = "JavaScript", + IMAGE = "Image", +} \ No newline at end of file diff --git a/src/payloads/testflow.payload.ts b/src/payloads/testflow.payload.ts new file mode 100644 index 0000000..9e3e523 --- /dev/null +++ b/src/payloads/testflow.payload.ts @@ -0,0 +1,327 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + ValidateNested, + IsArray, + IsNotEmptyObject, + IsString, + IsBoolean, + IsNotEmpty, + IsOptional, + IsEnum, + IsNumber, + IsDate, +} from 'class-validator'; +import { HTTPMethods } from "fastify"; +import { BodyModeEnum, RequestDataTypeEnum,} from 'src/enum/testflow.enum'; +import { AuthModeEnum } from 'src/enum/testflow.enum'; +import { Auth } from 'src/enum/testflow.enum'; + + +export class KeyValue { + key: string; + value: string | unknown; + checked: boolean; +} + +export class VariableDto { + @IsString() + key: string; + + @IsString() + value: string; + + @IsBoolean() + @IsOptional() + checked?: boolean; +} + + +export class SparrowRequestBody { + raw?: string; + urlencoded?: KeyValue[]; + formdata?: FormData; +} + +export class RequestMetaData { + @ApiProperty({ example: 'put' }) + @IsNotEmpty() + method: HTTPMethods; + + @ApiProperty({ example: 'pet' }) + @IsString() + @IsNotEmpty() + name: string; + + @ApiProperty({ example: 'updatePet' }) + @IsString() + @IsOptional() + operationId?: string; + + @ApiProperty({ example: '/pet' }) + @IsString() + @IsNotEmpty() + url: string; + + @ApiProperty({ type: [SparrowRequestBody] }) + @Type(() => SparrowRequestBody) + @ValidateNested({ each: true }) + @IsOptional() + body?: SparrowRequestBody[]; + + @ApiProperty({ + enum: [ + 'application/json', + 'application/xml', + 'application/x-www-form-urlencoded', + 'multipart/form-data', + 'application/javascript', + 'text/plain', + 'text/html', + ], + }) + @IsEnum(BodyModeEnum) + @IsOptional() + selectedRequestBodyType?: BodyModeEnum; + + @ApiProperty({ + enum: AuthModeEnum, + }) + @IsEnum(AuthModeEnum) + @IsNotEmpty() + selectedRequestAuthType?: AuthModeEnum; + + @ApiProperty({ + example: { + name: 'search', + description: 'The search term to filter results', + required: false, + schema: {}, + }, + }) + @IsArray() + @Type(() => KeyValue) + @ValidateNested({ each: true }) + @IsOptional() + queryParams?: KeyValue[]; + + @ApiProperty({ + type: [KeyValue], + example: { + name: 'userID', + description: 'The unique identifier of the user', + required: true, + schema: {}, + }, + }) + @IsArray() + @Type(() => KeyValue) + @ValidateNested({ each: true }) + @IsOptional() + pathParams?: KeyValue[]; + + @ApiProperty({ + type: [KeyValue], + example: { + name: 'Authorization', + description: 'Bearer token for authentication', + }, + }) + @IsArray() + @Type(() => KeyValue) + @ValidateNested({ each: true }) + @IsOptional() + headers?: KeyValue[]; + + @ApiProperty({ + type: [Auth], + example: { + bearerToken: 'Bearer xyz', + }, + }) + @IsArray() + @Type(() => Auth) + @ValidateNested({ each: true }) + @IsOptional() + auth?: Auth[]; +} + +export class TestflowEdges { + @IsString() + @IsNotEmpty() + id: string; + + @IsString() + @IsNotEmpty() + source: string; + + @IsString() + @IsNotEmpty() + target: string; +} + +export class TestflowRunDto { + @IsArray() + @Type(() => TestflowNodes) + @ValidateNested({ each: true }) + @IsOptional() + nodes: TestflowNodes[]; + + @ApiProperty({ type: [VariableDto] }) + @IsArray() + @Type(() => VariableDto) + @ValidateNested({ each: true }) + variables: VariableDto[]; + + @IsArray() + @Type(() => TestflowEdges) + @ValidateNested({ each: true }) + @IsOptional() + edges: TestflowEdges[]; + + @IsString() + @IsOptional() + userId:string; +} + +export class NodeData { + @IsString() + @IsNotEmpty() + blockName: string; + + @IsString() + @IsNotEmpty() + @IsOptional() + requestId?: string; + + @IsString() + @IsNotEmpty() + @IsOptional() + folderId?: string; + + @IsString() + @IsNotEmpty() + @IsOptional() + collectionId?: string; + + @ApiProperty({ type: RequestMetaData }) + @IsOptional() + @Type(() => RequestMetaData) + requestData?: RequestMetaData; +} + +export class TestflowNodes { + @IsString() + @IsNotEmpty() + id: string; + + @IsString() + @IsNotEmpty() + type: string; + + @IsOptional() + position?: any; + + @Type(() => NodeData) + @IsOptional() + data?: NodeData; +} + +export class TestflowSchedularHistoryRequest { + @IsString() + @IsOptional() + method?: string; + + @IsString() + @IsOptional() + name?: string; + + @IsString() + @IsOptional() + status?: string; + + @IsString() + @IsOptional() + time: string; + + @IsString() + @IsOptional() + errorMessage?: string; + + @IsString() + @IsOptional() + error?: string; +} + +export class TFKeyValueStoreDto { + @IsString() + key: string; + + @IsString() + value: string; +} + +export class TestflowSchedularHistoryResponse { + @IsArray() + @ValidateNested({ each: true }) + @Type(() => TFKeyValueStoreDto) + headers: TFKeyValueStoreDto[]; + + @IsString() + status: string; + + @IsString() + body: string; + + @IsNumber() + time: number; + + @IsNumber() + size: number; + + @IsOptional() + @IsString() + responseContentType?: RequestDataTypeEnum; +} + +export class TestFlowSchedularRunHistory { + @IsString() + @IsNotEmpty() + failedRequests: number; + + @IsArray() + @IsOptional() + requests?: TestflowSchedularHistoryRequest[]; + + @IsArray() + @IsOptional() + responses?:TestflowSchedularHistoryResponse[]; + + @IsString() + @IsNotEmpty() + status: string; + + @IsNumber() + @IsNotEmpty() + successRequests: number; + + @IsString() + @IsNotEmpty() + totalTime: string; + + @IsDate() + @IsOptional() + createdAt?: Date; + + @IsDate() + @IsOptional() + updatedAt?: Date; + + @IsString() + @IsOptional() + createdBy?: string; + + @IsString() + @IsOptional() + updatedBy?: string; +} \ No newline at end of file diff --git a/src/proxy/http/http.module.ts b/src/proxy/http/http.module.ts index 849420e..a73c5d0 100644 --- a/src/proxy/http/http.module.ts +++ b/src/proxy/http/http.module.ts @@ -7,5 +7,6 @@ import { HttpModule as NestHttpModule } from '@nestjs/axios'; imports: [NestHttpModule], controllers: [HttpController], providers: [HttpService], + exports: [HttpService], }) export class HttpModule {} diff --git a/src/proxy/http/http.service.ts b/src/proxy/http/http.service.ts index 1241cd5..8cbf9e1 100644 --- a/src/proxy/http/http.service.ts +++ b/src/proxy/http/http.service.ts @@ -249,6 +249,22 @@ export class HttpService { // Add custom user agent config.headers['User-Agent'] = 'SparrowRuntime/1.0.0'; + // DNS rebinding protection: re-validate resolved IP before request + const resolvedAddresses = await lookup(new URL(url).hostname, { all: true }); + for (const addr of resolvedAddresses) { + const ip = ipaddr.parse(addr.address); + if ( + ip.range() === 'linkLocal' || + ip.range() === 'loopback' || + ip.range() === 'private' || + ip.range() === 'reserved' + ) { + throw new BadRequestException( + `Access to internal IP addresses is not allowed: ${addr.address}`, + ); + } + } + try { const response = await this.httpService.axiosRef({ url: config.url, diff --git a/src/proxy/proxy.module.ts b/src/proxy/proxy.module.ts index 3efbe93..b354624 100644 --- a/src/proxy/proxy.module.ts +++ b/src/proxy/proxy.module.ts @@ -3,8 +3,9 @@ import { SocketIoModule } from './socketio/socketio.module'; import { HttpModule } from './http/http.module'; import { WebSocketModule } from './websocket/websocket.module'; import { GraphqlModule } from "./graphql/graphql.module"; +import { TestflowModule } from './testflow/testflow.module'; @Module({ - imports: [HttpModule, SocketIoModule, WebSocketModule, GraphqlModule], + imports: [HttpModule, SocketIoModule, WebSocketModule, GraphqlModule,TestflowModule], }) export class ProxyModule {} diff --git a/src/proxy/testflow/testflow.controller.ts b/src/proxy/testflow/testflow.controller.ts new file mode 100644 index 0000000..56918fa --- /dev/null +++ b/src/proxy/testflow/testflow.controller.ts @@ -0,0 +1,36 @@ +import { + Controller, + Post, + Body, + Req, + Res, + HttpException, + HttpStatus +} from '@nestjs/common'; +import { Request, Response } from 'express'; +import { TestflowService } from './testflow.service'; +import { TestflowRunDto } from 'src/payloads/testflow.payload'; + +@Controller('proxy/testflow') +export class TestflowController { + constructor( + private readonly testflowService: TestflowService, + ) {} + + @Post('/execute') + async testflowRun( + @Body() payload: TestflowRunDto, + @Req() req: Request, + @Res() res: Response, + ) { + try { + const result = await this.testflowService.runTestflow(payload); + return res.status(200).send(result); + } catch (error: any) { + throw new HttpException( + error?.message || 'Failed to run testflow', + HttpStatus.BAD_GATEWAY, + ); + } + } +} diff --git a/src/proxy/testflow/testflow.module.ts b/src/proxy/testflow/testflow.module.ts new file mode 100644 index 0000000..20e7b16 --- /dev/null +++ b/src/proxy/testflow/testflow.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TestflowController } from './testflow.controller'; +import { TestflowService } from './testflow.service'; +import { DecodeTestflow } from 'src/utils/decode-testflow'; +import { HttpModule as NestHttpModule } from '@nestjs/axios'; + +@Module({ + imports: [NestHttpModule], + controllers: [TestflowController], + providers: [TestflowService, DecodeTestflow], +}) +export class TestflowModule {} diff --git a/src/proxy/testflow/testflow.service.ts b/src/proxy/testflow/testflow.service.ts new file mode 100644 index 0000000..4afe1ee --- /dev/null +++ b/src/proxy/testflow/testflow.service.ts @@ -0,0 +1,665 @@ +import { Injectable } from '@nestjs/common'; +import { TestflowNodes, TestflowRunDto, TestFlowSchedularRunHistory } from 'src/payloads/testflow.payload'; +import { Logger } from '@nestjs/common'; +import { success,error } from 'src/enum/httpResponseFormat'; +import { DecodeTestflow, RequestData } from 'src/utils/decode-testflow'; +import { ParseTime } from 'src/utils/parse-time'; +import { TFAPIResponseType } from 'src/enum/testflow.enum'; +import { TFKeyValueStoreType } from 'src/enum/testflow.enum'; +import { ResponseStatusCode } from 'src/enum/httpRequest.enum'; +import { BadRequestException } from '@nestjs/common'; +import { HttpService as NestHttpService } from '@nestjs/axios'; +import * as https from 'https'; +import FormData from 'form-data'; +import { lookup } from 'dns/promises'; +import * as ipaddr from 'ipaddr.js'; + +@Injectable() +export class TestflowService { + private readonly logger = new Logger(TestflowService.name); + private _decodeRequest = new DecodeTestflow(); + constructor( + private readonly httpService: NestHttpService, + ) {} + + private base64ToBuffer(base64: string): { buffer: Buffer; mime: string } { + const arr = base64.split(','); + const mime = arr[0].match(/:(.*?);/)?.[1]; + const bstr = Buffer.from(arr[1], 'base64'); + return { buffer: bstr, mime }; + } + + private getStatusText(statusCode: number): string { + const statusMap: Record = { + // 1xx Informational + 100: 'Continue', + 101: 'Switching Protocols', + 102: 'Processing', + 103: 'Early Hints', + + // 2xx Success + 200: 'OK', + 201: 'Created', + 202: 'Accepted', + 203: 'Non-Authoritative Information', + 204: 'No Content', + 205: 'Reset Content', + 206: 'Partial Content', + 207: 'Multi-Status', + 208: 'Already Reported', + 226: 'IM Used', + + // 3xx Redirection + 300: 'Multiple Choices', + 301: 'Moved Permanently', + 302: 'Found', + 303: 'See Other', + 304: 'Not Modified', + 305: 'Use Proxy', + 307: 'Temporary Redirect', + 308: 'Permanent Redirect', + + // 4xx Client Errors + 400: 'Bad Request', + 401: 'Unauthorized', + 402: 'Payment Required', + 403: 'Forbidden', + 404: 'Not Found', + 405: 'Method Not Allowed', + 406: 'Not Acceptable', + 407: 'Proxy Authentication Required', + 408: 'Request Timeout', + 409: 'Conflict', + 410: 'Gone', + 411: 'Length Required', + 412: 'Precondition Failed', + 413: 'Payload Too Large', + 414: 'URI Too Long', + 415: 'Unsupported Media Type', + 416: 'Range Not Satisfiable', + 417: 'Expectation Failed', + 418: `I\'m a teapot`, + 421: 'Misdirected Request', + 422: 'Unprocessable Entity', + 423: 'Locked', + 424: 'Failed Dependency', + 425: 'Too Early', + 426: 'Upgrade Required', + 428: 'Precondition Required', + 429: 'Too Many Requests', + 431: 'Request Header Fields Too Large', + 451: 'Unavailable For Legal Reasons', + + // 5xx Server Errors + 500: 'Internal Server Error', + 501: 'Not Implemented', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + 504: 'Gateway Timeout', + 505: 'HTTP Version Not Supported', + 506: 'Variant Also Negotiates', + 507: 'Insufficient Storage', + 508: 'Loop Detected', + 509: 'Bandwidth Limit Exceeded', + 510: 'Not Extended', + 511: 'Network Authentication Required', + }; + + return statusMap[statusCode] || 'Unknown Status'; + } + + private async validateUrl(targetUrl: string) { + try { + const url = new URL(targetUrl); + + // Resolve hostname to IPs + const addresses = await lookup(url.hostname, { all: true }); + + for (const addr of addresses) { + const ip = ipaddr.parse(addr.address); + + // Block local, private, or reserved IPs + if ( + ip.range() === 'linkLocal' || // 169.254.0.0/16 (Azure IMDS lives here) + ip.range() === 'loopback' || // 127.0.0.0/8 + ip.range() === 'private' || // 10.x, 192.168.x, 172.16-31.x + ip.range() === 'reserved' // Other reserved ranges + ) { + throw new BadRequestException( + `Access to internal IP addresses is not allowed: ${addr.address}`, + ); + } + } + } catch (err) { + throw new BadRequestException('Invalid or disallowed URL'); + } + } + + private async makeHttpRequest({ + url, + method, + headers, + body, + contentType, + }: { + url: string; + method: string; + headers: string; + body: any; + contentType: string; + }): Promise<{ status: string; data: any; headers: any }> { + try { + await this.validateUrl(url); + // Parse headers from stringified JSON + const parsedHeaders: Record = {}; + let headersArray; + + try { + headersArray = JSON.parse(headers); + if (Array.isArray(headersArray)) { + headersArray.forEach((item: any) => { + parsedHeaders[item.key] = item.value; + }); + } + } catch (headerError) { + console.error('Error parsing headers:', headerError); + throw new Error('Invalid headers format'); + } + + // Prepare the request configuration + const config: any = { + url, + method, + headers: parsedHeaders, + data: null, + }; + + // Handle body based on content type + try { + switch (contentType) { + case 'application/json': + if (typeof body === 'string') { + // Check if the body is a numeric string + const isNumeric = !isNaN(body as any) && !isNaN(parseFloat(body)); + if (isNumeric) { + config.data = body; // Keep numeric string as is + } else { + // Try parsing as JSON only if it's not a numeric string + try { + config.data = JSON.parse(body); + } catch (e) { + config.data = body; // If parsing fails, use the original string + } + } + } else { + config.data = body; + } + break; + + case 'application/x-www-form-urlencoded': + // Parse body if it's a string + const formParsedBody = + typeof body === 'string' ? JSON.parse(body) : body; + + if (!Array.isArray(formParsedBody)) { + throw new Error('Body must be an array for URL-encoded data.'); + } + + // Filter and transform the body into key-value pairs + const formBody: Record = {}; + formParsedBody.forEach((item: any) => { + formBody[item.key] = item.value; + }); + + const formUrlEncoded = new URLSearchParams(formBody); + config.data = formUrlEncoded.toString(); + config.headers['Content-Type'] = + 'application/x-www-form-urlencoded'; + break; + + case 'multipart/form-data': + const formData = new FormData(); + const parsedBody = + typeof body === 'string' ? JSON.parse(body) : body; + if (Array.isArray(parsedBody)) { + for (const field of parsedBody || []) { + try { + if (field?.base) { + const { buffer, mime } = this.base64ToBuffer(field.base); + formData.append(field.key, buffer, { + filename: field.value, + contentType: mime, + }); + } else { + formData.append(field.key, field.value); + } + } catch (e) { + formData.append(field.key, field.value); + } + } + } + + config.data = formData; + config.headers = { + ...parsedHeaders, + ...formData.getHeaders(), + }; + break; + + case 'text/plain': + config.data = body; + config.headers['Content-Type'] = 'text/plain'; + break; + + default: + break; + } + } catch (bodyError) { + console.error('Error processing request body:', bodyError); + throw new Error('Invalid request body format'); + } + + // Add custom user agent + config.headers['User-Agent'] = 'SparrowRuntime/1.0.0'; + + // DNS rebinding protection: re-validate resolved IP before request + const resolvedAddresses = await lookup(new URL(url).hostname, { all: true }); + for (const addr of resolvedAddresses) { + const ip = ipaddr.parse(addr.address); + if ( + ip.range() === 'linkLocal' || + ip.range() === 'loopback' || + ip.range() === 'private' || + ip.range() === 'reserved' + ) { + throw new BadRequestException( + `Access to internal IP addresses is not allowed: ${addr.address}`, + ); + } + } + + try { + const response = await this.httpService.axiosRef({ + url: config.url, + method: config.method, + headers: config.headers, + data: config.data, + responseType: 'arraybuffer', + httpsAgent: new https.Agent({ rejectUnauthorized: false }), // allows expired SSL certs. + }); + + let contentType = response.headers['content-type']; + let responseData = ''; + if (contentType?.startsWith('image/')) { + const base64 = Buffer.from(response.data).toString('base64'); + responseData = `data:${contentType};base64,${base64}`; + } else { + responseData = Buffer.from(response.data).toString('utf-8'); + } + + return { + status: + response.status + + ' ' + + (response.statusText || this.getStatusText(response.status)), + data: `${responseData}`, + headers: response.headers, + }; + } catch (axiosError: any) { + try { + const responseData = Buffer.from(axiosError.response?.data).toString( + 'utf-8', + ); + return { + status: axiosError.response?.status + ? axiosError.response?.status + + ' ' + + (axiosError.response?.statusText || + this.getStatusText(axiosError.response?.status)) + : null, + data: `${responseData}` || JSON.stringify({ message: axiosError.message }), + headers: axiosError.response?.headers, + }; + } catch (e) { + return { + status: null, + data: JSON.stringify({ message: axiosError.message }), + headers: axiosError.response?.headers, + }; + } + } + } catch (error: any) { + console.error('HTTP Service Error:', error); + throw new Error(error.message || 'Unknown error occurred'); + } + } + + private async makeRequest( + url: string, + method: string, + headers: string, + body: string, + contentType: string, + signal?: AbortSignal, + ) { + try { + const response = await this.makeHttpRequest({ + url, + method, + headers, + body, + contentType, + }); + return success({ + body: response.data, + status: response.status, + headers: response.headers, + }); + } catch (cloudError) { + return error(cloudError.message || "Cloud agent request failed"); + } + } + + async waitForAbort(signal: AbortSignal): Promise { + return new Promise((_, reject) => { + if (signal?.aborted) { + return reject(new Error("Aborted before starting")); + } + signal?.addEventListener( + "abort", + () => { + reject(new Error("Aborted during request")); + }, + { once: true }, + ); + }); + } + + private findConnectedNodes = ( + adj: any[], + start: number, + nodes:TestflowNodes[], + result:TestflowNodes[], + visited = new Set(), + ) => { + if (visited.has(start)) return; + for (let i = 0; i < nodes.length; i++) { + if (Number(nodes[i].id) === start) { + result.push(nodes[i]); + } + } + visited.add(start); + for (const neighbor of adj[start]) { + this.findConnectedNodes(adj, neighbor, nodes, result, visited); + } + }; + + async runTestflow(payload: TestflowRunDto) { + const { nodes, variables, edges } = payload; + const abortController = new AbortController(); + const { signal } = abortController; + let successRequests = 0; + let failedRequests = 0; + let totalTime = 0; + const history: TestFlowSchedularRunHistory = { + status: "fail", + successRequests: 0, + failedRequests: 0, + totalTime: "", + createdAt: new Date(), + createdBy: payload.userId, + requests: [], + responses:[] + }; + + let requestChainResponse: Record = {}; + const executedNodes: any[] = []; + const environmentVariables = variables || []; + let runningNodes: any[] = []; + let maxNodeId = 1; + for (let i = 0; i < nodes.length; i++) { + maxNodeId = Math.max(maxNodeId, Number(nodes[i].id)); + } + // Initialize adjacency list + const graph = Array.from({ length: maxNodeId + 1 }, () => []); + // Populate adjacency list + for (let i = 0; i < edges.length; i++) { + graph[Number(edges[i].source)].push(Number(edges[i].target)); + } + let result = []; + this.findConnectedNodes(graph, Number("1"), nodes, result); + runningNodes = [...result]; + for (const element of runningNodes) { + if (element?.type !== "requestBlock" || !element?.data?.requestData) { + continue; + } + const requestData: RequestData = element.data.requestData as RequestData; + try { + // Decode request + const decodeData = this._decodeRequest.init( + requestData, + environmentVariables.filter( + (env: { key: string; value: string; checked: boolean }) => + env.key?.trim() && env.value?.trim(), + ), + requestChainResponse, + ); + const [url, method, headers, body, contentType] = decodeData; + const start = Date.now(); + let resData: any; + try { + const response: any = await this.makeRequest( + url, + method, + headers, + body, + contentType, + signal, + ); + const duration = Date.now() - start; + if (response.isSuccessful) { + const byteLength = new TextEncoder().encode( + JSON.stringify(response), + ).length; + const responseSizeKB = byteLength / 1024; + const responseData: TFAPIResponseType = response.data; + const responseBody = responseData.body; + const formattedHeaders = Object.entries( + response?.data?.headers || {}, + ).map(([key, value]) => ({ + key, + value: String(value), + })) as TFKeyValueStoreType[]; + const responseStatus = response?.data?.status; + resData = { + body: responseBody, + headers: formattedHeaders, + status: responseStatus, + time: duration, + size: responseSizeKB, + responseContentType: + this._decodeRequest.setResponseContentType(formattedHeaders), + }; + const statusCode = Number(resData.status.split(" ")[0]); + if (statusCode >= 200 && statusCode < 300) { + successRequests++; + } else { + failedRequests++; + } + totalTime += duration; + history.requests.push({ + method: requestData.method as string, + name: requestData.name as string, + status: resData.status, + time: new ParseTime().convertMilliseconds(duration), + }); + history.responses.push({ + headers: resData.headers, + status: resData.status, + body: resData.body, + time: resData.time, + size: resData.size, + responseContentType: resData.responseContentType , + }) + // Build chaining object + const responseHeader = + this._decodeRequest.setResponseContentType(formattedHeaders); + const reqParam: Record = {}; + const params = new URL(url).searchParams; + for (const [key, value] of params.entries()) { + reqParam[key] = value; + } + const parsedHeaders = JSON.parse(headers) as { + key: string; + value: string; + }[]; + const headersObject = Object.fromEntries( + parsedHeaders.map(({ key, value }) => [key, value]), + ); + let reqBody: any; + if (contentType === "application/json") { + try { + reqBody = JSON.parse(body); + } catch { + reqBody = {}; + } + } else if ( + contentType === "multipart/form-data" || + contentType === "application/x-www-form-urlencoded" + ) { + try { + const parsedBody = JSON.parse(body) as { + key: string; + value: string; + }[]; + reqBody = Object.fromEntries( + parsedBody.map(({ key, value }) => [key, value]), + ); + } catch { + reqBody = {}; + } + } else { + reqBody = body; + } + const responseObject = { + response: { + body: + responseHeader === "JSON" + ? JSON.parse(resData.body) + : resData.body, + headers: response?.data?.headers, + }, + request: { + headers: headersObject || {}, + body: reqBody, + parameters: reqParam || {}, + }, + }; + const sanitizedRequestName = requestData.name.replace( + /[^a-zA-Z0-9_]/g, + "_", + ); + const sanitizedBlockName = ( + element.data.blockName || element.id + ).replace(/[^a-zA-Z0-9_]/g, "_"); + requestChainResponse[`$$${sanitizedRequestName}`] = responseObject; + requestChainResponse[`$$${sanitizedBlockName}`] = responseObject; + } else { + resData = { + body: response.message || "Request failed", + headers: [], + status: ResponseStatusCode.ERROR, + time: duration, + size: 0, + }; + failedRequests++; + totalTime += duration; + history.requests.push({ + method: requestData.method as string, + name: requestData.name as string, + status: ResponseStatusCode.ERROR, + time: new ParseTime().convertMilliseconds(duration), + errorMessage:response.message, + error:response.error + }); + history.responses.push({ + headers: resData.headers, + status: resData.status, + body: resData.body, + time: resData.time, + size: resData.size, + }) + } + } catch (error) { + const duration = Date.now() - start; + if (error?.name === "AbortError") { + console.warn("🛑 Request aborted, breaking loop"); + break; + } + resData = { + body: error?.message || "Request failed", + headers: [], + status: ResponseStatusCode.ERROR, + time: duration, + size: 0, + }; + failedRequests++; + totalTime += duration; + history.requests.push({ + method: requestData.method as string, + name: requestData.name as string, + status: ResponseStatusCode.ERROR, + time: new ParseTime().convertMilliseconds(duration), + errorMessage:error.message, + error:error + }); + history.responses.push({ + headers: resData.headers, + status: resData.status, + body: resData.body, + time: resData.time, + size: resData.size, + }) + } + executedNodes.push({ + id: element.id, + response: resData, + request: requestData, + }); + } catch (error) { + failedRequests++; + history.requests.push({ + method: requestData?.method || "UNKNOWN", + name: requestData?.name || "Unknown Request", + status: ResponseStatusCode.ERROR, + time: "0 ms", + errorMessage:error.message, + error:error + }); + executedNodes.push({ + id: element.id, + response: { + body: error?.message || "Processing failed", + headers: [], + status: ResponseStatusCode.ERROR, + time: 0, + size: 0, + }, + request: requestData, + }); + } + } + + // Finalize history + history.totalTime = new ParseTime().convertMilliseconds(totalTime); + history.successRequests = successRequests; + history.failedRequests = failedRequests; + history.status = failedRequests === 0 ? "pass" : "fail"; + return { + history, + requestChainResponse, + nodes: executedNodes, + }; + } +} + diff --git a/src/utils/decode-testflow.ts b/src/utils/decode-testflow.ts new file mode 100644 index 0000000..db914b7 --- /dev/null +++ b/src/utils/decode-testflow.ts @@ -0,0 +1,543 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import JSON5 from "json5" +import type { KeyValue} from "src/enum/httpRequest.enum"; +import { RequestDataTypeEnum } from "src/enum/httpRequest.enum"; + +/** + * Updated RequestData interface for the new structure + */ +interface RequestData { + headers?: Array<{ key: string; value: string; checked: boolean }>; + queryParams?: Array<{ key: string; value: string; checked: boolean }>; + body?: { + raw?: string; + urlencoded?: Array<{ key: string; value: string; checked: boolean }>; + formdata?: { + text?: Array<{ key: string; value: string; checked: boolean }>; + file?: Array<{ + key: string; + value?: string; + base?: string; + checked?: boolean; + }>; + }; + }; + auth?: { + bearerToken?: string; + basicAuth?: { + username: string; + password: string; + }; + apiKey?: { + authKey: string; + authValue: string; + addTo: string; + }; + }; + url: string; + method: string; + name: string; + selectedRequestBodyType?: string; + selectedRequestAuthType?: string; +} + +/** + * DecodeTestflow - Updated for new RequestData structure + * + * Parses requests (URL, headers, body, content-type) for testflow execution. + * Designed for Node/Next.js backend. Uses JSON5 for permissive JSON parsing of raw bodies. + */ +class DecodeTestflow { + constructor() {} + + /** + * Determine response content type enum from response headers. + */ + public setResponseContentType = ( + responseHeaders: KeyValue[] | undefined, + ): RequestDataTypeEnum => { + if (!responseHeaders) return RequestDataTypeEnum.TEXT; + + for (let i = 0; i < responseHeaders.length; i++) { + const key = (responseHeaders[i].key || "").toLowerCase(); + const value = String(responseHeaders[i].value || ""); + if (key === "content-type") { + if (value.includes("text/html")) return RequestDataTypeEnum.HTML; + if ( + value.includes("application/json") || + value.includes("application/hal+json") + ) + return RequestDataTypeEnum.JSON; + if (value.includes("application/xml")) return RequestDataTypeEnum.XML; + if (value.includes("application/javascript")) + return RequestDataTypeEnum.JAVASCRIPT; + if (value.startsWith("image/")) return RequestDataTypeEnum.IMAGE; + return RequestDataTypeEnum.TEXT; + } + } + return RequestDataTypeEnum.TEXT; + }; + + /** + * Ensure URL has protocol; handles protocol-relative URLs. + */ + private ensureHttpOrHttps = (str: string): string => { + if (!str) return "http://"; + if (str.startsWith("http://") || str.startsWith("https://")) { + return ""; + } else if (str.startsWith("//")) { + return "http:"; + } else { + return "http://"; + } + }; + + /** + * Return only checked KeyValue entries (preserves order). + */ + private extractKeyValue = ( + pairs?: Array<{ key: string; value: string; checked: boolean }>, + ): KeyValue[] => { + if (!Array.isArray(pairs)) return []; + const checkedPairs: KeyValue[] = []; + for (const pair of pairs) { + if (pair && pair.checked && pair.key) { + checkedPairs.push({ key: pair.key, value: String(pair.value || "") }); + } + } + return checkedPairs; + }; + + /** + * Build URL with query parameters and authentication parameters (if any). + */ + private extractURL = ( + requestData: RequestData, + environmentVariables: any[] = [], + previousResponse?: any, + ): string => { + let url = (requestData.url || "").trim(); + // Replace environment variables in URL + url = this.setEnvironmentVariables(url, environmentVariables); + url = this.setDynamicExpression(url, previousResponse); + // Add query parameters + const queryParams = this.extractKeyValue(requestData.queryParams); + if (queryParams.length > 0) { + const processedParams = queryParams.map((param) => ({ + key: this.setEnvironmentVariables(param.key, environmentVariables), + value: this.setEnvironmentVariables( + String(param.value), + environmentVariables, + ), + })); + const queryString = processedParams + .map( + (param) => + `${encodeURIComponent(param.key)}=${encodeURIComponent(param.value)}`, + ) + .join("&"); + const hasQuery = url.includes("?"); + url = `${url}${hasQuery ? "&" : "?"}${queryString}`; + } + + // Handle API Key in query parameters + if ( + requestData.selectedRequestAuthType === "API Key" && + requestData.auth?.apiKey?.addTo === "Query" && + requestData.auth?.apiKey?.authKey && + requestData.auth?.apiKey?.authValue + ) { + const processedKey = this.setEnvironmentVariables( + requestData.auth.apiKey.authKey, + environmentVariables, + ); + const processedValue = this.setEnvironmentVariables( + requestData.auth.apiKey.authValue, + environmentVariables, + ); + + const hasQuery = url.includes("?"); + url = `${url}${hasQuery ? "&" : "?"}${encodeURIComponent(processedKey)}=${encodeURIComponent(processedValue)}`; + } + + return this.ensureHttpOrHttps(url) + url; + }; + + /** + * Process authentication and return auth header if needed. + */ + private processAuthentication = ( + requestData: RequestData, + environmentVariables: any[] = [], + ): KeyValue | null => { + const authType = requestData.selectedRequestAuthType; + const auth = requestData.auth; + if (!auth) return null; + switch (authType) { + case "Bearer Token": + if (auth.bearerToken) { + const processedToken = this.setEnvironmentVariables( + auth.bearerToken, + environmentVariables, + ); + return { + key: "Authorization", + value: `Bearer ${processedToken}`, + }; + } + break; + + case "Basic Auth": + if (auth.basicAuth?.username && auth.basicAuth?.password) { + const processedUsername = this.setEnvironmentVariables( + auth.basicAuth.username, + environmentVariables, + ); + const processedPassword = this.setEnvironmentVariables( + auth.basicAuth.password, + environmentVariables, + ); + const credentials = Buffer.from( + `${processedUsername}:${processedPassword}`, + ).toString("base64"); + return { + key: "Authorization", + value: `Basic ${credentials}`, + }; + } + break; + + case "API Key": + if ( + auth.apiKey?.addTo === "Header" && + auth.apiKey?.authKey && + auth.apiKey?.authValue + ) { + const processedKey = this.setEnvironmentVariables( + auth.apiKey.authKey, + environmentVariables, + ); + const processedValue = this.setEnvironmentVariables( + auth.apiKey.authValue, + environmentVariables, + ); + return { + key: processedKey, + value: processedValue, + }; + } + break; + + case "No Auth": + default: + return null; + } + + return null; + }; + + /** + * Extract and process headers, including authentication headers. + */ + private extractHeaders = ( + requestData: RequestData, + environmentVariables: any[] = [], + previousResponse?: any, + ): string => { + // Get regular headers + const headers = this.extractKeyValue(requestData.headers); + // Process environment variables in headers + const processedHeaders = headers.map((header) => ({ + key: this.setEnvironmentVariables(header.key, environmentVariables), + value: this.setEnvironmentVariables( + String(header.value), + environmentVariables, + ), + })); + // Get authentication header + const authHeader = this.processAuthentication( + requestData, + environmentVariables, + ); + if (authHeader) { + processedHeaders.unshift(authHeader); + } + // Remove duplicates (keep first occurrence) and filter empty keys + const uniqueHeaders = new Map(); + for (const header of processedHeaders) { + const key = header.key.toLowerCase(); + if (key && !uniqueHeaders.has(key) && key !== "content-length") { + uniqueHeaders.set(key, header.value); + } + } + + // Convert back to array format + const result = Array.from(uniqueHeaders.entries()).map(([key, value]) => ({ + key, + value, + })); + + // Apply dynamic expressions + const jsonString = JSON.stringify(result); + const processed = this.setDynamicExpression(jsonString, previousResponse); + + return processed; + }; + + /** + * Replace environment variables in the text. Uses {{KEY}} syntax. + */ + public setEnvironmentVariables = ( + text: string, + environmentVariables: any[] = [], + ): string => { + if (typeof text !== "string") return String(text || ""); + let updatedText = text.replace( + /\[\*\$\[(.*?)\]\$\*\]/gs, + (_, squareContent) => { + const updated = squareContent + .replace(/\\/g, "") + .replace(/"/g, `'`) + .replace(/\{\{(.*?)\}\}/g, (_: any, inner: string) => { + return `'{{${inner.trim()}}}'`; + }); + return `[*$[${updated}]$*]`; + }, + ); + + if (!Array.isArray(environmentVariables)) environmentVariables = []; + + for (const element of environmentVariables) { + if (!element || typeof element.key !== "string" || !element.checked) + continue; + const keyEscaped = element.key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const regex = new RegExp(`{{(${keyEscaped})}}`, "g"); + updatedText = updatedText.replace(regex, String(element.value ?? "")); + } + + return updatedText; + }; + + /** + * Evaluate dynamic expressions inside the special wrapper [*$[ ... ]$*] + */ + private setDynamicExpression = (text: string, response: any): string => { + if (!text || typeof text !== "string") return String(text || ""); + const result = text.replace(/\[\*\$\[(.*?)\]\$\*\]/gs, (_, expr) => { + try { + const de = expr.replace(/'\{\{(.*?)\}\}'/g, "undefined"); + const fn = new Function( + "response", + `with (response) { return (${de}); }`, + ); + const s = fn(response); + if (typeof s === "string") { + return s + .replace(/\n/g, "") + .replace(/\\/g, "\\\\") + .replace(/\"/g, '\\"') + .replace(/\t/g, "\\t"); + } else { + return String(s); + } + } catch (e: any) { + return ""; + } + }); + return result; + }; + + /** + * Similar to setDynamicExpression but returns properly quoted JSON-safe values. + */ + public setDynamicExpression2 = (text: string, response: any): string => { + if (!text || typeof text !== "string") return String(text || ""); + const result = text.replace(/\[\*\$\[(.*?)\]\$\*\]/gs, (_, expr) => { + try { + const de = expr.replace(/'\{\{(.*?)\}\}'/g, "undefined"); + const fn = new Function( + "response", + `with (response) { return (${de}); }`, + ); + const s = fn(response); + if (typeof s === "string") return `"${s}"`; + if (typeof s === "object" && s !== null) return `${JSON.stringify(s)}`; + return String(s); + } catch (e: any) { + return ""; + } + }); + return result; + }; + + /** + * Entry point - returns [url, method, headers, body, contentType] + * Updated to work with new RequestData structure + */ + public init( + requestData: RequestData, + environmentVariables: any[] = [], + previousResponse?: any, + ): string[] { + const url = this.extractURL( + requestData, + environmentVariables, + previousResponse, + ); + const method = (requestData.method || "GET").toUpperCase(); + const headers = this.extractHeaders( + requestData, + environmentVariables, + previousResponse, + ); + const body = this.extractBody( + requestData, + environmentVariables, + previousResponse, + ); + const contentType = this.extractDataType(requestData); + return [url, method, headers, body, contentType]; + } + + private mapToBodyType = (selectedBodyType: string): string => { + switch (selectedBodyType) { + case "application/json": + case "application/xml": + case "text/plain": + case "text/html": + case "raw": + return "raw"; + case "application/x-www-form-urlencoded": + case "urlencoded": + return "urlencoded"; + case "multipart/form-data": + case "formdata": + return "formdata"; + case "none": + default: + return "none"; + } + }; + + /** + * Extract and format the request body based on body type. + */ + private extractBody = ( + requestData: RequestData, + environmentVariables: any[] = [], + previousResponse?: any, + ): string => { + const bodyType = this.mapToBodyType( + requestData.selectedRequestBodyType || "", + ); + const body = requestData.body; + + if (!body) return ""; + + switch (bodyType) { + case "raw": + const rawText = this.setEnvironmentVariables( + body.raw || "", + environmentVariables, + ); + if (!rawText || rawText.trim() === "") return "{}"; + + const evaluated = this.setDynamicExpression2(rawText, previousResponse); + if (evaluated === "") return "{}"; + + // Try to parse as JSON for formatting + try { + const parsed = JSON5.parse(evaluated); + return JSON.stringify(parsed, null, 2); + } catch { + return evaluated; + } + + case "urlencoded": + const urlencodedData = this.extractKeyValue(body.urlencoded); + const processedUrlencoded = urlencodedData.map((item) => ({ + key: this.setEnvironmentVariables(item.key, environmentVariables), + value: this.setEnvironmentVariables( + String(item.value), + environmentVariables, + ), + })); + + const urlEncodedJson = JSON.stringify(processedUrlencoded); + return this.setDynamicExpression(urlEncodedJson, previousResponse); + + case "formdata": + const formDataItems: any[] = []; + + // Add text fields + if (body.formdata?.text) { + const textFields = this.extractKeyValue(body.formdata.text); + textFields.forEach((field) => { + formDataItems.push({ + key: this.setEnvironmentVariables( + field.key, + environmentVariables, + ), + value: this.setEnvironmentVariables( + String(field.value), + environmentVariables, + ), + type: "text", + }); + }); + } + + // Add file fields + if (body.formdata?.file) { + body.formdata.file.forEach((field) => { + if (field.checked !== false) { + formDataItems.push({ + key: this.setEnvironmentVariables( + field.key || "", + environmentVariables, + ), + value: field.value || "", + base: field.base || "", + type: "file", + }); + } + }); + } + + return this.setDynamicExpression( + JSON.stringify(formDataItems), + previousResponse, + ); + + case "none": + default: + return ""; + } + }; + + /** + * Map body type to content type - now handles both body types and content types + */ + private extractDataType = (requestData: RequestData): string => { + const selectedType = requestData.selectedRequestBodyType; + // If it's already a content type, return it + if (selectedType?.includes("/")) { + return selectedType; + } + // Otherwise map from body type to content type + const bodyType = this.mapToBodyType(selectedType || ""); + switch (bodyType) { + case "raw": + return "application/json"; + case "urlencoded": + return "application/x-www-form-urlencoded"; + case "formdata": + return "multipart/form-data"; + case "none": + default: + return "text/plain"; + } + }; +} + +export { DecodeTestflow, type RequestData }; diff --git a/src/utils/parse-time.ts b/src/utils/parse-time.ts new file mode 100644 index 0000000..0976996 --- /dev/null +++ b/src/utils/parse-time.ts @@ -0,0 +1,17 @@ +export class ParseTime { + public convertMilliseconds(ms: number): string { + const seconds = ms / 1000; + const minutes = seconds / 60; + const hours = minutes / 60; + + if (hours >= 1) { + return `${hours.toFixed(2)} hr`; + } else if (minutes >= 1) { + return `${minutes.toFixed(2)} min`; + } else if (seconds >= 1) { + return `${seconds.toFixed(2)} sec`; + } else { + return `${ms} ms`; + } + } +} diff --git a/yarn.lock b/yarn.lock index 2660054..f924998 100644 --- a/yarn.lock +++ b/yarn.lock @@ -352,6 +352,101 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== +"@fastify/ajv-compiler@^3.5.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@fastify/ajv-compiler/-/ajv-compiler-3.6.0.tgz#907497a0e62a42b106ce16e279cf5788848e8e79" + integrity sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ== + dependencies: + ajv "^8.11.0" + ajv-formats "^2.1.1" + fast-uri "^2.0.0" + +"@fastify/ajv-compiler@^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@fastify/ajv-compiler/-/ajv-compiler-4.0.2.tgz#da05938cf852901bfb953738764f553b5449b80b" + integrity sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ== + dependencies: + ajv "^8.12.0" + ajv-formats "^3.0.1" + fast-uri "^3.0.0" + +"@fastify/cors@11.1.0": + version "11.1.0" + resolved "https://registry.yarnpkg.com/@fastify/cors/-/cors-11.1.0.tgz#09f79748f08f147d19cfc3f1807b59791bc77cf0" + integrity sha512-sUw8ed8wP2SouWZTIbA7V2OQtMNpLj2W6qJOYhNdcmINTu6gsxVYXjQiM9mdi8UUDlcoDDJ/W2syPo1WB2QjYA== + dependencies: + fastify-plugin "^5.0.0" + toad-cache "^3.7.0" + +"@fastify/error@^3.3.0", "@fastify/error@^3.4.0": + version "3.4.1" + resolved "https://registry.yarnpkg.com/@fastify/error/-/error-3.4.1.tgz#b14bb4cac3dd4ec614becbc643d1511331a6425c" + integrity sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ== + +"@fastify/error@^4.0.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@fastify/error/-/error-4.2.0.tgz#d40f46ba75f541fdcc4dc276b7308bbc8e8e6d7a" + integrity sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ== + +"@fastify/fast-json-stringify-compiler@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz#5df89fa4d1592cbb8780f78998355feb471646d5" + integrity sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA== + dependencies: + fast-json-stringify "^5.7.0" + +"@fastify/fast-json-stringify-compiler@^5.0.0": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz#fae495bf30dbbd029139839ec5c2ea111bde7d3f" + integrity sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ== + dependencies: + fast-json-stringify "^6.0.0" + +"@fastify/formbody@8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@fastify/formbody/-/formbody-8.0.2.tgz#7f97c8ab25933db77760bbeaacd2ff5355a54682" + integrity sha512-84v5J2KrkXzjgBpYnaNRPqwgMsmY7ZDjuj0YVuMR3NXCJRCgKEZy/taSP1wUYGn0onfxJpLyRGDLa+NMaDJtnA== + dependencies: + fast-querystring "^1.1.2" + fastify-plugin "^5.0.0" + +"@fastify/forwarded@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@fastify/forwarded/-/forwarded-3.0.0.tgz#0fc96cdbbb5a38ad453d2d5533a34f09b4949b37" + integrity sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA== + +"@fastify/merge-json-schemas@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz#3551857b8a17a24e8c799e9f51795edb07baa0bc" + integrity sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA== + dependencies: + fast-deep-equal "^3.1.3" + +"@fastify/merge-json-schemas@^0.2.0": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz#3aa30d2f0c81a8ac5995b6d94ed4eaa2c3055824" + integrity sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A== + dependencies: + dequal "^2.0.3" + +"@fastify/middie@9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@fastify/middie/-/middie-9.0.3.tgz#7d8edfd90dca9ff8e82dc721a460ff3d8646f230" + integrity sha512-7OYovKXp9UKYeVMcjcFLMcSpoMkmcZmfnG+eAvtdiatN35W7c+r9y1dRfpA+pfFVNuHGGqI3W+vDTmjvcfLcMA== + dependencies: + "@fastify/error" "^4.0.0" + fastify-plugin "^5.0.0" + path-to-regexp "^8.1.0" + reusify "^1.0.4" + +"@fastify/proxy-addr@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@fastify/proxy-addr/-/proxy-addr-5.0.0.tgz#e9d1c7a49b8380d9f92a879fdc623ac47ee27de3" + integrity sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA== + dependencies: + "@fastify/forwarded" "^3.0.0" + ipaddr.js "^2.1.0" + "@humanwhocodes/config-array@^0.13.0": version "0.13.0" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" @@ -723,6 +818,20 @@ multer "1.4.4-lts.1" tslib "2.7.0" +"@nestjs/platform-fastify@^11.1.6": + version "11.1.6" + resolved "https://registry.yarnpkg.com/@nestjs/platform-fastify/-/platform-fastify-11.1.6.tgz#22524df44cad32012858566f1b3bb627ce2788f8" + integrity sha512-udnIg7vfA103wppRkcMRVWX71S7NfeDnlprTndhcZzYXcDY2i5c+RwrQN/xU4Aw5X22Fg8ryi7bFbn6/Lquv8w== + dependencies: + "@fastify/cors" "11.1.0" + "@fastify/formbody" "8.0.2" + "@fastify/middie" "9.0.3" + fast-querystring "1.1.2" + fastify "5.4.0" + light-my-request "6.6.0" + path-to-regexp "8.2.0" + tslib "2.8.1" + "@nestjs/platform-socket.io@^10.4.8": version "10.4.8" resolved "https://registry.yarnpkg.com/@nestjs/platform-socket.io/-/platform-socket.io-10.4.8.tgz#cf483794f3b1831d804a3ac3a3f7b999664489d4" @@ -1096,6 +1205,11 @@ "@types/methods" "^1.1.4" "@types/superagent" "^8.1.0" +"@types/validator@^13.11.8": + version "13.15.3" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.15.3.tgz#67e8aeacbace03517f9bd3f99e750bb666207ff4" + integrity sha512-7bcUmDyS6PN3EuD9SlGGOxM77F8WLVsrwkxyWxKnxzmXoequ6c7741QBrANq6htVRGOITJ7z72mTP6Z4XyuG+Q== + "@types/ws@^8.5.13": version "8.5.13" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.13.tgz#6414c280875e2691d0d1e080b05addbf5cb91e20" @@ -1337,6 +1451,11 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +abstract-logging@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/abstract-logging/-/abstract-logging-2.0.1.tgz#6b0c371df212db7129b57d2e7fcf282b8bf1c839" + integrity sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA== + accepts@~1.3.4, accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -1362,13 +1481,20 @@ acorn@^8.11.0, acorn@^8.14.0, acorn@^8.4.1, acorn@^8.8.2, acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== -ajv-formats@2.1.1: +ajv-formats@2.1.1, ajv-formats@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== dependencies: ajv "^8.0.0" +ajv-formats@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" + integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== + dependencies: + ajv "^8.0.0" + ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" @@ -1394,7 +1520,7 @@ ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0: +ajv@^8.0.0, ajv@^8.10.0, ajv@^8.11.0, ajv@^8.12.0: version "8.17.1" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== @@ -1503,6 +1629,27 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +atomic-sleep@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" + integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== + +avvio@^8.3.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/avvio/-/avvio-8.4.0.tgz#7cbd5bca74f0c9effa944ced601f94ffd8afc5ed" + integrity sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA== + dependencies: + "@fastify/error" "^3.3.0" + fastq "^1.17.1" + +avvio@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/avvio/-/avvio-9.1.0.tgz#0ff80ed211682441d8aa39ff21a4b9d022109c44" + integrity sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw== + dependencies: + "@fastify/error" "^4.0.0" + fastq "^1.17.1" + axios@^1.7.7: version "1.7.7" resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" @@ -1777,6 +1924,25 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz#707413784dbb3a72aa11c2f2b042a0bef4004170" integrity sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA== +class-transformer-validator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/class-transformer-validator/-/class-transformer-validator-0.9.1.tgz#81af4bab5e13ce619a25a74cc70f723a8c4e2779" + integrity sha512-83/KFCyd6UiiwH6PlQS5y17O5TTx58CawvNI+XdrMs0Ig9QI5kiuzRqGcC/WrEpd1F7i4KIxCwdn6m4B6fl0jw== + +class-transformer@0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336" + integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw== + +class-validator@^0.14.0: + version "0.14.2" + resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.14.2.tgz#a3de95edd26b703e89c151a2023d3c115030340d" + integrity sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw== + dependencies: + "@types/validator" "^13.11.8" + libphonenumber-js "^1.11.1" + validator "^13.9.0" + cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" @@ -1924,11 +2090,16 @@ cookie@0.7.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== -cookie@~0.7.2: +cookie@^0.7.0, cookie@~0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== +cookie@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.0.2.tgz#27360701532116bd3f1f9416929d176afe1e4610" + integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA== + cookiejar@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" @@ -2039,6 +2210,11 @@ depd@2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== +dequal@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + destroy@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" @@ -2425,6 +2601,16 @@ external-editor@^3.0.3, external-editor@^3.1.0: iconv-lite "^0.4.24" tmp "^0.0.33" +fast-content-type-parse@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz#4087162bf5af3294d4726ff29b334f72e3a1092c" + integrity sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ== + +fast-decode-uri-component@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz#46f8b6c22b30ff7a81357d4f59abfae938202543" + integrity sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -2451,21 +2637,123 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-sta resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-json-stringify@^5.7.0, fast-json-stringify@^5.8.0: + version "5.16.1" + resolved "https://registry.yarnpkg.com/fast-json-stringify/-/fast-json-stringify-5.16.1.tgz#a6d0c575231a3a08c376a00171d757372f2ca46e" + integrity sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g== + dependencies: + "@fastify/merge-json-schemas" "^0.1.0" + ajv "^8.10.0" + ajv-formats "^3.0.1" + fast-deep-equal "^3.1.3" + fast-uri "^2.1.0" + json-schema-ref-resolver "^1.0.1" + rfdc "^1.2.0" + +fast-json-stringify@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/fast-json-stringify/-/fast-json-stringify-6.0.1.tgz#82f1cb45fa96d0ca24b601f1738066976d6e2430" + integrity sha512-s7SJE83QKBZwg54dIbD5rCtzOBVD43V1ReWXXYqBgwCwHLYAAT0RQc/FmrQglXqWPpz6omtryJQOau5jI4Nrvg== + dependencies: + "@fastify/merge-json-schemas" "^0.2.0" + ajv "^8.12.0" + ajv-formats "^3.0.1" + fast-uri "^3.0.0" + json-schema-ref-resolver "^2.0.0" + rfdc "^1.2.0" + fast-levenshtein@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-querystring@1.1.2, fast-querystring@^1.0.0, fast-querystring@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fast-querystring/-/fast-querystring-1.1.2.tgz#a6d24937b4fc6f791b4ee31dcb6f53aeafb89f53" + integrity sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg== + dependencies: + fast-decode-uri-component "^1.0.1" + +fast-redact@^3.1.1: + version "3.5.0" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.5.0.tgz#e9ea02f7e57d0cd8438180083e93077e496285e4" + integrity sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A== + fast-safe-stringify@2.1.1, fast-safe-stringify@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== +fast-uri@^2.0.0, fast-uri@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-2.4.0.tgz#67eae6fbbe9f25339d5d3f4c4234787b65d7d55e" + integrity sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA== + +fast-uri@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + fast-uri@^3.0.1: version "3.0.3" resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.3.tgz#892a1c91802d5d7860de728f18608a0573142241" integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw== +fastify-plugin@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/fastify-plugin/-/fastify-plugin-5.0.1.tgz#82d44e6fe34d1420bb5a4f7bee434d501e41939f" + integrity sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ== + +fastify@4.28.1: + version "4.28.1" + resolved "https://registry.yarnpkg.com/fastify/-/fastify-4.28.1.tgz#39626dedf445d702ef03818da33064440b469cd1" + integrity sha512-kFWUtpNr4i7t5vY2EJPCN2KgMVpuqfU4NjnJNCgiNB900oiDeYqaNDRcAfeBbOF5hGixixxcKnOU4KN9z6QncQ== + dependencies: + "@fastify/ajv-compiler" "^3.5.0" + "@fastify/error" "^3.4.0" + "@fastify/fast-json-stringify-compiler" "^4.3.0" + abstract-logging "^2.0.1" + avvio "^8.3.0" + fast-content-type-parse "^1.1.0" + fast-json-stringify "^5.8.0" + find-my-way "^8.0.0" + light-my-request "^5.11.0" + pino "^9.0.0" + process-warning "^3.0.0" + proxy-addr "^2.0.7" + rfdc "^1.3.0" + secure-json-parse "^2.7.0" + semver "^7.5.4" + toad-cache "^3.3.0" + +fastify@5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/fastify/-/fastify-5.4.0.tgz#82bf56e0bc36ba8dfb0bd372a0de8b62ccf3287c" + integrity sha512-I4dVlUe+WNQAhKSyv15w+dwUh2EPiEl4X2lGYMmNSgF83WzTMAPKGdWEv5tPsCQOb+SOZwz8Vlta2vF+OeDgRw== + dependencies: + "@fastify/ajv-compiler" "^4.0.0" + "@fastify/error" "^4.0.0" + "@fastify/fast-json-stringify-compiler" "^5.0.0" + "@fastify/proxy-addr" "^5.0.0" + abstract-logging "^2.0.1" + avvio "^9.0.0" + fast-json-stringify "^6.0.0" + find-my-way "^9.0.0" + light-my-request "^6.0.0" + pino "^9.0.0" + process-warning "^5.0.0" + rfdc "^1.3.1" + secure-json-parse "^4.0.0" + semver "^7.6.0" + toad-cache "^3.7.0" + +fastq@^1.17.1: + version "1.19.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + dependencies: + reusify "^1.0.4" + fastq@^1.6.0: version "1.17.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" @@ -2521,6 +2809,24 @@ finalhandler@1.3.1: statuses "2.0.1" unpipe "~1.0.0" +find-my-way@^8.0.0: + version "8.2.2" + resolved "https://registry.yarnpkg.com/find-my-way/-/find-my-way-8.2.2.tgz#f3e78bc6ead2da4fdaa201335da3228600ed0285" + integrity sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA== + dependencies: + fast-deep-equal "^3.1.3" + fast-querystring "^1.0.0" + safe-regex2 "^3.1.0" + +find-my-way@^9.0.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/find-my-way/-/find-my-way-9.3.0.tgz#9f57786b5d772cc45142bf39dd5349f9cc883f91" + integrity sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg== + dependencies: + fast-deep-equal "^3.1.3" + fast-querystring "^1.0.0" + safe-regex2 "^5.0.0" + find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -2913,7 +3219,7 @@ ipaddr.js@1.9.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -ipaddr.js@^2.2.0: +ipaddr.js@^2.1.0, ipaddr.js@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== @@ -3473,6 +3779,20 @@ json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-schema-ref-resolver@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz#6586f483b76254784fc1d2120f717bdc9f0a99bf" + integrity sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw== + dependencies: + fast-deep-equal "^3.1.3" + +json-schema-ref-resolver@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/json-schema-ref-resolver/-/json-schema-ref-resolver-2.0.1.tgz#c92f16b452df069daac53e1984159e0f9af0598d" + integrity sha512-HG0SIB9X4J8bwbxCbnd5FfPEbcXAJYTi1pBJeP/QPON+w8ovSME8iRG+ElHNxZNX2Qh6eYn1GdzJFS4cDFfx0Q== + dependencies: + dequal "^2.0.3" + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -3537,6 +3857,29 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +libphonenumber-js@^1.11.1: + version "1.12.22" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.12.22.tgz#2a56f633173cdb91bd173f5984ec23198eebe5d0" + integrity sha512-nzdkDyqlcLV754o1RrOJxh8kycG+63odJVUqnK4dxhw7buNkdTqJc/a/CE0h599dTJgFbzvr6GEOemFBSBryAA== + +light-my-request@6.6.0, light-my-request@^6.0.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/light-my-request/-/light-my-request-6.6.0.tgz#c9448772323f65f33720fb5979c7841f14060add" + integrity sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A== + dependencies: + cookie "^1.0.1" + process-warning "^4.0.0" + set-cookie-parser "^2.6.0" + +light-my-request@^5.11.0: + version "5.14.0" + resolved "https://registry.yarnpkg.com/light-my-request/-/light-my-request-5.14.0.tgz#11ddae56de4053fd5c1845cbfbee5c29e8a257e7" + integrity sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA== + dependencies: + cookie "^0.7.0" + process-warning "^3.0.0" + set-cookie-parser "^2.4.1" + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -3838,6 +4181,11 @@ object-inspect@^1.13.1: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a" integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== +on-exit-leak-free@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" + integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== + on-finished@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -3989,6 +4337,16 @@ path-to-regexp@3.3.0: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b" integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw== +path-to-regexp@8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" + integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== + +path-to-regexp@^8.1.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz#aa818a6981f99321003a08987d3cec9c3474cd1f" + integrity sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA== + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -4009,6 +4367,35 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pino-abstract-transport@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz#de241578406ac7b8a33ce0d77ae6e8a0b3b68a60" + integrity sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw== + dependencies: + split2 "^4.0.0" + +pino-std-serializers@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz#7c625038b13718dbbd84ab446bd673dc52259e3b" + integrity sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA== + +pino@^9.0.0: + version "9.11.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-9.11.0.tgz#7fc383f815cf6bf5979b4d791eafd2f2496c42a6" + integrity sha512-+YIodBB9sxcWeR8PrXC2K3gEDyfkUuVEITOcbqrfcj+z5QW4ioIcqZfYFbrLTYLsmAwunbS7nfU/dpBB6PZc1g== + dependencies: + atomic-sleep "^1.0.0" + fast-redact "^3.1.1" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^2.0.0" + pino-std-serializers "^7.0.0" + process-warning "^5.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.2.0" + safe-stable-stringify "^2.3.1" + sonic-boom "^4.0.1" + thread-stream "^3.0.0" + pirates@^4.0.4: version "4.0.6" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" @@ -4057,6 +4444,21 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process-warning@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-3.0.0.tgz#96e5b88884187a1dce6f5c3166d611132058710b" + integrity sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ== + +process-warning@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-4.0.1.tgz#5c1db66007c67c756e4e09eb170cdece15da32fb" + integrity sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q== + +process-warning@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-5.0.0.tgz#566e0bf79d1dff30a72d8bbbe9e8ecefe8d378d7" + integrity sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA== + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -4065,7 +4467,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -proxy-addr@~2.0.7: +proxy-addr@^2.0.7, proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== @@ -4107,6 +4509,11 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -4163,6 +4570,11 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +real-require@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" + integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== + reflect-metadata@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b" @@ -4222,11 +4634,26 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" +ret@~0.4.0: + version "0.4.3" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.4.3.tgz#5243fa30e704a2e78a9b9b1e86079e15891aa85c" + integrity sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ== + +ret@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.5.0.tgz#30a4d38a7e704bd96dc5ffcbe7ce2a9274c41c95" + integrity sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw== + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rfdc@^1.2.0, rfdc@^1.3.0, rfdc@^1.3.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" + integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== + rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -4268,6 +4695,25 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-regex2@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/safe-regex2/-/safe-regex2-3.1.0.tgz#fd7ec23908e2c730e1ce7359a5b72883a87d2763" + integrity sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug== + dependencies: + ret "~0.4.0" + +safe-regex2@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/safe-regex2/-/safe-regex2-5.0.0.tgz#762e4a4c328603427281d2b99662f2d04e4ae811" + integrity sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw== + dependencies: + ret "~0.5.0" + +safe-stable-stringify@^2.3.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd" + integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== + "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -4282,6 +4728,16 @@ schema-utils@^3.1.1, schema-utils@^3.2.0: ajv "^6.12.5" ajv-keywords "^3.5.2" +secure-json-parse@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862" + integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw== + +secure-json-parse@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-4.0.0.tgz#2ee1b7581be38ab348bab5a3e49280ba80a89c85" + integrity sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA== + semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" @@ -4292,6 +4748,11 @@ semver@^7.3.4, semver@^7.3.5, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semve resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== +semver@^7.6.0: + version "7.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + send@0.19.0: version "0.19.0" resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" @@ -4328,6 +4789,11 @@ serve-static@1.16.2: parseurl "~1.3.3" send "0.19.0" +set-cookie-parser@^2.4.1, set-cookie-parser@^2.6.0: + version "2.7.1" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz#3016f150072202dfbe90fadee053573cc89d2943" + integrity sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ== + set-function-length@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" @@ -4426,6 +4892,13 @@ socket.io@4.8.0: socket.io-adapter "~2.5.2" socket.io-parser "~4.2.4" +sonic-boom@^4.0.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-4.2.0.tgz#e59a525f831210fa4ef1896428338641ac1c124d" + integrity sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww== + dependencies: + atomic-sleep "^1.0.0" + source-map-support@0.5.13: version "0.5.13" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" @@ -4452,6 +4925,11 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +split2@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -4667,6 +5145,13 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +thread-stream@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-3.1.0.tgz#4b2ef252a7c215064507d4ef70c05a5e2d34c4f1" + integrity sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A== + dependencies: + real-require "^0.2.0" + through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -4691,6 +5176,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toad-cache@^3.3.0, toad-cache@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/toad-cache/-/toad-cache-3.7.0.tgz#b9b63304ea7c45ec34d91f1d2fa513517025c441" + integrity sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw== + toidentifier@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" @@ -4779,7 +5269,7 @@ tslib@2.7.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== -tslib@^2.1.0, tslib@^2.6.2: +tslib@2.8.1, tslib@^2.1.0, tslib@^2.6.2: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -4885,6 +5375,11 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" +validator@^13.9.0: + version "13.15.15" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.15.15.tgz#246594be5671dc09daa35caec5689fcd18c6e7e4" + integrity sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A== + vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"