diff --git a/Assets/Samples/Unity Render Streaming/3.1.0-exp.9/Example/RenderStreamingSample.asset b/Assets/Samples/Unity Render Streaming/3.1.0-exp.9/Example/RenderStreamingSample.asset index de16384..9ef6abc 100644 --- a/Assets/Samples/Unity Render Streaming/3.1.0-exp.9/Example/RenderStreamingSample.asset +++ b/Assets/Samples/Unity Render Streaming/3.1.0-exp.9/Example/RenderStreamingSample.asset @@ -21,7 +21,7 @@ MonoBehaviour: - rid: 9043491472605708289 type: {class: WebSocketSignalingSettings, ns: Unity.RenderStreaming, asm: Unity.RenderStreaming} data: - m_url: wss://192.168.31.67:8080 + m_url: wss://127.0.0.1:8080 m_iceServers: - m_urls: - stun:stun.l.google.com:19302 diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 8e4d040..5b4a35d 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -1,28 +1,28 @@ { "dependencies": { "com.unity.burst": { - "version": "1.8.19", + "version": "1.8.21", "depth": 1, "source": "registry", "dependencies": { "com.unity.mathematics": "1.2.1", "com.unity.modules.jsonserialize": "1.0.0" }, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.editorcoroutines": { "version": "1.0.0", "depth": 2, "source": "registry", "dependencies": {}, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.ext.nunit": { "version": "1.0.6", "depth": 1, "source": "registry", "dependencies": {}, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.ide.rider": { "version": "3.0.39", @@ -31,23 +31,23 @@ "dependencies": { "com.unity.ext.nunit": "1.0.6" }, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.inputsystem": { - "version": "1.11.2", + "version": "1.14.0", "depth": 1, "source": "registry", "dependencies": { "com.unity.modules.uielements": "1.0.0" }, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.mathematics": { "version": "1.2.6", "depth": 1, "source": "registry", "dependencies": {}, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.render-pipelines.core": { "version": "14.0.12", @@ -90,14 +90,14 @@ "com.unity.inputsystem": "1.5.1", "com.unity.modules.screencapture": "1.0.0" }, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.searcher": { "version": "4.9.2", "depth": 2, "source": "registry", "dependencies": {}, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.shadergraph": { "version": "14.0.12", @@ -126,7 +126,7 @@ "com.unity.editorcoroutines": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0" }, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.modules.ai": { "version": "1.0.0", diff --git a/ProjectSettings/PackageManagerSettings.asset b/ProjectSettings/PackageManagerSettings.asset index 8e2751a..b4c4834 100644 --- a/ProjectSettings/PackageManagerSettings.asset +++ b/ProjectSettings/PackageManagerSettings.asset @@ -21,7 +21,7 @@ MonoBehaviour: m_Registries: - m_Id: main m_Name: - m_Url: https://packages.unity.cn + m_Url: https://packages.unity.com m_Scopes: [] m_IsDefault: 1 m_Capabilities: 7 diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index 56af71e..587f809 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2022.3.60f1c1 -m_EditorVersionWithRevision: 2022.3.60f1c1 (469b28c2de88) +m_EditorVersion: 2022.3.62f3 +m_EditorVersionWithRevision: 2022.3.62f3 (96770f904ca7) diff --git a/WebApp/package-lock.json b/WebApp/package-lock.json index dca2e83..a6747e4 100644 --- a/WebApp/package-lock.json +++ b/WebApp/package-lock.json @@ -15,6 +15,8 @@ "debug": "~4.3.4", "express": "~4.18.1", "morgan": "^1.10.0", + "swagger-jsdoc": "^6.2.1", + "swagger-ui-express": "^4.5.0", "uuid": "^9.0.0", "ws": "^8.8.1" }, @@ -25,6 +27,8 @@ "@jest-mock/express": "^2.0.1", "@types/jest": "^29.0.2", "@types/morgan": "^1.9.3", + "@types/swagger-jsdoc": "^6.0.1", + "@types/swagger-ui-express": "^4.1.3", "@typescript-eslint/eslint-plugin": "^5.36.2", "@typescript-eslint/parser": "^5.36.2", "eslint": "^8.23.0", @@ -52,6 +56,46 @@ "node": ">=6.0.0" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, "node_modules/@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -1566,6 +1610,11 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1651,6 +1700,12 @@ "node": "*" } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true + }, "node_modules/@sinclair/typebox": { "version": "0.24.35", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.35.tgz", @@ -1824,8 +1879,7 @@ "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, "node_modules/@types/mime": { "version": "1.3.2", @@ -1877,6 +1931,22 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/swagger-jsdoc": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz", + "integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==", + "dev": true + }, + "node_modules/@types/swagger-ui-express": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", + "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, "node_modules/@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -2244,8 +2314,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-flatten": { "version": "1.1.1", @@ -2357,8 +2426,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -2482,7 +2550,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2609,6 +2676,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2821,8 +2893,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/console-control-strings": { "version": "1.1.0", @@ -3101,7 +3172,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -3497,7 +3567,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3904,8 +3973,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.2", @@ -4407,7 +4475,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -5744,7 +5811,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -5903,6 +5969,18 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead." + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead." + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -5915,6 +5993,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -6085,7 +6168,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -6416,7 +6498,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -6436,6 +6517,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "peer": true + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -6558,7 +6645,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -7802,6 +7888,86 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "dependencies": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/swagger-jsdoc/node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/swagger-jsdoc/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.32.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.0.tgz", + "integrity": "sha512-nKZB0OuDvacB0s/lC2gbge+RigYvGRGpLLMWMFxaTUwfM+CfndVk9Th2IaTinqXiz6Mn26GK2zriCpv6/+5m3Q==", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.3.tgz", + "integrity": "sha512-CDje4PndhTD2HkgyKH3pab+LKspDeB/NhPN2OF1j+piYIamQqBYwAXWESOT1Yju2xFg51bRW9sUng2WxDjzArw==", + "dependencies": { + "swagger-ui-dist": ">=4.11.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, "node_modules/tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -8219,6 +8385,14 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -8331,8 +8505,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "node_modules/write-file-atomic": { "version": "4.0.1", @@ -8391,6 +8564,14 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "engines": { + "node": ">= 6" + } + }, "node_modules/yargs": { "version": "17.5.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", @@ -8438,6 +8619,34 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, + "node_modules/z-schema/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } } }, "dependencies": { @@ -8451,6 +8660,40 @@ "@jridgewell/trace-mapping": "^0.3.9" } }, + "@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "requires": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==" + }, + "@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==" + }, + "@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "requires": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + } + }, "@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -9633,6 +9876,11 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -9699,6 +9947,11 @@ "safe-buffer": "^5.0.1" } }, + "@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==" + }, "@sinclair/typebox": { "version": "0.24.35", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.35.tgz", @@ -9872,8 +10125,7 @@ "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, "@types/mime": { "version": "1.3.2", @@ -9925,6 +10177,22 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/swagger-jsdoc": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz", + "integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==", + "dev": true + }, + "@types/swagger-ui-express": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", + "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, "@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -10160,8 +10428,7 @@ "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "array-flatten": { "version": "1.1.1", @@ -10255,8 +10522,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base64-js": { "version": "1.5.1", @@ -10357,7 +10623,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -10442,6 +10707,11 @@ "get-intrinsic": "^1.0.2" } }, + "call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -10599,8 +10869,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "console-control-strings": { "version": "1.1.0", @@ -10804,7 +11073,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, "requires": { "esutils": "^2.0.2" } @@ -11086,8 +11354,7 @@ "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, "etag": { "version": "1.8.1", @@ -11414,8 +11681,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "fsevents": { "version": "2.3.2", @@ -11768,7 +12034,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -12806,7 +13071,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "requires": { "argparse": "^2.0.1" } @@ -12930,6 +13194,16 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -12942,6 +13216,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -13069,7 +13348,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -13321,7 +13599,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } @@ -13335,6 +13612,12 @@ "mimic-fn": "^2.1.0" } }, + "openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "peer": true + }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -13420,8 +13703,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, "path-key": { "version": "3.1.1", @@ -14373,6 +14655,63 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, + "swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "requires": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "dependencies": { + "commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "requires": { + "@apidevtools/swagger-parser": "10.0.3" + } + }, + "swagger-ui-dist": { + "version": "5.32.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.0.tgz", + "integrity": "sha512-nKZB0OuDvacB0s/lC2gbge+RigYvGRGpLLMWMFxaTUwfM+CfndVk9Th2IaTinqXiz6Mn26GK2zriCpv6/+5m3Q==", + "requires": { + "@scarf/scarf": "=1.4.0" + } + }, + "swagger-ui-express": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.3.tgz", + "integrity": "sha512-CDje4PndhTD2HkgyKH3pab+LKspDeB/NhPN2OF1j+piYIamQqBYwAXWESOT1Yju2xFg51bRW9sUng2WxDjzArw==", + "requires": { + "swagger-ui-dist": ">=4.11.0" + } + }, "tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -14664,6 +15003,11 @@ "convert-source-map": "^1.6.0" } }, + "validator": { + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -14757,8 +15101,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { "version": "4.0.1", @@ -14794,6 +15137,11 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==" + }, "yargs": { "version": "17.5.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", @@ -14826,6 +15174,25 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true + }, + "z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "requires": { + "commander": "^9.4.1", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "dependencies": { + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "optional": true + } + } } } } diff --git a/WebApp/package.json b/WebApp/package.json index c76941e..9be94db 100644 --- a/WebApp/package.json +++ b/WebApp/package.json @@ -19,6 +19,8 @@ "debug": "~4.3.4", "express": "~4.18.1", "morgan": "^1.10.0", + "swagger-jsdoc": "^6.2.1", + "swagger-ui-express": "^4.5.0", "uuid": "^9.0.0", "ws": "^8.8.1" }, @@ -26,6 +28,8 @@ "@jest-mock/express": "^2.0.1", "@types/jest": "^29.0.2", "@types/morgan": "^1.9.3", + "@types/swagger-jsdoc": "^6.0.1", + "@types/swagger-ui-express": "^4.1.3", "@typescript-eslint/eslint-plugin": "^5.36.2", "@typescript-eslint/parser": "^5.36.2", "eslint": "^8.23.0", diff --git a/WebApp/src/class/httphandler.ts b/WebApp/src/class/httphandler.ts index 4741913..04897ac 100644 --- a/WebApp/src/class/httphandler.ts +++ b/WebApp/src/class/httphandler.ts @@ -1,55 +1,115 @@ +/** + * HTTP处理器 + * 负责处理HTTP请求,管理会话和连接,处理信令消息 + */ import { Request, Response } from 'express'; import Offer from './offer'; import Answer from './answer'; import Candidate from './candidate'; import { v4 as uuid } from 'uuid'; +/** + * 断开连接记录类 + * 用于记录断开连接的信息 + */ class Disconnection { - id: string; - datetime: number; + id: string; // 连接ID + datetime: number; // 断开连接的时间戳 + + /** + * 构造函数 + * @param id 连接ID + * @param datetime 断开连接的时间戳 + */ constructor(id: string, datetime: number) { this.id = id; this.datetime = datetime; } } +// 会话超时时间(毫秒) const TimeoutRequestedTime = 10000; // 10sec +// 是否为私有模式 let isPrivate: boolean; -// [{sessonId:[connectionId,...]}] +/** + * 客户端会话映射 + * 键: 会话ID + * 值: 该会话的连接ID集合 + */ const clients: Map> = new Map>(); -// [{sessonId:Date}] +/** + * 会话最后请求时间映射 + * 键: 会话ID + * 值: 最后请求的时间戳 + */ const lastRequestedTime: Map = new Map(); -// [{connectionId:[sessionId1, sessionId2]}] +/** + * 连接对映射 + * 键: 连接ID + * 值: [会话ID1, 会话ID2] + */ const connectionPair: Map = new Map(); // key = connectionId -// [{sessionId:[{connectionId:Offer},...]}] +/** + * 会话的offer映射 + * 键: 会话ID + * 值: 该会话的连接ID到Offer对象的映射 + */ const offers: Map> = new Map>(); // key = sessionId -// [{sessionId:[{connectionId:Answer},...]}] +/** + * 会话的answer映射 + * 键: 会话ID + * 值: 该会话的连接ID到Answer对象的映射 + */ const answers: Map> = new Map>(); // key = sessionId -// [{sessionId:[{connectionId:Candidate},...]}] +/** + * 会话的candidate映射 + * 键: 会话ID + * 值: 该会话的连接ID到Candidate数组的映射 + */ const candidates: Map> = new Map>(); // key = sessionId -// [{sessionId:[Disconnection,...]}] +/** + * 会话的断开连接记录映射 + * 键: 会话ID + * 值: 该会话的Disconnection对象数组 + */ const disconnections: Map = new Map(); // key = sessionId +/** + * 获取或创建会话的连接ID集合 + * @param sessionId 会话ID + * @returns 连接ID的Set集合 + */ function getOrCreateConnectionIds(sessionId: string): Set { let connectionIds = null; + // 检查会话是否已存在 if (!clients.has(sessionId)) { + // 如果不存在,创建新的连接ID集合 connectionIds = new Set(); + // 将新的连接ID集合与会话关联 clients.set(sessionId, connectionIds); } + // 获取会话的连接ID集合 connectionIds = clients.get(sessionId); + // 返回连接ID集合 return connectionIds; } +/** + * 重置处理器状态 + * @param mode 通信模式(public或private) + */ function reset(mode: string): void { + // 设置是否为私有模式 isPrivate = mode == "private"; + // 清空所有映射 clients.clear(); connectionPair.clear(); offers.clear(); @@ -58,36 +118,59 @@ function reset(mode: string): void { disconnections.clear(); } +/** + * 检查会话ID是否有效 + * @param req Express请求对象 + * @param res Express响应对象 + * @param next 下一个中间件函数 + */ function checkSessionId(req: Request, res: Response, next): void { + // 如果是根路径,直接通过 if (req.url === '/') { next(); return; } + // 从请求头获取会话ID const id: string = req.header('session-id'); + // 检查会话是否存在 if (!clients.has(id)) { res.sendStatus(404); return; } + // 更新会话的最后请求时间 lastRequestedTime.set(id, Date.now()); + // 继续处理请求 next(); } +/** + * 删除连接 + * @param sessionId 会话ID + * @param connectionId 连接ID + * @param datetime 时间戳 + */ function _deleteConnection(sessionId:string, connectionId:string, datetime:number) { + // 从会话的连接ID集合中删除连接ID clients.get(sessionId).delete(connectionId); + // 处理私有模式 if(isPrivate) { if (connectionPair.has(connectionId)) { const pair = connectionPair.get(connectionId); + // 找到另一个会话ID const otherSessionId = pair[0] == sessionId ? pair[1] : pair[0]; if (otherSessionId) { if (clients.has(otherSessionId)) { + // 从另一个会话的连接ID集合中删除连接ID clients.get(otherSessionId).delete(connectionId); + // 向另一个会话的断开连接记录中添加记录 const array1 = disconnections.get(otherSessionId); array1.push(new Disconnection(connectionId, datetime)); } } } } else { + // 公共模式:向所有其他会话的断开连接记录中添加记录 disconnections.forEach((array, id) => { if (id == sessionId) return; @@ -95,21 +178,32 @@ function _deleteConnection(sessionId:string, connectionId:string, datetime:numbe }); } + // 从连接对映射中删除 connectionPair.delete(connectionId); + // 从会话的offer映射中删除 offers.get(sessionId).delete(connectionId); + // 从会话的answer映射中删除 answers.get(sessionId).delete(connectionId); + // 从会话的candidate映射中删除 candidates.get(sessionId).delete(connectionId); + // 向当前会话的断开连接记录中添加记录 const array2 = disconnections.get(sessionId); array2.push(new Disconnection(connectionId, datetime)); } +/** + * 删除会话 + * @param sessionId 会话ID + */ function _deleteSession(sessionId: string) { + // 如果会话存在,删除其所有连接 if(clients.has(sessionId)) { for(const connectionId of Array.from(clients.get(sessionId))) { _deleteConnection(sessionId, connectionId, Date.now()); } } + // 从所有映射中删除会话 offers.delete(sessionId); answers.delete(sessionId); candidates.delete(sessionId); @@ -117,86 +211,142 @@ function _deleteSession(sessionId: string) { disconnections.delete(sessionId); } +/** + * 检查超时会话 + */ function _checkForTimedOutSessions(): void { + // 遍历所有会话 for (const sessionId of Array.from(clients.keys())) { + // 如果会话没有最后请求时间,跳过 if(!lastRequestedTime.has(sessionId)) continue; + // 如果会话未超时,跳过 if(lastRequestedTime.get(sessionId) > Date.now() - TimeoutRequestedTime) continue; + // 删除超时会话 _deleteSession(sessionId); console.log(`deleted sessionId:${sessionId} by timeout.`); } } +/** + * 获取会话的连接ID列表 + * @param sessionId 会话ID + * @returns 连接ID数组 + */ function _getConnection(sessionId: string): string[] { + // 检查超时会话 _checkForTimedOutSessions(); + // 返回会话的连接ID集合的数组形式 return Array.from(clients.get(sessionId)); } +/** + * 获取会话的断开连接记录 + * @param sessionId 会话ID + * @param fromTime 起始时间戳 + * @returns 断开连接记录数组 + */ function _getDisconnection(sessionId: string, fromTime: number): Disconnection[] { + // 检查超时会话 _checkForTimedOutSessions(); let arrayDisconnections: Disconnection[] = []; + // 如果断开连接记录存在,获取该会话的断开连接记录 if (disconnections.size != 0 && disconnections.has(sessionId)) { arrayDisconnections = disconnections.get(sessionId); } + // 如果指定了起始时间,过滤出时间戳大于等于起始时间的记录 if (fromTime > 0) { arrayDisconnections = arrayDisconnections.filter((v) => v.datetime >= fromTime); } return arrayDisconnections; } +/** + * 获取会话的offer列表 + * @param sessionId 会话ID + * @param fromTime 起始时间戳 + * @returns [连接ID, Offer]数组 + */ function _getOffer(sessionId: string, fromTime: number): [string, Offer][] { let arrayOffers: [string, Offer][] = []; + // 如果offer映射不为空 if (offers.size != 0) { + // 处理私有模式 if (isPrivate) { + // 如果会话存在offer记录,获取该会话的offer列表 if (offers.has(sessionId)) { arrayOffers = Array.from(offers.get(sessionId)); } } else { + // 公共模式:获取所有其他会话的offer列表 const otherSessionMap = Array.from(offers).filter(x => x[0] != sessionId); arrayOffers = [].concat(...Array.from(otherSessionMap, x => Array.from(x[1], y => [y[0], y[1]]))); } } + // 如果指定了起始时间,过滤出时间戳大于等于起始时间的offer if (fromTime > 0) { arrayOffers = arrayOffers.filter((v) => v[1].datetime >= fromTime); } return arrayOffers; } +/** + * 获取会话的answer列表 + * @param sessionId 会话ID + * @param fromTime 起始时间戳 + * @returns [连接ID, Answer]数组 + */ function _getAnswer(sessionId: string, fromTime: number): [string, Answer][] { let arrayAnswers: [string, Answer][] = []; + // 如果answer映射不为空且会话存在answer记录,获取该会话的answer列表 if (answers.size != 0 && answers.has(sessionId)) { arrayAnswers = Array.from(answers.get(sessionId)); } + // 如果指定了起始时间,过滤出时间戳大于等于起始时间的answer if (fromTime > 0) { arrayAnswers = arrayAnswers.filter((v) => v[1].datetime >= fromTime); } return arrayAnswers; } +/** + * 获取会话的candidate列表 + * @param sessionId 会话ID + * @param fromTime 起始时间戳 + * @returns [连接ID, Candidate]数组 + */ function _getCandidate(sessionId: string, fromTime: number): [string, Candidate][] { + // 获取会话的连接ID列表 const connectionIds = Array.from(clients.get(sessionId)); const arr: [string, Candidate][] = []; + // 遍历每个连接ID for (const connectionId of connectionIds) { + // 获取连接对 const pair = connectionPair.get(connectionId); if (pair == null) { continue; } + // 找到另一个会话ID const otherSessionId = sessionId === pair[0] ? pair[1] : pair[0]; + // 如果另一个会话不存在candidate记录或该连接ID不存在candidate记录,跳过 if (!candidates.get(otherSessionId) || !candidates.get(otherSessionId).get(connectionId)) { continue; } + // 获取该连接ID的candidate列表,并过滤出时间戳大于等于起始时间的candidate const arrayCandidates = candidates.get(otherSessionId).get(connectionId) .filter((v) => v.datetime >= fromTime); + // 如果没有符合条件的candidate,跳过 if (arrayCandidates.length === 0) { continue; } + // 将符合条件的candidate添加到结果数组中 for (const candidate of arrayCandidates) { arr.push([connectionId, candidate]); } @@ -204,40 +354,269 @@ function _getCandidate(sessionId: string, fromTime: number): [string, Candidate] return arr; } +/** + * @swagger + * /signaling/answer: + * get: + * summary: 获取answer列表 + * description: 获取当前会话的answer信令消息列表 + * security: + * - sessionAuth: [] + * parameters: + * - in: query + * name: fromtime + * schema: + * type: number + * description: 起始时间戳,用于过滤消息 + * responses: + * 200: + * description: 成功获取answer列表 + * content: + * application/json: + * schema: + * type: object + * properties: + * answers: + * type: array + * items: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * sdp: + * type: string + * description: SDP描述 + * type: + * type: string + * description: 消息类型 + * datetime: + * type: number + * description: 时间戳 + */ function getAnswer(req: Request, res: Response): void { - // get `fromtime` parameter from request query + // 从请求查询参数中获取`fromtime`参数 const fromTime: number = req.query.fromtime ? Number(req.query.fromtime) : 0; + // 从请求头获取会话ID const sessionId: string = req.header('session-id'); + // 获取会话的answer列表 const answers: [string, Answer][] = _getAnswer(sessionId, fromTime); + // 返回JSON响应,包含answer列表 res.json({ answers: answers.map((v) => ({ connectionId: v[0], sdp: v[1].sdp, type: "answer", datetime: v[1].datetime })) }); } +/** + * @swagger + * /signaling/connection: + * get: + * summary: 获取连接列表 + * description: 获取当前会话的连接列表 + * security: + * - sessionAuth: [] + * responses: + * 200: + * description: 成功获取连接列表 + * content: + * application/json: + * schema: + * type: object + * properties: + * connections: + * type: array + * items: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * type: + * type: string + * description: 消息类型 + * datetime: + * type: number + * description: 时间戳 + */ function getConnection(req: Request, res: Response): void { - // get `fromtime` parameter from request query + // 从请求头获取会话ID const sessionId: string = req.header('session-id'); + // 获取会话的连接ID列表 const connections = _getConnection(sessionId); + // 返回JSON响应,包含连接列表 res.json({ connections: connections.map((v) => ({ connectionId: v, type: "connect", datetime: Date.now() })) }); } +/** + * @swagger + * /signaling/offer: + * get: + * summary: 获取offer列表 + * description: 获取当前会话的offer信令消息列表 + * security: + * - sessionAuth: [] + * parameters: + * - in: query + * name: fromtime + * schema: + * type: number + * description: 起始时间戳,用于过滤消息 + * responses: + * 200: + * description: 成功获取offer列表 + * content: + * application/json: + * schema: + * type: object + * properties: + * offers: + * type: array + * items: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * sdp: + * type: string + * description: SDP描述 + * polite: + * type: boolean + * description: 是否为polite模式 + * type: + * type: string + * description: 消息类型 + * datetime: + * type: number + * description: 时间戳 + */ function getOffer(req: Request, res: Response): void { - // get `fromtime` parameter from request query + // 从请求查询参数中获取`fromtime`参数 const fromTime: number = req.query.fromtime ? Number(req.query.fromtime) : 0; + // 从请求头获取会话ID const sessionId: string = req.header('session-id'); + // 获取会话的offer列表 const offers = _getOffer(sessionId, fromTime); + // 返回JSON响应,包含offer列表 res.json({ offers: offers.map((v) => ({ connectionId: v[0], sdp: v[1].sdp, polite: v[1].polite, type: "offer", datetime: v[1].datetime })) }); } +/** + * @swagger + * /signaling/candidate: + * get: + * summary: 获取candidate列表 + * description: 获取当前会话的candidate信令消息列表 + * security: + * - sessionAuth: [] + * parameters: + * - in: query + * name: fromtime + * schema: + * type: number + * description: 起始时间戳,用于过滤消息 + * responses: + * 200: + * description: 成功获取candidate列表 + * content: + * application/json: + * schema: + * type: object + * properties: + * candidates: + * type: array + * items: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * candidate: + * type: string + * description: ICE候选者信息 + * sdpMLineIndex: + * type: number + * description: SDP媒体行索引 + * sdpMid: + * type: string + * description: SDP媒体ID + * type: + * type: string + * description: 消息类型 + * datetime: + * type: number + * description: 时间戳 + */ function getCandidate(req: Request, res: Response): void { - // get `fromtime` parameter from request query + // 从请求查询参数中获取`fromtime`参数 const fromTime: number = req.query.fromtime ? Number(req.query.fromtime) : 0; + // 从请求头获取会话ID const sessionId: string = req.header('session-id'); + // 获取会话的candidate列表 const candidates = _getCandidate(sessionId, fromTime); + // 返回JSON响应,包含candidate列表 res.json({ candidates: candidates.map((v) => ({ connectionId: v[0], candidate: v[1].candidate, sdpMLineIndex: v[1].sdpMLineIndex, sdpMid: v[1].sdpMid, type: "candidate", datetime: v[1].datetime })) }); } +/** + * @swagger + * /signaling: + * get: + * summary: 获取所有信令消息 + * description: 获取当前会话的所有信令消息,包括连接、断开连接、offer、answer和candidate + * security: + * - sessionAuth: [] + * parameters: + * - in: query + * name: fromtime + * schema: + * type: number + * description: 起始时间戳,用于过滤消息 + * responses: + * 200: + * description: 成功获取所有信令消息 + * content: + * application/json: + * schema: + * type: object + * properties: + * messages: + * type: array + * items: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * type: + * type: string + * description: 消息类型 + * datetime: + * type: number + * description: 时间戳 + * sdp: + * type: string + * description: SDP描述(仅offer和answer消息) + * polite: + * type: boolean + * description: 是否为polite模式(仅offer消息) + * candidate: + * type: string + * description: ICE候选者信息(仅candidate消息) + * sdpMLineIndex: + * type: number + * description: SDP媒体行索引(仅candidate消息) + * sdpMid: + * type: string + * description: SDP媒体ID(仅candidate消息) + * datetime: + * type: number + * description: 当前时间戳 + */ function getAll(req: Request, res: Response): void { + // 从请求查询参数中获取`fromtime`参数 const fromTime: number = req.query.fromtime ? Number(req.query.fromtime) : 0; + // 从请求头获取会话ID const sessionId: string = req.header('session-id'); + // 获取各种信令消息 const connections = _getConnection(sessionId); const offers = _getOffer(sessionId, fromTime); const answers: [string, Answer][] = _getAnswer(sessionId, fromTime); @@ -247,80 +626,231 @@ function getAll(req: Request, res: Response): void { let array: any[] = []; + // 合并所有信令消息 array = array.concat(connections.map((v) => ({ connectionId: v, type: "connect", datetime: datetime }))); array = array.concat(offers.map((v) => ({ connectionId: v[0], sdp: v[1].sdp, polite: v[1].polite, type: "offer", datetime: v[1].datetime }))); array = array.concat(answers.map((v) => ({ connectionId: v[0], sdp: v[1].sdp, type: "answer", datetime: v[1].datetime }))); array = array.concat(candidates.map((v) => ({ connectionId: v[0], candidate: v[1].candidate, sdpMLineIndex: v[1].sdpMLineIndex, sdpMid: v[1].sdpMid, type: "candidate", datetime: v[1].datetime }))); array = array.concat(disconnections.map((v) => ({ connectionId: v.id, type: "disconnect", datetime: v.datetime }))); + // 按时间戳排序 array.sort((a, b) => a.datetime - b.datetime); + // 返回JSON响应,包含所有信令消息 res.json({ messages: array, datetime: datetime }); } +/** + * @swagger + * /signaling: + * put: + * summary: 创建会话 + * description: 创建一个新的会话,并返回会话ID + * responses: + * 200: + * description: 成功创建会话 + * content: + * application/json: + * schema: + * type: object + * properties: + * sessionId: + * type: string + * description: 新创建的会话ID + */ function createSession(sessionId: string, res: Response): void; function createSession(req: Request, res: Response): void; function createSession(req: string | Request, res: Response): void { + // 确定会话ID const sessionId: string = typeof req === "string" ? req : uuid(); + // 为会话创建各种映射 clients.set(sessionId, new Set()); offers.set(sessionId, new Map()); answers.set(sessionId, new Map()); candidates.set(sessionId, new Map()); disconnections.set(sessionId, []); + // 返回JSON响应,包含会话ID res.json({ sessionId: sessionId }); } +/** + * @swagger + * /signaling: + * delete: + * summary: 删除会话 + * description: 删除当前会话及其所有连接 + * security: + * - sessionAuth: [] + * responses: + * 200: + * description: 成功删除会话 + */ function deleteSession(req: Request, res: Response): void { + // 从请求头获取会话ID const id: string = req.header('session-id'); + // 删除会话 _deleteSession(id); + // 返回200状态码表示请求处理成功 res.sendStatus(200); } +/** + * @swagger + * /signaling/connection: + * put: + * summary: 创建连接 + * description: 创建一个新的连接,并返回连接信息 + * security: + * - sessionAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * responses: + * 200: + * description: 成功创建连接 + * content: + * application/json: + * schema: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * polite: + * type: boolean + * description: 是否为polite模式 + * type: + * type: string + * description: 消息类型 + * datetime: + * type: number + * description: 时间戳 + * 400: + * description: 请求参数错误 + */ function createConnection(req: Request, res: Response): void { + // 从请求头获取会话ID const sessionId: string = req.header('session-id'); + // 从请求体获取连接ID const { connectionId } = req.body; + // 获取会话的最后请求时间 const datetime = lastRequestedTime.get(sessionId); + // 检查连接ID是否存在 if (connectionId == null) { res.status(400).send({ error: new Error(`connectionId is required`) }); return; } let polite = true; + // 处理私有模式 if (isPrivate) { if (connectionPair.has(connectionId)) { const pair = connectionPair.get(connectionId); + // 检查连接ID是否已被使用 if (pair[0] != null && pair[1] != null) { const err = new Error(`${connectionId}: This connection id is already used.`); console.log(err); res.status(400).send({ error: err }); return; } else if (pair[0] != null) { + // 找到配对连接 connectionPair.set(connectionId, [pair[0], sessionId]); + // 添加连接ID到另一个会话 const map = getOrCreateConnectionIds(pair[0]); map.add(connectionId); } } else { + // 创建新的连接对 connectionPair.set(connectionId, [sessionId, null]); polite = false; } } + // 添加连接ID到当前会话 const connectionIds = getOrCreateConnectionIds(sessionId); connectionIds.add(connectionId); + // 返回JSON响应,包含连接信息 res.json({ connectionId: connectionId, polite: polite, type: "connect", datetime: datetime }); } +/** + * @swagger + * /signaling/connection: + * delete: + * summary: 删除连接 + * description: 删除指定的连接 + * security: + * - sessionAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * responses: + * 200: + * description: 成功删除连接 + * content: + * application/json: + * schema: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + */ function deleteConnection(req: Request, res: Response): void { + // 从请求头获取会话ID const sessionId: string = req.header('session-id'); + // 从请求体获取连接ID const { connectionId } = req.body; + // 获取会话的最后请求时间 const datetime = lastRequestedTime.get(sessionId); + // 删除连接 _deleteConnection(sessionId, connectionId, datetime); + // 返回JSON响应,包含连接ID res.json({ connectionId: connectionId }); } +/** + * @swagger + * /signaling/offer: + * post: + * summary: 发送offer信令 + * description: 发送offer信令消息 + * security: + * - sessionAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * sdp: + * type: string + * description: SDP描述 + * responses: + * 200: + * description: 成功发送offer信令 + */ function postOffer(req: Request, res: Response): void { const sessionId: string = req.header('session-id'); const { connectionId } = req.body; @@ -354,6 +884,31 @@ function postOffer(req: Request, res: Response): void { res.sendStatus(200); } +/** + * @swagger + * /signaling/answer: + * post: + * summary: 发送answer信令 + * description: 发送answer信令消息 + * security: + * - sessionAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * sdp: + * type: string + * description: SDP描述 + * responses: + * 200: + * description: 成功发送answer信令 + */ function postAnswer(req: Request, res: Response): void { const sessionId: string = req.header('session-id'); const { connectionId } = req.body; @@ -395,6 +950,37 @@ function postAnswer(req: Request, res: Response): void { res.sendStatus(200); } +/** + * @swagger + * /signaling/candidate: + * post: + * summary: 发送candidate信令 + * description: 发送candidate信令消息 + * security: + * - sessionAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * connectionId: + * type: string + * description: 连接ID + * candidate: + * type: string + * description: ICE候选者信息 + * sdpMLineIndex: + * type: number + * description: SDP媒体行索引 + * sdpMid: + * type: string + * description: SDP媒体ID + * responses: + * 200: + * description: 成功发送candidate信令 + */ function postCandidate(req: Request, res: Response): void { const sessionId: string = req.header('session-id'); const { connectionId } = req.body; @@ -409,5 +995,101 @@ function postCandidate(req: Request, res: Response): void { arr.push(candidate); res.sendStatus(200); } +/** + * @swagger + * /signaling/rooms: + * get: + * summary: 获取房间和用户信息 + * description: 获取所有房间的信息,包括房间ID和链接的用户 + * security: + * - sessionAuth: [] + * responses: + * 200: + * description: 成功获取房间和用户信息 + * content: + * application/json: + * schema: + * type: object + * properties: + * rooms: + * type: array + * items: + * type: object + * properties: + * roomId: + * type: string + * description: 房间ID + * users: + * type: array + * items: + * type: object + * properties: + * sessionId: + * type: string + * description: 会话ID + * connected: + * type: boolean + * description: 连接状态 + * userCount: + * type: number + * description: 用户数量 + * totalRooms: + * type: number + * description: 总房间数 + */ +function onGetConnections(req: Request, res: Response): void { -export { reset, checkSessionId, getAll, getConnection, getOffer, getAnswer, getCandidate, createSession, deleteSession, createConnection, deleteConnection, postOffer, postAnswer, postCandidate }; + // 收集所有房间ID和链接用户信息 + const rooms = []; + + // 遍历所有连接对 + for (const [connectionId, pair] of Array.from(connectionPair.entries())) { + // 收集房间中的用户信息 + const users = []; + + // 添加第一个用户 + if (pair[0] && clients.has(pair[0])) { + users.push({ + sessionId: pair[0], + connected: true + }); + } + + // 添加第二个用户 + if (pair[1] && clients.has(pair[1])) { + users.push({ + sessionId: pair[1], + connected: true + }); + } + + // 添加房间信息 + rooms.push({ + roomId: connectionId, + users: users, + userCount: users.length + }); + } + + res.json({ rooms: rooms, totalRooms: rooms.length }); +} +/** + * 导出HTTP处理器函数 + */ +export { + reset, // 重置处理器状态 + checkSessionId, // 检查会话ID是否有效 + getAll, // 获取所有信令消息 + getConnection, // 获取连接列表 + getOffer, // 获取offer列表 + getAnswer, // 获取answer列表 + getCandidate, // 获取candidate列表 + createSession, // 创建会话 + deleteSession, // 删除会话 + createConnection, // 创建连接 + deleteConnection, // 删除连接 + postOffer, // 处理offer信令消息 + postAnswer, // 处理answer信令消息 + postCandidate, // 处理candidate信令消息 + onGetConnections // 获取房间和用户信息 +}; diff --git a/WebApp/src/class/websockethandler.ts b/WebApp/src/class/websockethandler.ts index 455e1ea..8d5bdb5 100644 --- a/WebApp/src/class/websockethandler.ts +++ b/WebApp/src/class/websockethandler.ts @@ -127,6 +127,8 @@ function onConnect(ws: WebSocket, connectionId: string): void { connectionIds.add(connectionId); // 发送连接成功消息 ws.send(JSON.stringify({ type: "connect", connectionId: connectionId, polite: polite })); + //启用心跳包 + //AddHeartbeat(ws, connectionId); } /** @@ -154,6 +156,9 @@ function onDisconnect(ws: WebSocket, connectionId: string): void { connectionPair.delete(connectionId); // 向当前连接发送断开连接消息 ws.send(JSON.stringify({ type: "disconnect", connectionId: connectionId })); + //RemoveHeartbeat(ws); + // 记录断开连接的日志 + console.log(`Disconnect connectionId: ${connectionId}`); } /** @@ -262,30 +267,7 @@ function onCandidate(ws: WebSocket, message: any): void { }); } -/** - * 处理获取连接信息请求 - * @param ws WebSocket连接实例 - */ -function onGetConnections(ws: WebSocket): void { - // 收集所有connectionId - const allConnectionIds = Array.from(connectionPair.keys()); - // 收集所有WebSocket连接信息 - const allWebSockets = Array.from(clients.entries()).map(([ws, connectionIds]) => { - return { - connectionIds: Array.from(connectionIds), - // 注意:这里不能直接序列化WebSocket对象,只能返回连接数量或其他信息 - connected: true - }; - }); - - // 发送连接信息给请求的客户端 - ws.send(JSON.stringify({ - type: "connections", - connectionIds: allConnectionIds, - websocketCount: clients.size - })); -} /** * 处理广播消息请求 @@ -332,7 +314,7 @@ function onBroadcast(ws: WebSocket, message: any): void { }); } } -function AddHeartbeat(ws: WebSocket){ +function AddHeartbeat(ws: WebSocket, connectionId: string){ // 初始化心跳检测 (ws as any).lastActivity = Date.now(); @@ -343,7 +325,8 @@ function AddHeartbeat(ws: WebSocket){ if (now - (ws as any).lastActivity > 10000) { console.log('WebSocket connection timeout, closing...'); clearInterval((ws as any).heartbeatTimer); - ws.close(); + //ws.close(); + onDisconnect(ws, connectionId); } else { // 发送ping消息 ws.send(JSON.stringify({ type: "ping" })); @@ -360,4 +343,4 @@ function RemoveHeartbeat(ws: WebSocket){ /** * 导出WebSocket处理器函数 */ -export { reset, add, remove, onConnect, onDisconnect, onOffer, onAnswer, onCandidate, onGetConnections, onBroadcast, AddHeartbeat, RemoveHeartbeat }; +export { reset, add, remove, onConnect, onDisconnect, onOffer, onAnswer, onCandidate, onBroadcast, AddHeartbeat, RemoveHeartbeat }; diff --git a/WebApp/src/server.ts b/WebApp/src/server.ts index 224433a..d65522b 100644 --- a/WebApp/src/server.ts +++ b/WebApp/src/server.ts @@ -6,11 +6,12 @@ import signaling from './signaling'; import { log, LogLevel } from './log'; import Options from './class/options'; import { reset as resetHandler }from './class/httphandler'; +import { initSwagger } from './swagger'; const cors = require('cors'); -export const createServer = (config: Options): express.Application => { - const app: express.Application = express(); +export const createServer = (config: Options): express.Express => { + const app: express.Express = express(); resetHandler(config.mode); // logging http access if (config.logging != "none") { @@ -35,5 +36,8 @@ export const createServer = (config: Options): express.Application => { } }); }); + // 初始化Swagger + initSwagger(app, config); + return app; }; diff --git a/WebApp/src/swagger.ts b/WebApp/src/swagger.ts new file mode 100644 index 0000000..0f2e16e --- /dev/null +++ b/WebApp/src/swagger.ts @@ -0,0 +1,64 @@ +/** + * Swagger配置文件 + * 用于设置API文档的基本信息和路由 + */ +import * as swaggerJSDoc from 'swagger-jsdoc'; +import * as swaggerUi from 'swagger-ui-express'; +import { Express } from 'express'; +import Options from './class/options'; + +/** + * 初始化Swagger + * @param app Express应用实例 + * @param config 配置选项 + */ +export const initSwagger = (app: Express, config: Options): void => { + // 根据配置生成服务器URL + const protocol = config.secure ? 'https' : 'http'; + const port = config.port || 8080; + const serverUrl = `${protocol}://localhost:${port}`; + + /** + * Swagger配置选项 + */ + const swaggerOptions = { + definition: { + openapi: '3.0.0', + info: { + title: 'WebRTC Signaling API', + version: '1.0.0', + description: 'WebRTC信令服务器API文档', + contact: { + name: 'WebRTC Team', + email: 'contact@webrtc.example.com' + } + }, + servers: [ + { + url: serverUrl, + description: '本地开发服务器' + } + ], + components: { + securitySchemes: { + sessionAuth: { + type: 'apiKey', + in: 'header', + name: 'session-id', + description: '会话ID' + } + } + }, + security: [ + { + sessionAuth: [] + } + ] + }, + apis: ['./src/class/httphandler.ts', './src/signaling.ts'] + }; + + const swaggerSpec = swaggerJSDoc(swaggerOptions); + app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); + console.log(`Swagger文档已初始化,访问 ${serverUrl}/api-docs 查看`); +}; diff --git a/WebApp/src/websocket.ts b/WebApp/src/websocket.ts index d208c3b..e66d823 100644 --- a/WebApp/src/websocket.ts +++ b/WebApp/src/websocket.ts @@ -26,14 +26,14 @@ export default class WSSignaling { this.wss.on('connection', (ws: WebSocket) => { // 添加新的WebSocket连接到处理器 handler.add(ws); - handler.AddHeartbeat(ws); + //handler.AddHeartbeat(ws); /** * 监听连接关闭事件 */ ws.onclose = (): void => { // 从处理器中移除关闭的连接 handler.remove(ws); - handler.RemoveHeartbeat(ws); + //handler.RemoveHeartbeat(ws); }; /** @@ -104,9 +104,6 @@ export default class WSSignaling { case "broadcast": handler.onBroadcast(ws, msg.data); break; - case "onGetConnections": - handler.onGetConnections(ws); - break; default: // 忽略未知消息类型 break;