diff --git a/jest.config.js b/jest.config.js index bf6e047e68..e28ffcdb75 100644 --- a/jest.config.js +++ b/jest.config.js @@ -97,6 +97,7 @@ module.exports = { `./packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/openapi/openapi-validation-go.test.ts`, `./packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/openapi/openapi-validation.test.ts`, `./packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/unit/identity-internal-crypto-utils.test.ts`, + `./packages/cactus-plugin-ledger-connector-cdl-socketio/src/test/typescript/integration/cdl-connector-manual.test.ts`, `./packages/cactus-plugin-keychain-vault/src/test/typescript/integration/cactus-keychain-vault-server.test.ts`, `./packages/cactus-plugin-keychain-vault/src/test/typescript/integration/plugin-keychain-vault.test.ts`, `./packages/cactus-plugin-keychain-vault/src/test/typescript/integration/openapi/openapi-validation.test.ts`, diff --git a/packages/cactus-plugin-ledger-connector-cdl-socketio/Dockerfile b/packages/cactus-plugin-ledger-connector-cdl-socketio/Dockerfile new file mode 100644 index 0000000000..b1a951028e --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl-socketio/Dockerfile @@ -0,0 +1,13 @@ +# TODO +# Install connector as yarn package like in @hyperledger/cactus-plugin-ledger-connector-besu + +FROM node:18 + +WORKDIR /root/cactus/ + +COPY ./dist ./dist/ +COPY ./dist/yarn.lock ./package.json ./ +RUN yarn install --production --ignore-engines --non-interactive --cache-folder ./.yarnCache; rm -rf ./.yarnCache + +EXPOSE 5061 +CMD [ "npm", "run", "start" ] diff --git a/packages/cactus-plugin-ledger-connector-cdl-socketio/README.md b/packages/cactus-plugin-ledger-connector-cdl-socketio/README.md new file mode 100644 index 0000000000..0c6d63976b --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl-socketio/README.md @@ -0,0 +1,87 @@ +# `@hyperledger/cactus-plugin-ledger-connector-cdl-socketio` + +This plugin provides `Cacti` a way to interact with Fujitsu CDL networks. Using this we can perform: + +- `sendSyncRequest`: Send sync-typed requests to the API. +- `sendAsyncRequest`: Send async-typed requests to the API. + +## Getting started + +### Required software components + +- OS: Linux (recommended Ubuntu20.04,18.04 or CentOS7) +- Docker (recommend: v17.06.2-ce or greater) +- node.js v12 (recommend: v12.20.2 or greater) + +## Boot methods + +### Common setup + +1. Always run configure command first, from the project root directory: + + ```bash + pushd ../.. + npm run configure + popd + ``` + +1. Copy default configuration + +- **Remember to replace default CA and to adjust the `default.yaml` configuration on production deployments!** + ```bash + mkdir -p /etc/cactus/connector-cdl-socketio + rm -r /etc/cactus/connector-cdl-socketio/* + cp -rf ./sample-config/* /etc/cactus/connector-cdl-socketio/ + ``` + +#### Configuring CDL API Gateway Access + +- Set the base URL of GW service in `cdlApiGateway.url`. Do not include `api/v1`, just the base URL. (example: `"http://localhost:3000"`). +- If the service certificate is signed with a known CA (node uses Mozilla DB), then you can skip the next steps. +- If the service is signed with unknown CA, you can specify the gateway certificate to trust manually: + - Set `cdlApiGateway.caPath` to path of API Gateway certificate (in PEM format). (example: `"/etc/cactus/connector-cdl-socketio/CA/cdl-api-gateway-ca.pem"`) + - (optional) If server name in cert doesn't match the one in `cdlApiGateway.url`, you can overwrite it in `cdlApiGateway.serverName` +- (not recommended - only for development): To ignore certificate rejection (e.g. use self-signed certificate) set `cdlApiGateway.skipCertCheck` to `true`. + +### Docker + +- Docker build process will use artifacts from the latest build. Make sure `./dist` contains the version you want to dockerize. + +``` +# Build +DOCKER_BUILDKIT=1 docker build ./packages/cactus-plugin-ledger-connector-cdl-socketio -t cactus-plugin-ledger-connector-cdl-socketio + +# Run +docker run -v/etc/cactus/:/etc/cactus -p 5061:5061 cactus-plugin-ledger-connector-cdl-socketio +``` + +### Manual + +``` +npm run start +``` + +## Configuration + +- Validator can be configured in `/etc/cactus/connector-cdl-socketio/default.yaml` (see [sample-config](./sample-config/default.yaml) for details). + +## Manual Tests + +- There are no automatic tests for this plugin. +- `cdl-connector-manual.test` contains a Jest test script that will check every implemented operation on a running CDL instance. +- **You need access to a running instance of CDL in order to run this script.** +- Before running the script you must update the following variables in it: + - `ACCESS_TOKEN` - JWT token for authorization in CDL gateway. + - `TOKEN_AGENT_ID` - Token agent ID. + - `VALIDATOR_KEY_PATH` - Path to validator public certificate. +- Script can be used as a quick reference for using this connector plugin. + +## Contributing + +We welcome contributions to Hyperledger Cacti in many forms, and there's always plenty to do! + +Please review [CONTIRBUTING.md](../../CONTRIBUTING.md) to get started. + +## License + +This distribution is published under the Apache License Version 2.0 found in the [LICENSE](../../LICENSE) file. diff --git a/packages/cactus-plugin-ledger-connector-cdl-socketio/package.json b/packages/cactus-plugin-ledger-connector-cdl-socketio/package.json new file mode 100644 index 0000000000..d7d0b3c2a3 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl-socketio/package.json @@ -0,0 +1,84 @@ +{ + "name": "@hyperledger/cactus-plugin-ledger-connector-cdl-socketio", + "version": "2.0.0-alpha.1", + "description": "Allows Cacti nodes to connect to Fujitsu CDL.", + "keywords": [ + "Hyperledger", + "Cacti", + "Integration", + "Blockchain", + "Distributed Ledger Technology" + ], + "homepage": "https://github.com/hyperledger/cacti#readme", + "bugs": { + "url": "https://github.com/hyperledger/cacti/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hyperledger/cacti.git" + }, + "license": "Apache-2.0", + "author": { + "name": "Hyperledger Cacti Contributors", + "email": "cacti@lists.hyperledger.org", + "url": "https://www.hyperledger.org/use/cacti" + }, + "contributors": [ + { + "name": "Please add yourself to the list of contributors", + "email": "your.name@example.com", + "url": "https://example.com" + }, + { + "name": "Michal Bajer", + "email": "michal.bajer@fujitsu.com", + "url": "https://www.fujitsu.com/global/" + } + ], + "main": "dist/index.js", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist/*" + ], + "scripts": { + "build": "npm run build-ts && npm run build:dev:backend:postbuild", + "build-ts": "tsc", + "build:dev:backend:postbuild": "npm run init-cdl", + "debug": "nodemon --inspect ./dist/lib/main/typescript/common/core/bin/www.js", + "init-cdl": "cp -af ../../yarn.lock ./dist/yarn.lock", + "start": "node ./dist/lib/main/typescript/common/core/bin/www.js" + }, + "dependencies": { + "axios": "0.27.2", + "body-parser": "1.20.2", + "config": "3.3.7", + "cookie-parser": "1.4.6", + "express": "4.18.2", + "fast-safe-stringify": "2.1.1", + "http-errors": "1.6.3", + "js-yaml": "3.14.1", + "jsonwebtoken": "9.0.0", + "log4js": "6.4.1", + "sanitize-html": "2.7.0", + "socket.io": "4.5.4" + }, + "devDependencies": { + "@hyperledger/cactus-api-client": "2.0.0-alpha.1", + "@hyperledger/cactus-common": "2.0.0-alpha.1", + "@types/config": "0.0.41", + "@types/express": "4.17.13", + "@types/node": "14.17.32", + "@types/sanitize-html": "2.6.2", + "jest-extended": "0.11.5", + "uuid": "8.3.2" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "publishConfig": { + "access": "public" + }, + "watch": {} +} diff --git a/packages/cactus-plugin-ledger-connector-cdl-socketio/sample-config/default.yaml b/packages/cactus-plugin-ledger-connector-cdl-socketio/sample-config/default.yaml new file mode 100644 index 0000000000..15a82aa28d --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl-socketio/sample-config/default.yaml @@ -0,0 +1,11 @@ +sslParam: + port: 5061 + key: "/etc/cactus/connector-cdl-socketio/CA/connector.priv" + cert: "/etc/cactus/connector-cdl-socketio/CA/connector.crt" +logLevel: "debug" +userAgent: "CactiCDLConnector" +cdlApiGateway: + url: "http://localhost:3000" + #skipCertCheck: true # Set to true to ignore self-signed and other rejected certificates + #caPath: "/etc/cactus/connector-cdl-socketio/CA/cdl-api-gateway-ca.pem" # CA of CDL API gateway server in PEM format to use + #serverName: "cdl.fujitsu" # Overwrite server name from cdlApiGateway.url to match one specified in CA diff --git a/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/common/core/app.ts b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/common/core/app.ts new file mode 100644 index 0000000000..14da5c56ca --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/common/core/app.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2023 Hyperledger Cacti Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * app.js + */ + +/* Summary: + * + */ + +import { NextFunction, Request, Response } from "express"; +import createError from "http-errors"; +import express from "express"; +import cookieParser from "cookie-parser"; +import bodyParser from "body-parser"; + +const app: express.Express = express(); + +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(cookieParser()); + +// catch 404 and forward to error handler +app.use((req: Request, res: Response, next: NextFunction) => { + next(createError(404)); +}); + +// error handler +app.use( + ( + err: { message: string; status?: number }, + req: Request, + res: Response, + next: NextFunction, + ) => { + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get("env") === "development" ? err : {}; + + // set erreor response + const errorResponse: {} = { + statusCode: err.status || 500, + message: err.message, + }; + + // render the error page + res.status(err.status || 500); + res.send(errorResponse); + }, +); + +export default app; diff --git a/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/common/core/bin/www.ts b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/common/core/bin/www.ts new file mode 100644 index 0000000000..181ed024a4 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/common/core/bin/www.ts @@ -0,0 +1,176 @@ +#!/usr/bin/env node + +import app from "../app"; +import { read as configRead } from "../config"; + +import axios from "axios"; +import https from "https"; +import fs from "fs"; +import { Server } from "socket.io"; +import safeStringify from "fast-safe-stringify"; +import sanitizeHtml from "sanitize-html"; + +// Log settings +import { getLogger } from "log4js"; +const logger = getLogger("connector_main[" + process.pid + "]"); +logger.level = configRead("logLevel", "info"); + +// implementation class of a part dependent of end-chains (server plugin) +import { ServerPlugin } from "../../../connector/ServerPlugin"; +import { ValidatorAuthentication } from "../../../connector/ValidatorAuthentication"; + +// Normalize a port into a number, string, or false. +function normalizePort(val: string) { + const port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Return secure string representation of error from the input. + * Handles circular structures and removes HTML. + * + * @param error Any object to return as an error, preferable `Error` + * @returns Safe string representation of an error. + */ +export function safeStringifyException(error: unknown): string { + if (axios.isAxiosError(error)) { + return safeStringify(error.toJSON()); + } + + if (error instanceof Error) { + return sanitizeHtml(error.stack || error.message); + } + + return sanitizeHtml(safeStringify(error)); +} + +export async function startCDLSocketIOConnector() { + const Splug = new ServerPlugin(); + + // Get port from environment and store in Express. + const sslport = normalizePort( + process.env.PORT || configRead("sslParam.port"), + ); + app.set("port", sslport); + + // Specify private key and certificate + let keyString: string; + let certString: string; + try { + keyString = configRead("sslParam.keyValue"); + certString = configRead("sslParam.certValue"); + } catch { + keyString = fs.readFileSync(configRead("sslParam.key"), "ascii"); + certString = fs.readFileSync(configRead("sslParam.cert"), "ascii"); + } + + // Create HTTPS server. + const server = https.createServer( + { + key: keyString, + cert: certString, + }, + app, + ); // Start as an https server. + const io = new Server(server); + + // Event listener for HTTPS server "error" event. + server.on("error", (error: any) => { + if (error.syscall !== "listen") { + throw error; + } + + const bind = + typeof sslport === "string" ? "Pipe " + sslport : "Port " + sslport; + + // handle specific listen errors with friendly messages + switch (error.code) { + case "EACCES": + console.error(bind + " requires elevated privileges"); + process.exit(1); + break; + case "EADDRINUSE": + console.error(bind + " is already in use"); + process.exit(1); + break; + default: + throw error; + } + }); + + io.on("connection", (client) => { + logger.info("Client " + client.id + " connected."); + + client.on("request2", async (data) => { + try { + const result = await Splug.executeFunction({ + method: data.method, + args: data.args, + reqID: data.reqID, + }); + const response = { + resObj: { + status: 200, + data: ValidatorAuthentication.sign({ result }), + }, + id: data.reqID, + }; + logger.info("Client ID :" + client.id); + logger.info("Response:", JSON.stringify(response)); + client.emit("response", response); + } catch (error: unknown) { + const errorObj = { + resObj: { + status: 504, + errorDetail: safeStringifyException(error), + }, + id: data.reqID, + }; + logger.error("request2 connector_error:", JSON.stringify(errorObj)); + client.emit("connector_error", errorObj); + } + }); + + client.on("disconnect", function (reason) { + // Unexpected disconnect as well as explicit disconnect request can be received here + logger.info("Client", client.id, "disconnected."); + logger.info("Reason :", reason); + }); + }); + + // Listen on provided port, on all network interfaces. + return new Promise((resolve) => + server.listen(sslport, () => resolve(server)), + ); +} + +if (require.main === module) { + // When this file executed as a script, not loaded as module - run the connector + startCDLSocketIOConnector() + .then((server) => { + const addr = server.address(); + + if (!addr) { + logger.error("Could not get running server address - exit."); + process.exit(1); + } + + const bind = + typeof addr === "string" ? "pipe " + addr : "port " + addr.port; + logger.debug("Listening on " + bind); + }) + .catch((err) => { + logger.error("Could not start cdl-socketio connector:", err); + }); +} diff --git a/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/common/core/config.ts b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/common/core/config.ts new file mode 100644 index 0000000000..ffada9f71c --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/common/core/config.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2021 Hyperledger Cactus Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * config.js + */ + +// TODO - Common + +export const DEFAULT_NODE_CONFIG_DIR = "/etc/cactus/connector-cdl-socketio/"; +if (!process.env["NODE_CONFIG_DIR"]) { + // Must be set before import config + process.env["NODE_CONFIG_DIR"] = DEFAULT_NODE_CONFIG_DIR; +} + +import config from "config"; + +/** + * Get configuration entry (uses node-config setup) + * + * @param key : Key to retrieve + * @param defaultValue : Value to return if key is not present in the config. + * @returns : Configuration value + */ +export function read(key: string, defaultValue?: T): T { + if (config.has(key)) { + return config.get(key); + } + + if (typeof defaultValue !== "undefined") { + return defaultValue; + } + + throw Error( + `Missing configuration entry '${key}', config dir = ${process.env["NODE_CONFIG_DIR"]}`, + ); +} diff --git a/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/connector/ServerPlugin.ts b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/connector/ServerPlugin.ts new file mode 100644 index 0000000000..2bd613c1a3 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/connector/ServerPlugin.ts @@ -0,0 +1,228 @@ +import { read as configRead } from "../common/core/config"; +import { cdlRequest } from "./cdl-request"; +import sanitizeHtml from "sanitize-html"; +import safeStringify from "fast-safe-stringify"; + +// Log settings +import { getLogger } from "log4js"; +const logger = getLogger("ServerPlugin[" + process.pid + "]"); +logger.level = configRead("logLevel", "info"); + +type SupportedFunctions = + | "registerHistoryData" + | "getLineage" + | "searchByHeader" + | "searchByGlobalData" + | "status"; + +type FunctionArgsType = { + method: { + type: SupportedFunctions; + accessToken?: string; + trustAgentId?: string; + }; + args: any; + reqID?: string; +}; + +/* + * ServerPlugin + * Class definition for server plugins + */ +export class ServerPlugin { + /** + * Dispatch function that runs method with name from `args.method.type` with `args` arguments. + */ + async executeFunction(args: FunctionArgsType): Promise { + switch (args.method.type) { + case "registerHistoryData": + return this.registerHistoryData(args); + case "getLineage": + return this.getLineage(args); + case "searchByHeader": + return this.searchByHeader(args); + case "searchByGlobalData": + return this.searchByGlobalData(args); + case "status": + return this.status(args); + default: + const _unknownMethod: never = args.method.type; + throw new Error(`Unknown CDL ServerPlugin method: ${_unknownMethod}`); + } + } + + /** + * Throws if any property in an object starts with `cdl:` (not allowed by the API) + * @param properties object with string fields. + */ + private checkPropertyNames(properties?: Record) { + const invalidProps = Object.keys(properties ?? {}).filter((k) => + k.startsWith("cdl:"), + ); + if (invalidProps.length > 0) { + throw new Error( + `Properties can't start with 'cdl:'. Invalid properties provided: ${invalidProps}`, + ); + } + } + + /** + * Send request to `trail_registration` CDL endpoint. + */ + async registerHistoryData(args: FunctionArgsType): Promise { + logger.debug( + "ServerPlugin:registerHistoryData() args:", + JSON.stringify(args), + ); + + // Check args + const typedArgs = args.args as { + eventId?: string; + lineageId?: string; + tags?: Record; + properties?: Record; + }; + this.checkPropertyNames(typedArgs.tags); + this.checkPropertyNames(typedArgs.properties); + + const responseData = await cdlRequest( + `trail_registration`, + getAccessTokenOrThrow(args), + {}, + { + "cdl:EventId": typedArgs.eventId ?? "", + "cdl:LineageId": typedArgs.lineageId ?? "", + "cdl:Tags": typedArgs.tags, + ...typedArgs.properties, + }, + ); + + if (responseData.result !== "OK") { + throw new Error(sanitizeHtml(safeStringify(responseData))); + } + + logger.debug("registerHistoryData results:", responseData); + return responseData; + } + + /** + * Get data from `trail_acquisition` CDL endpoint. + */ + async getLineage(args: FunctionArgsType): Promise { + logger.debug("ServerPlugin:getLineage() args:", JSON.stringify(args)); + + // Check args + const typedArgs = args.args as { + eventId: string; + direction?: "backward" | "forward" | "both"; + depth?: string; + }; + + if (!typedArgs.eventId) { + throw new Error("Missing eventId in getLineage args!"); + } + const direction = (typedArgs.direction ?? "backward").toUpperCase(); + let depth = parseInt(typedArgs.depth ?? "-1", 10); + if (isNaN(depth)) { + logger.warn( + "Could not parse depth from the argument, using default (-1). Wrong input:", + typedArgs.depth, + ); + depth = -1; + } + + const responseData = await cdlRequest( + `trail_acquisition/${sanitizeHtml(typedArgs.eventId)}`, + getAccessTokenOrThrow(args), + { + direction, + depth, + }, + ); + + if (responseData.result !== "OK") { + throw new Error(sanitizeHtml(safeStringify(responseData))); + } + logger.debug("getLineage results:", responseData); + + return responseData; + } + + /** + * Search data using `trail_search_headers` CDL endpoint. + */ + async searchByHeader(args: FunctionArgsType): Promise { + logger.debug("ServerPlugin:searchByHeader() args:", JSON.stringify(args)); + return await searchRequest("trail_search_headers", args); + } + + /** + * Search data using `trail_search_globaldata` CDL endpoint. + */ + async searchByGlobalData(args: FunctionArgsType): Promise { + logger.debug( + "ServerPlugin:searchByGlobalData() args:", + JSON.stringify(args), + ); + return await searchRequest("trail_search_globaldata", args); + } + + /** + * Simple method to get current status of the connector plugin. + */ + async status(args: FunctionArgsType): Promise { + logger.debug("ServerPlugin:status() args:", JSON.stringify(args)); + return { + status: "OK.", + }; + } +} + +/** + * Common logic for sending trail search requests + */ +async function searchRequest( + searchType: "trail_search_headers" | "trail_search_globaldata", + args: FunctionArgsType, +) { + // Check args + const typedArgs = args.args as { + searchType: "exactmatch" | "partialmatch" | "regexpmatch"; + fields: Record; + }; + + if (!typedArgs.searchType || !typedArgs.fields) { + throw new Error("Missing required searchByHeader args!"); + } + + const responseData = await cdlRequest( + searchType, + getAccessTokenOrThrow(args), + {}, + { + searchType: typedArgs.searchType, + body: typedArgs.fields, + }, + ); + + if (responseData.result !== "OK") { + throw new Error(sanitizeHtml(safeStringify(responseData))); + } + + return responseData; +} + +function getAccessTokenOrThrow(args: FunctionArgsType): [string, string] { + const accessToken = args?.method?.accessToken; + const trustAgentId = args?.method?.trustAgentId; + + if (!accessToken) { + throw new Error("Missing CDL accessToken"); + } + + if (!trustAgentId) { + throw new Error("Missing CDL trustAgentId"); + } + + return [accessToken, trustAgentId]; +} diff --git a/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/connector/ValidatorAuthentication.ts b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/connector/ValidatorAuthentication.ts new file mode 100644 index 0000000000..86179338f8 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/connector/ValidatorAuthentication.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Hyperledger Cacti Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * ValidatorAuthentication.ts + */ + +import * as config from "../common/core/config"; + +import fs from "fs"; +import path from "path"; +import jwt from "jsonwebtoken"; + +import { getLogger } from "log4js"; +const logger = getLogger("ValidatorAuthentication[" + process.pid + "]"); +logger.level = config.read("logLevel", "info"); + +const privateKey = fs.readFileSync( + path.resolve(__dirname, config.read("sslParam.key")), +); + +export class ValidatorAuthentication { + static sign(payload: object): string { + const signature: string = jwt.sign(payload, privateKey, { + algorithm: "ES256", + expiresIn: "10000", + }); + logger.debug(`signature: OK`); + return signature; + } +} diff --git a/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/connector/cdl-request.ts b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/connector/cdl-request.ts new file mode 100644 index 0000000000..59aa4bcadf --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/connector/cdl-request.ts @@ -0,0 +1,82 @@ +import { read as configRead } from "../common/core/config"; +import axios from "axios"; +import https from "https"; + +// Log settings +import { getLogger } from "log4js"; +import { readFileSync } from "fs"; +const logger = getLogger("cdl-request[" + process.pid + "]"); +logger.level = configRead("logLevel", "info"); + +function getHttpsAgent() { + const agentOptions: https.AgentOptions = {}; + + const skipCertCheck = configRead( + "cdlApiGateway.skipCertCheck", + false, + ); + if (skipCertCheck) { + logger.info( + `Allowing self signed CDL API GW certificates (skipCertCheck=${skipCertCheck})`, + ); + agentOptions.rejectUnauthorized = !skipCertCheck; + } + + const caPath = configRead("cdlApiGateway.caPath", ""); + if (caPath) { + logger.info(`Using CDL API GW CA ${caPath}`); + const gatewayCAString = readFileSync(caPath, "ascii"); + logger.debug("CDL Gateway certificate read:", gatewayCAString); + agentOptions.ca = gatewayCAString; + } + + const serverName = configRead("cdlApiGateway.serverName", ""); + if (serverName) { + logger.info(`Overwrite CDL API GW server name with '${serverName}'`); + agentOptions.servername = serverName; + } + + return new https.Agent(agentOptions); +} + +const COMMON_HTTPS_AGENT = getHttpsAgent(); + +export async function cdlRequest( + url: string, + accessToken: [string, string], + queryParams?: any, + dataPayload?: any, +) { + let httpMethod = "get"; + if (dataPayload) { + httpMethod = "post"; + } + + logger.debug(`cdlRequest ${httpMethod} ${url} executed`); + + try { + const requestResponse = await axios({ + httpsAgent: COMMON_HTTPS_AGENT, + method: httpMethod, + baseURL: configRead("cdlApiGateway.url"), + url: `api/v1/${url}`, + responseType: "json", + headers: { + "User-Agent": configRead("userAgent", "CactiCDLConnector"), + Authorization: `Bearer ${accessToken[0]}`, + "Trust-Agent-Id": accessToken[1], + "Content-Type": "application/json;charset=UTF-8", + }, + params: queryParams, + data: dataPayload, + }); + + return requestResponse.data; + } catch (error: any) { + if ("toJSON" in error) { + logger.error("CDL API request failed:", error.toJSON()); + } + + throw error; + } +} diff --git a/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/index.ts b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/index.ts new file mode 100644 index 0000000000..87cb558397 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/index.ts @@ -0,0 +1 @@ +export * from "./public-api"; diff --git a/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/public-api.ts b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/public-api.ts new file mode 100644 index 0000000000..136baee164 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/main/typescript/public-api.ts @@ -0,0 +1 @@ +export { startCDLSocketIOConnector } from "./common/core/bin/www"; diff --git a/packages/cactus-plugin-ledger-connector-cdl-socketio/src/test/typescript/integration/cdl-connector-manual.test.ts b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/test/typescript/integration/cdl-connector-manual.test.ts new file mode 100644 index 0000000000..621de2b133 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/test/typescript/integration/cdl-connector-manual.test.ts @@ -0,0 +1,419 @@ +/** + * Manual tests for CDL connector. + * Must be exectued on Azure environment with access to CDL service. + * Check out CDL connector readme for instructions on how to run these tests. + */ + +////////////////////////////////// +// Constants +////////////////////////////////// + +const ACCESS_TOKEN = "_____FILL_TOKEN_HERE_____" +const TOKEN_AGENT_ID = "_____FILL_AGENT_ID_HERE_____" +const VALIDATOR_KEY_PATH = "_____FILL_KEY_PATH_HERE_____" + +const testLogLevel: LogLevelDesc = "info"; +const sutLogLevel: LogLevelDesc = "info"; +const setupTimeout = 1000 * 60; // 1 minute timeout for setup + +// ApiClient settings +const syncReqTimeout = 1000 * 10; // 10 seconds + +import { + LogLevelDesc, + LoggerProvider, + Logger, +} from "@hyperledger/cactus-common"; +import { SocketIOApiClient } from "@hyperledger/cactus-api-client"; + +import "jest-extended"; +import { Server as HttpsServer } from "https"; +import { v4 as uuidv4 } from "uuid"; + +import * as cdlConnector from "../../../main/typescript/index"; + +// Logger setup +const log: Logger = LoggerProvider.getOrCreate({ + label: "cdl-connector-manual.test", + level: testLogLevel, +}); + +describe("CDL Connector manual tests", () => { + let connectorServer: HttpsServer; + let apiClient: SocketIOApiClient; + + ////////////////////////////////// + // Helper Methods + ////////////////////////////////// + + async function registerHistoryDataOnCDL(args: any = {}) { + const response = await apiClient.sendSyncRequest( + {}, + { + type: "registerHistoryData", + accessToken: ACCESS_TOKEN, + trustAgentId: TOKEN_AGENT_ID, + }, + args, + ); + + log.debug("registerHistoryData response:", JSON.stringify(response)); + + expect(response.status).toEqual(200); + expect(response.data.result).toEqual("OK"); + const event = response.data.detail; + expect(event).toBeTruthy(); + expect(event["cdl:Lineage"]).toBeTruthy(); + expect(event["cdl:Lineage"]["cdl:EventId"]).toBeTruthy(); + expect(event["cdl:Lineage"]["cdl:LineageId"]).toBeTruthy(); + expect(event["cdl:Tags"]).toBeTruthy(); + expect(event["cdl:Verification"]).toBeTruthy(); + return event; + } + + async function getLineageFromCDL(args: any = {}): Promise { + const response = await apiClient.sendSyncRequest( + {}, + { + type: "getLineage", + accessToken: ACCESS_TOKEN, + trustAgentId: TOKEN_AGENT_ID, + }, + args, + ); + + log.debug("getLineage response:", JSON.stringify(response)); + + expect(response.status).toEqual(200); + expect(response.data.result).toEqual("OK"); + const eventList = response.data.detail; + expect(eventList).toBeTruthy(); + expect(eventList.length).toBeGreaterThan(0); + return eventList; + } + + async function searchByHeaderOnCDL(args: any = {}): Promise { + const response = await apiClient.sendSyncRequest( + {}, + { + type: "searchByHeader", + accessToken: ACCESS_TOKEN, + trustAgentId: TOKEN_AGENT_ID, + }, + args, + ); + + log.debug("searchByHeaderOnCDL response:", JSON.stringify(response)); + + expect(response.status).toEqual(200); + expect(response.data.result).toEqual("OK"); + const eventList = response.data.detail; + expect(eventList).toBeTruthy(); + return eventList; + } + + async function searchByGlobalDataOnCDL(args: any = {}): Promise { + const response = await apiClient.sendSyncRequest( + {}, + { + type: "searchByGlobalData", + accessToken: ACCESS_TOKEN, + trustAgentId: TOKEN_AGENT_ID, + }, + args, + ); + + log.debug("searchByGlobalData response:", JSON.stringify(response)); + + expect(response.status).toEqual(200); + expect(response.data.result).toEqual("OK"); + const eventList = response.data.detail; + expect(eventList).toBeTruthy(); + return eventList; + } + + ////////////////////////////////// + // Environment Setup + ////////////////////////////////// + + beforeAll(async () => { + // Run the connector + connectorServer = await cdlConnector.startCDLSocketIOConnector(); + expect(connectorServer).toBeTruthy(); + const connectorAddress = connectorServer.address(); + if (!connectorAddress || typeof connectorAddress === "string") { + throw new Error("Unexpected CDL connector AddressInfo type"); + } + log.info( + "CDL-SocketIO Connector started on:", + `${connectorAddress.address}:${connectorAddress.port}`, + ); + + // Create ApiClient instance + const apiConfigOptions = { + validatorID: "cdl-connector-manual.test", + validatorURL: `https://localhost:${connectorAddress.port}`, + validatorKeyPath: VALIDATOR_KEY_PATH, + logLevel: sutLogLevel, + maxCounterRequestID: 1000, + syncFunctionTimeoutMillisecond: syncReqTimeout, + socketOptions: { + rejectUnauthorized: false, + reconnection: false, + timeout: syncReqTimeout * 2, + }, + }; + log.debug("ApiClient config:", apiConfigOptions); + apiClient = new SocketIOApiClient(apiConfigOptions); + }, setupTimeout); + + afterAll(async () => { + log.info("FINISHING THE TESTS"); + + if (apiClient) { + log.info("Close ApiClient connection..."); + apiClient.close(); + } + + if (connectorServer) { + log.info("Stop the CDL connector..."); + await new Promise((resolve) => + connectorServer.close(() => resolve()), + ); + } + + // SocketIOApiClient has timeout running for each request which is not cancellable at the moment. + // Wait timeout amount of seconds to make sure all handles are closed. + await new Promise((resolve) => setTimeout(resolve, syncReqTimeout)); + }, setupTimeout); + + ////////////////////////////////// + // Tests + ////////////////////////////////// + + /** + * Test if connector was started correctly and communication is possible + */ + test("Get connector status", async () => { + const argsParam = { + args: ["test1", "test2"], + }; + + const response = await apiClient.sendSyncRequest( + {}, + { + type: "status", + }, + argsParam, + ); + + log.debug("Status response:", response); + expect(response.status).toEqual(200); + expect(response.data.status).toEqual("OK."); + }); + + test("Register single history data", async () => { + const newEvent = await registerHistoryDataOnCDL({ + eventId: "", + lineageId: "", + tags: { + test: "abc", + }, + properties: { + prop1: "abc", + prop2: "cba", + }, + }); + + // Check custom properties and tags + expect(newEvent["cdl:Event"]["prop1"]).toEqual("abc"); + expect(newEvent["cdl:Event"]["prop2"]).toEqual("cba"); + expect(newEvent["cdl:Tags"]["test"]).toEqual("abc"); + }); + + test("Register history data in signle lineage", async () => { + const firstEvent = await registerHistoryDataOnCDL(); + const firstEventId = firstEvent["cdl:Lineage"]["cdl:EventId"]; + + const secondEvent = await registerHistoryDataOnCDL({ + lineageId: firstEventId, + }); + + // Check if two events belong to same lineage + expect(secondEvent["cdl:Lineage"]["cdl:LineageId"]).toEqual(firstEventId); + }); + + /** + * Tests for getLineage endpoint + */ + describe("Get lineage tests", () => { + const eventsInLineage: string[] = []; + + beforeAll(async () => { + const firstEvent = await registerHistoryDataOnCDL(); + const firstEventId = firstEvent["cdl:Lineage"]["cdl:EventId"]; + log.info("First eventId (lineageId):", firstEventId); + eventsInLineage.push(firstEventId); + + for (let i = 0; i < 2; i++) { + const event = await registerHistoryDataOnCDL({ + lineageId: firstEventId, + }); + eventsInLineage.push(event["cdl:Lineage"]["cdl:EventId"]); + } + + log.info("Events in test lineage:", eventsInLineage); + }); + + // both middle + test("Get lineage forward all (default) on the first event (lineageId)", async () => { + const eventList = await getLineageFromCDL({ + eventId: eventsInLineage[0], + direction: "forward", + depth: "-1", + }); + + // Forward from first should return all events in lineage + expect(eventList.length).toEqual(eventsInLineage.length); + }); + + test("Get lineage forward all (default) on the last event", async () => { + const eventList = await getLineageFromCDL({ + eventId: eventsInLineage[eventsInLineage.length - 1], + direction: "forward", + depth: "-1", + }); + + // Forward from last should return only one event + expect(eventList.length).toEqual(1); + }); + + test("Get lineage backward all on the last event", async () => { + const eventList = await getLineageFromCDL({ + eventId: eventsInLineage[eventsInLineage.length - 1], + direction: "backward", + depth: "-1", + }); + + // Backward from last should return all events in lineage + expect(eventList.length).toEqual(eventsInLineage.length); + }); + + test("Get lineage both all on the middle event", async () => { + const eventList = await getLineageFromCDL({ + eventId: eventsInLineage[1], + direction: "both", + depth: "-1", + }); + + // Both on middle event should return all events in lineage + expect(eventList.length).toEqual(eventsInLineage.length); + }); + }); + + /** + * Tests for searchByHeader and searchByGlobalData endpoints + */ + describe("Search data tests", () => { + const privateTagValue = uuidv4(); + const customEventPropValue = uuidv4(); + let searchedEvent: any; + let searchedEventTimestamp: string; + + beforeAll(async () => { + searchedEvent = await registerHistoryDataOnCDL({ + tags: { + privateTag: privateTagValue, + }, + properties: { + customEventProp: customEventPropValue, + }, + }); + log.info("Event to search for:", searchedEvent); + + searchedEventTimestamp = + searchedEvent["cdl:Lineage"]["cdl:DataRegistrationTimeStamp"]; + }); + + test("Search header data using exact match", async () => { + log.info( + "Search for events with exact timestamp:", + searchedEventTimestamp, + ); + const events = await searchByHeaderOnCDL({ + searchType: "exactmatch", + fields: { + "cdl:DataRegistrationTimeStamp": searchedEventTimestamp, + }, + }); + + expect(events).toBeTruthy(); + expect(events.length).toBeGreaterThan(0); + for (const e of events) { + expect(e["cdl:Lineage"]["cdl:DataRegistrationTimeStamp"]).toEqual( + searchedEventTimestamp, + ); + } + }); + + test("Search header data using partial match", async () => { + const datePart = searchedEventTimestamp.split("T")[0]; + log.info("Search for events with partialmatch (date):", datePart); + + const events = await searchByHeaderOnCDL({ + searchType: "partialmatch", + fields: { + "cdl:DataRegistrationTimeStamp": datePart, + }, + }); + + expect(events).toBeTruthy(); + expect(events.length).toBeGreaterThan(0); + for (const e of events) { + expect(e["cdl:Lineage"]["cdl:DataRegistrationTimeStamp"]).toMatch( + datePart, + ); + } + }); + + test("Search header data using regex match", async () => { + const datePart = searchedEventTimestamp.split("T")[0]; + const dateRegex = `^${datePart}.*$`; + log.info("Search for events with regexpmatch:", dateRegex); + + const events = await searchByHeaderOnCDL({ + searchType: "regexpmatch", + fields: { + "cdl:DataRegistrationTimeStamp": dateRegex, + }, + }); + + expect(events).toBeTruthy(); + expect(events.length).toBeGreaterThan(0); + for (const e of events) { + expect(e["cdl:Lineage"]["cdl:DataRegistrationTimeStamp"]).toMatch( + datePart, + ); + } + }); + + test("Search global data using exact match", async () => { + log.info( + "Search for events with exact customEventProp:", + customEventPropValue, + ); + const events = await searchByGlobalDataOnCDL({ + searchType: "exactmatch", + fields: { + customEventProp: customEventPropValue, + }, + }); + + expect(events).toBeTruthy(); + expect(events.length).toEqual(1); + for (const e of events) { + expect(e["cdl:Event"]["customEventProp"]).toEqual(customEventPropValue); + } + }); + }); +}); diff --git a/packages/cactus-plugin-ledger-connector-cdl-socketio/tsconfig.json b/packages/cactus-plugin-ledger-connector-cdl-socketio/tsconfig.json new file mode 100644 index 0000000000..ef6d560e46 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl-socketio/tsconfig.json @@ -0,0 +1,28 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist/lib", + "declarationDir": "./dist/lib", + "rootDir": "./src", + "sourceMap": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "tsBuildInfoFile": "../../.build-cache/cactus-plugin-ledger-connector-cdl-socketio.tsbuildinfo" + }, + "include": [ + "./src/main/typescript/common/core/*.ts", + "./src/main/typescript/common/core/bin/*.ts", + "./src/main/typescript/common/core/config/*.ts", + "./src/main/typescript/connector/*.ts", + "./src/main/typescript/*.ts" + ], + "references": [ + { + "path": "../cactus-common/tsconfig.json" + }, + { + "path": "../cactus-api-client/tsconfig.json" + } + ] +} diff --git a/tsconfig.json b/tsconfig.json index c329c56c0b..8e7f7078ce 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -64,6 +64,9 @@ { "path": "./packages/cactus-plugin-ledger-connector-go-ethereum-socketio/tsconfig.json" }, + { + "path": "./packages/cactus-plugin-ledger-connector-cdl-socketio/tsconfig.json" + }, { "path": "./packages/cactus-plugin-ledger-connector-iroha/tsconfig.json" }, diff --git a/yarn.lock b/yarn.lock index 63986d1654..8ad127e2c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5842,10 +5842,20 @@ resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== -"@stencil/core@^2.18.0": - version "2.22.3" - resolved "https://registry.yarnpkg.com/@stencil/core/-/core-2.22.3.tgz#83987e20bba855c450f6d6780e3a20192603f13f" - integrity "sha1-g5h+ILuoVcRQ9tZ4DjogGSYD8T8= sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng==" +"@socket.io/component-emitter@~3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" + integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== + +"@stencil/core@^2.14.2": + version "2.15.2" + resolved "https://registry.yarnpkg.com/@stencil/core/-/core-2.15.2.tgz#73a18050714a9edc488e6a2ea3380f5a91a04690" + integrity sha512-D6dv2KAXlWt9mjC28q0s6anghQgXRn0k93suOf+4pqsv1Uq19zNJXpYL68N5GxMSiNZyMPTU4Tt2NCbut7DVGg== + +"@stencil/core@~2.12.0": + version "2.12.1" + resolved "https://registry.yarnpkg.com/@stencil/core/-/core-2.12.1.tgz#92364d3e57337b8d36dd9b70cfbed2833929f22d" + integrity sha512-u24TZ+FEvjnZt5ZgIkLjLpUNsO6Ml3mUZqwmqk81w6RWWz75hgB5p4RFI5rvuErFeh2xvMIGo+pNdG24XUBz1A== "@szmarczak/http-timer@^1.1.2": version "1.1.2" @@ -8631,6 +8641,14 @@ axios@0.24.0: dependencies: follow-redirects "^1.14.4" +axios@0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== + dependencies: + follow-redirects "^1.14.9" + form-data "^4.0.0" + axios@1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.4.tgz#6555dd955d2efa9b8f4cb4cb0b3371b7b243537a" @@ -9354,39 +9372,39 @@ body-parser@1.20.1: type-is "~1.6.18" unpipe "1.0.0" -body-parser@^1.16.0, body-parser@^1.19.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" - integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== +body-parser@1.20.2, body-parser@^1.20.0: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== dependencies: bytes "3.1.2" - content-type "~1.0.4" + content-type "~1.0.5" debug "2.6.9" depd "2.0.0" destroy "1.2.0" http-errors "2.0.0" iconv-lite "0.4.24" on-finished "2.4.1" - qs "6.10.3" - raw-body "2.5.1" + qs "6.11.0" + raw-body "2.5.2" type-is "~1.6.18" unpipe "1.0.0" -body-parser@^1.20.0: - version "1.20.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" - integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== +body-parser@^1.16.0, body-parser@^1.19.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" + integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== dependencies: bytes "3.1.2" - content-type "~1.0.5" + content-type "~1.0.4" debug "2.6.9" depd "2.0.0" destroy "1.2.0" http-errors "2.0.0" iconv-lite "0.4.24" on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.2" + qs "6.10.3" + raw-body "2.5.1" type-is "~1.6.18" unpipe "1.0.0" @@ -12681,6 +12699,11 @@ engine.io-parser@~5.0.3: dependencies: "@socket.io/base64-arraybuffer" "~1.0.2" +engine.io-parser@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.1.0.tgz#d593d6372d7f79212df48f807b8cace1ea1cb1b8" + integrity sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w== + engine.io@~6.1.0: version "6.1.3" resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.1.3.tgz#f156293d011d99a3df5691ac29d63737c3302e6f" @@ -12713,18 +12736,26 @@ engine.io@~6.2.1: engine.io-parser "~5.0.3" ws "~8.2.3" -enhanced-resolve@^5.0.0: - version "5.9.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.1.tgz#e898cea44d9199fd92137496cff5691b910fb43e" - integrity sha512-jdyZMwCQ5Oj4c5+BTnkxPgDZO/BJzh/ADDmKebayyzNwjVX1AFCeGkOfxNx0mHi2+8BKC5VxUYiw3TIvoT7vhw== +engine.io@~6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.5.0.tgz#02b9d9941c0d3ab2d46628e98ac3331dd533dff0" + integrity sha512-UlfoK1iD62Hkedw2TmuHdhDsZCGaAyp+LZ/AvnImjYBeWagA3qIEETum90d6shMeFZiDuGT66zVCdx1wKYKGGg== dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" + "@types/cookie" "^0.4.1" + "@types/cors" "^2.8.12" + "@types/node" ">=10.0.0" + accepts "~1.3.4" + base64id "2.0.0" + cookie "~0.4.1" + cors "~2.8.5" + debug "~4.3.1" + engine.io-parser "~5.1.0" + ws "~8.11.0" -enhanced-resolve@^5.10.0: - version "5.15.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" - integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== +enhanced-resolve@^5.0.0, enhanced-resolve@^5.10.0, enhanced-resolve@^5.9.2, enhanced-resolve@^5.9.3: + version "5.12.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634" + integrity sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -14040,7 +14071,7 @@ express@4.17.3, express@^4.14.0, express@^4.17.1: utils-merge "1.0.1" vary "~1.1.2" -express@^4.17.3, express@^4.18.1: +express@4.18.2, express@^4.17.3, express@^4.18.1: version "4.18.2" resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== @@ -14782,7 +14813,7 @@ follow-redirects@^1.0.0, follow-redirects@^1.12.1, follow-redirects@^1.14.0, fol resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== -follow-redirects@^1.10.0, follow-redirects@^1.15.0: +follow-redirects@^1.10.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0: version "1.15.2" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== @@ -26651,10 +26682,17 @@ socket.io-adapter@~2.4.0: resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz#b50a4a9ecdd00c34d4c8c808224daa1a786152a6" integrity "sha1-tQpKns3QDDTUyMgIIk2qGnhhUqY= sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" -socket.io-client@4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.5.4.tgz#d3cde8a06a6250041ba7390f08d2468ccebc5ac9" - integrity sha512-ZpKteoA06RzkD32IbqILZ+Cnst4xewU7ZYK12aS1mzHftFFjpoMz69IuhP/nL25pJfao/amoPI527KnuhFm01g== +socket.io-adapter@~2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12" + integrity sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA== + dependencies: + ws "~8.11.0" + +socket.io-client@4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.1.3.tgz#236daa642a9f229932e00b7221e843bf74232a62" + integrity sha512-hISFn6PDpgDifVUiNklLHVPTMv1LAk8poHArfIUdXa+gKgbr0MZbAlquDFqCqsF30yBqa+jg42wgos2FK50BHA== dependencies: "@socket.io/component-emitter" "~3.1.0" debug "~4.3.2" @@ -26704,19 +26742,15 @@ socket.io-parser@~4.2.1: "@socket.io/component-emitter" "~3.1.0" debug "~4.3.1" -socket.io@4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.5.4.tgz#a4513f06e87451c17013b8d13fdfaf8da5a86a90" - integrity sha512-m3GC94iK9MfIEeIBfbhJs5BqFibMtkRk8ZpKwG2QwxV0m/eEhPIV4ara6XCF1LWNAus7z58RodiZlAH71U3EhQ== +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== dependencies: - accepts "~1.3.4" - base64id "~2.0.0" - debug "~4.3.2" - engine.io "~6.2.1" - socket.io-adapter "~2.4.0" - socket.io-parser "~4.2.1" + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" -socket.io@^4.2.0: +socket.io@4.4.1, socket.io@^4.2.0: version "4.4.1" resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.4.1.tgz#cd6de29e277a161d176832bb24f64ee045c56ab8" integrity sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg== @@ -26728,6 +26762,31 @@ socket.io@^4.2.0: socket.io-adapter "~2.3.3" socket.io-parser "~4.0.4" +socket.io@4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.7.0.tgz#ae21460d5aef23b152d38de64d7c1798cd2d23fc" + integrity sha512-eOpu7oCNiPGBHn9Falg0cAGivp6TpDI3Yt596fbsf+vln8kRLFWxXKrecFrybn/xNYVn9HcdJNAkYToCmTjsyg== + dependencies: + accepts "~1.3.4" + base64id "~2.0.0" + cors "~2.8.5" + debug "~4.3.2" + engine.io "~6.5.0" + socket.io-adapter "~2.5.2" + socket.io-parser "~4.2.4" + +socket.io@^4.4.1: + version "4.5.0" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.5.0.tgz#78ae2e84784c29267086a416620c18ef95b37186" + integrity "sha1-eK4uhHhMKSZwhqQWYgwY75WzcYY= sha512-slTYqU2jCgMjXwresG8grhUi/cC6GjzmcfqArzaH3BN/9I/42eZk9yamNvZJdBfTubkjEdKAKs12NEztId+bUA==" + dependencies: + accepts "~1.3.4" + base64id "~2.0.0" + debug "~4.3.2" + engine.io "~6.2.0" + socket.io-adapter "~2.4.0" + socket.io-parser "~4.0.4" + socketio-wildcard@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/socketio-wildcard/-/socketio-wildcard-2.0.0.tgz#2466e832276b19163563bee772388747f912475b" @@ -31529,6 +31588,11 @@ ws@^8.4.2: resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== +ws@~8.11.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" + integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== + xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"