From 13aff451a84b0ae2c2aefcc5b9d224cae3fec635 Mon Sep 17 00:00:00 2001 From: Valerie Date: Wed, 24 Aug 2022 22:02:29 -0400 Subject: [PATCH] pasting attachments --- .gitignore | 2 + docker-compose.yml | 19 +- package-lock.json | 1072 ++++++----------- package.json | 9 + packages/renderer/index.html | 2 +- packages/renderer/src/App.tsx | 29 + .../renderer/src/components/ChatInput.tsx | 245 ++++ packages/renderer/src/hooks/useFileUpload.ts | 142 +++ packages/renderer/src/pages/Channels.tsx | 2 +- packages/renderer/src/pages/Chat.tsx | 113 +- packages/renderer/src/pages/Message.tsx | 68 +- packages/renderer/tsconfig.json | 2 +- .../server/public/migrations/10-files.sql | 27 + packages/server/src/constants.ts | 9 +- packages/server/src/db/query.ts | 2 +- .../server/src/db/snippets/message/new.sql | 12 +- .../server/src/db/snippets/message/recent.sql | 5 +- packages/server/src/http/file.ts | 25 + packages/server/src/index.ts | 28 +- packages/server/src/lib/WebSocketServer.ts | 53 +- .../dbHelpers/add/file/path/addFilePath.sql | 18 + .../dbHelpers/add/file/path/addFilePath.ts | 7 + .../lib/dbHelpers/add/file/raw/addFileRaw.sql | 18 + .../lib/dbHelpers/add/file/raw/addFileRaw.ts | 7 + packages/server/src/lib/dbHelpers/database.ts | 15 +- .../src/lib/dbHelpers/get/file/by/uid.sql | 3 + .../src/lib/dbHelpers/get/file/by/uid.ts | 6 + .../server/src/lib/generateSessionToken.ts | 2 +- packages/server/src/routers/file.ts | 143 +++ packages/server/src/routers/message.ts | 69 +- packages/server/src/routers/session.ts | 1 - packages/server/src/routers/voice.ts | 1 - scripts/watch.js | 21 +- types/api/file.d.ts | 36 + .../api/generic.d.ts | 0 types/api/message.d.ts | 35 + 36 files changed, 1372 insertions(+), 876 deletions(-) create mode 100644 packages/renderer/src/components/ChatInput.tsx create mode 100644 packages/renderer/src/hooks/useFileUpload.ts create mode 100644 packages/server/public/migrations/10-files.sql create mode 100644 packages/server/src/http/file.ts create mode 100644 packages/server/src/lib/dbHelpers/add/file/path/addFilePath.sql create mode 100644 packages/server/src/lib/dbHelpers/add/file/path/addFilePath.ts create mode 100644 packages/server/src/lib/dbHelpers/add/file/raw/addFileRaw.sql create mode 100644 packages/server/src/lib/dbHelpers/add/file/raw/addFileRaw.ts create mode 100644 packages/server/src/lib/dbHelpers/get/file/by/uid.sql create mode 100644 packages/server/src/lib/dbHelpers/get/file/by/uid.ts create mode 100644 packages/server/src/routers/file.ts create mode 100644 types/api/file.d.ts rename rtc-test/EventEmitter.mjs => types/api/generic.d.ts (100%) create mode 100644 types/api/message.d.ts diff --git a/.gitignore b/.gitignore index 223e1a0..8aaca79 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,5 @@ thumbs.db # docker data docker-volume + +storage \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index ee34c61..56c0182 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,7 @@ services: db: image: mariadb restart: always + command: --max_allowed_packet=200M environment: MARIADB_ROOT_PASSWORD: example MARIADB_DATABASE: corner @@ -20,15 +21,15 @@ services: ports: - 8080:8080 - coturn: - image: coturn/coturn - restart: always - ports: - - 3478:3478 - - 3478:3478/udp - - 5349:5349 - - 5349:5349/udp - - 49160-49200:49160-49200/udp + # coturn: + # image: coturn/coturn + # restart: always + # ports: + # - 3478:3478 + # - 3478:3478/udp + # - 5349:5349 + # - 5349:5349/udp + # - 49160-49200:49160-49200/udp # docker run -d -p 3478:3478 -p 3478:3478/udp -p 5349:5349 -p 5349:5349/udp -p 49160-49200:49160-49200/udp \ # coturn/coturn -n --log-file=stdout \ diff --git a/package-lock.json b/package-lock.json index c0135c5..b9f3b2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,19 +7,25 @@ "name": "corner", "hasInstallScript": true, "dependencies": { + "@types/express": "^4.17.13", + "@types/express-ws": "^3.0.1", "@types/mysql": "^2.15.21", "@types/qrcode": "^1.4.2", "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", "@types/react-timeago": "^4.1.3", + "@types/tmp": "^0.2.3", "@types/totp-generator": "^0.0.4", "@types/uuid": "^8.3.4", "@types/ws": "^8.5.3", "@vitejs/plugin-react": "^2.0.0", + "chalk": "^4.1.2", "cordova": "^11.0.0", "electron-updater": "5.0.5", "eslint-plugin-react": "^7.30.1", "express": "^4.18.1", + "express-ws": "^5.0.2", + "fs-extra": "^10.1.0", "get-port": "^6.1.2", "local-storage": "^2.0.0", "mysql": "^2.18.1", @@ -28,9 +34,12 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^4.4.0", + "react-spinners": "^0.13.4", "react-time-ago": "^7.2.1", "react-timeago": "^7.1.0", + "react-toastify": "^9.0.8", "reactjs-popup": "^2.0.5", + "tmp": "^0.2.1", "totp-generator": "^0.0.13", "uuid": "^8.3.2", "vue": "3.2.37", @@ -563,6 +572,29 @@ "global-tunnel-ng": "^2.7.1" } }, + "node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@electron/get/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/@electron/get/node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -605,18 +637,6 @@ "node": ">=10" } }, - "node_modules/@electron/universal/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/@electron/universal/node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -762,18 +782,6 @@ "node": ">=10" } }, - "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/@malept/flatpak-bundler/node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -958,6 +966,15 @@ "node": ">= 10" } }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, "node_modules/@types/chai": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz", @@ -982,6 +999,14 @@ "@types/node": "*" } }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", @@ -991,6 +1016,37 @@ "@types/ms": "*" } }, + "node_modules/@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.30", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz", + "integrity": "sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/express-ws": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/express-ws/-/express-ws-3.0.1.tgz", + "integrity": "sha512-VguRXzcpPBF0IggIGpUoM65cZJDfMQxoc6dKoCz1yLzcwcXW7ft60yhq3ygKhyEhEIQFtLrWjyz4AJ1qjmzCFw==", + "dependencies": { + "@types/express": "*", + "@types/express-serve-static-core": "*", + "@types/ws": "*" + } + }, "node_modules/@types/form-data": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", @@ -1026,6 +1082,11 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "node_modules/@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" + }, "node_modules/@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -1079,8 +1140,12 @@ "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "node_modules/@types/react": { "version": "18.0.15", @@ -1123,6 +1188,20 @@ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==" }, + "node_modules/@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA==" + }, "node_modules/@types/totp-generator": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/@types/totp-generator/-/totp-generator-0.0.4.tgz", @@ -1914,41 +1993,6 @@ "node": ">=14.0.0" } }, - "node_modules/app-builder-lib/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/app-builder-lib/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/app-builder-lib/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -2454,41 +2498,6 @@ "node": ">=12.0.0" } }, - "node_modules/builder-util/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/builder-util/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/builder-util/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/builtins": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", @@ -2808,6 +2817,14 @@ "mimic-response": "^1.0.0" } }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -3156,17 +3173,6 @@ "node": ">=10" } }, - "node_modules/cordova-common/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/cordova-common/node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -3196,38 +3202,6 @@ "node": ">=10" } }, - "node_modules/cordova-create/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cordova-create/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/cordova-create/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/cordova-fetch": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/cordova-fetch/-/cordova-fetch-3.0.1.tgz", @@ -3261,17 +3235,6 @@ "node": ">=10" } }, - "node_modules/cordova-fetch/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/cordova-fetch/node_modules/pify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", @@ -3317,30 +3280,6 @@ "node": ">=12" } }, - "node_modules/cordova-lib/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cordova-lib/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/cordova-lib/node_modules/pify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", @@ -3352,14 +3291,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cordova-lib/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/cordova-serve": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cordova-serve/-/cordova-serve-4.0.0.tgz", @@ -3388,38 +3319,6 @@ "node": ">=8" } }, - "node_modules/cordova/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cordova/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/cordova/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -3762,41 +3661,6 @@ "dmg-license": "^1.0.11" } }, - "node_modules/dmg-builder/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/dmg-builder/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/dmg-builder/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/dmg-license": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", @@ -3945,41 +3809,6 @@ "node": ">=14.0.0" } }, - "node_modules/electron-builder/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/electron-builder/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/electron-builder/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/electron-devtools-installer": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/electron-devtools-installer/-/electron-devtools-installer-3.2.0.tgz", @@ -4055,41 +3884,6 @@ "mime": "^2.5.2" } }, - "node_modules/electron-publish/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/electron-publish/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/electron-publish/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/electron-to-chromium": { "version": "1.4.195", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.195.tgz", @@ -4111,38 +3905,6 @@ "typed-emitter": "^2.1.0" } }, - "node_modules/electron-updater/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/electron-updater/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/electron-updater/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/elementtree": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/elementtree/-/elementtree-0.1.7.tgz", @@ -5018,6 +4780,40 @@ "node": ">= 0.10.0" } }, + "node_modules/express-ws": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/express-ws/-/express-ws-5.0.2.tgz", + "integrity": "sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ==", + "dependencies": { + "ws": "^7.4.6" + }, + "engines": { + "node": ">=4.5.0" + }, + "peerDependencies": { + "express": "^4.0.0 || ^5.0.0-alpha.1" + } + }, + "node_modules/express-ws/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -5359,17 +5155,24 @@ } }, "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dependencies": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=6 <7 || >=8" + "node": ">=12" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" } }, "node_modules/fs-minipass": { @@ -6799,14 +6602,24 @@ "dev": true }, "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, + "node_modules/jsonfile/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -8678,6 +8491,15 @@ "node": ">=0.10.0" } }, + "node_modules/react-spinners": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.13.4.tgz", + "integrity": "sha512-V6IURjYOwomhdngMfuVxBp4utCF6v21sjQ6r4K2JoKl8fwXZp1UeHMBLf+2SU+cts8hAVj9rHOJ8kdT5UqqaJw==", + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-time-ago": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/react-time-ago/-/react-time-ago-7.2.1.tgz", @@ -8701,6 +8523,18 @@ "react": "^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-toastify": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.0.8.tgz", + "integrity": "sha512-EwM+teWt49HSHx+67qI08yLAW1zAsBxCXLCsUfxHYv1W7/R3ZLhrqKalh7j+kjgPna1h5LQMSMwns4tB4ww2yQ==", + "dependencies": { + "clsx": "^1.1.1" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/reactjs-popup": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/reactjs-popup/-/reactjs-popup-2.0.5.tgz", @@ -9786,41 +9620,6 @@ "fs-extra": "^10.0.0" } }, - "node_modules/temp-file/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/temp-file/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/temp-file/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -11326,6 +11125,26 @@ "sumchecker": "^3.0.1" }, "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -11361,16 +11180,6 @@ "universalify": "^2.0.0" } }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -11481,16 +11290,6 @@ "universalify": "^2.0.0" } }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -11639,6 +11438,15 @@ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, "@types/chai": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz", @@ -11663,6 +11471,14 @@ "@types/node": "*" } }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "requires": { + "@types/node": "*" + } + }, "@types/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", @@ -11672,6 +11488,37 @@ "@types/ms": "*" } }, + "@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.30", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz", + "integrity": "sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/express-ws": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/express-ws/-/express-ws-3.0.1.tgz", + "integrity": "sha512-VguRXzcpPBF0IggIGpUoM65cZJDfMQxoc6dKoCz1yLzcwcXW7ft60yhq3ygKhyEhEIQFtLrWjyz4AJ1qjmzCFw==", + "requires": { + "@types/express": "*", + "@types/express-serve-static-core": "*", + "@types/ws": "*" + } + }, "@types/form-data": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", @@ -11707,6 +11554,11 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" + }, "@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -11760,8 +11612,12 @@ "@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "@types/react": { "version": "18.0.15", @@ -11806,6 +11662,20 @@ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==" }, + "@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "requires": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "@types/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA==" + }, "@types/totp-generator": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/@types/totp-generator/-/totp-generator-0.0.4.tgz", @@ -12383,35 +12253,6 @@ "semver": "^7.3.7", "tar": "^6.1.11", "temp-file": "^3.4.0" - }, - "dependencies": { - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } } }, "aproba": { @@ -12782,35 +12623,6 @@ "source-map-support": "^0.5.19", "stat-mode": "^1.0.0", "temp-file": "^3.4.0" - }, - "dependencies": { - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } } }, "builder-util-runtime": { @@ -13054,6 +12866,11 @@ "mimic-response": "^1.0.0" } }, + "clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -13285,32 +13102,6 @@ "semver": "^7.3.5", "systeminformation": "^5.9.17", "update-notifier": "^5.1.0" - }, - "dependencies": { - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" - } } }, "cordova-app-hello-world": { @@ -13350,15 +13141,6 @@ "universalify": "^2.0.0" } }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -13382,32 +13164,6 @@ "path-is-inside": "^1.0.2", "tmp": "^0.2.1", "valid-identifier": "0.0.2" - }, - "dependencies": { - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" - } } }, "cordova-fetch": { @@ -13436,15 +13192,6 @@ "universalify": "^2.0.0" } }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, "pify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", @@ -13480,34 +13227,10 @@ "write-file-atomic": "^3.0.3" }, "dependencies": { - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, "pify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==" - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" } } }, @@ -13783,35 +13506,6 @@ "fs-extra": "^10.0.0", "iconv-lite": "^0.6.2", "js-yaml": "^4.1.0" - }, - "dependencies": { - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } } }, "dmg-license": { @@ -13921,35 +13615,6 @@ "read-config-file": "6.2.0", "update-notifier": "^5.1.0", "yargs": "^17.0.1" - }, - "dependencies": { - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } } }, "electron-devtools-installer": { @@ -14017,35 +13682,6 @@ "fs-extra": "^10.0.0", "lazy-val": "^1.0.5", "mime": "^2.5.2" - }, - "dependencies": { - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } } }, "electron-to-chromium": { @@ -14067,32 +13703,6 @@ "lodash.isequal": "^4.5.0", "semver": "^7.3.5", "typed-emitter": "^2.1.0" - }, - "dependencies": { - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" - } } }, "elementtree": { @@ -14685,6 +14295,22 @@ } } }, + "express-ws": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/express-ws/-/express-ws-5.0.2.tgz", + "integrity": "sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ==", + "requires": { + "ws": "^7.4.6" + }, + "dependencies": { + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "requires": {} + } + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -14949,14 +14575,20 @@ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "requires": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + } } }, "fs-minipass": { @@ -16000,12 +15632,19 @@ "dev": true }, "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + } } }, "jsonparse": { @@ -17405,6 +17044,12 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==" }, + "react-spinners": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.13.4.tgz", + "integrity": "sha512-V6IURjYOwomhdngMfuVxBp4utCF6v21sjQ6r4K2JoKl8fwXZp1UeHMBLf+2SU+cts8hAVj9rHOJ8kdT5UqqaJw==", + "requires": {} + }, "react-time-ago": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/react-time-ago/-/react-time-ago-7.2.1.tgz", @@ -17421,6 +17066,14 @@ "integrity": "sha512-rouF7MiEm55fH791Y8cg+VobIJgx8gtNJ+gjr86R4ZqO1WKPkXiXjdT/lRzrvEkUzsxT1exHqV2V+Zdi114H3A==", "requires": {} }, + "react-toastify": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.0.8.tgz", + "integrity": "sha512-EwM+teWt49HSHx+67qI08yLAW1zAsBxCXLCsUfxHYv1W7/R3ZLhrqKalh7j+kjgPna1h5LQMSMwns4tB4ww2yQ==", + "requires": { + "clsx": "^1.1.1" + } + }, "reactjs-popup": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/reactjs-popup/-/reactjs-popup-2.0.5.tgz", @@ -18228,35 +17881,6 @@ "requires": { "async-exit-hook": "^2.0.1", "fs-extra": "^10.0.0" - }, - "dependencies": { - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } } }, "text-table": { diff --git a/package.json b/package.json index d33a261..0b608ab 100644 --- a/package.json +++ b/package.json @@ -55,19 +55,25 @@ "vue-tsc": "0.38.8" }, "dependencies": { + "@types/express": "^4.17.13", + "@types/express-ws": "^3.0.1", "@types/mysql": "^2.15.21", "@types/qrcode": "^1.4.2", "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", "@types/react-timeago": "^4.1.3", + "@types/tmp": "^0.2.3", "@types/totp-generator": "^0.0.4", "@types/uuid": "^8.3.4", "@types/ws": "^8.5.3", "@vitejs/plugin-react": "^2.0.0", + "chalk": "^4.1.2", "cordova": "^11.0.0", "electron-updater": "5.0.5", "eslint-plugin-react": "^7.30.1", "express": "^4.18.1", + "express-ws": "^5.0.2", + "fs-extra": "^10.1.0", "get-port": "^6.1.2", "local-storage": "^2.0.0", "mysql": "^2.18.1", @@ -76,9 +82,12 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^4.4.0", + "react-spinners": "^0.13.4", "react-time-ago": "^7.2.1", "react-timeago": "^7.1.0", + "react-toastify": "^9.0.8", "reactjs-popup": "^2.0.5", + "tmp": "^0.2.1", "totp-generator": "^0.0.13", "uuid": "^8.3.2", "vue": "3.2.37", diff --git a/packages/renderer/index.html b/packages/renderer/index.html index c40973c..754d27c 100644 --- a/packages/renderer/index.html +++ b/packages/renderer/index.html @@ -7,7 +7,7 @@ default-src 'self' data: https://ssl.gstatic.com https://fonts.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; media-src * data:; - img-src 'self' data: content: http://tinygraphs.com; + img-src 'self' data: content: *; connect-src *;"> Vite App diff --git a/packages/renderer/src/App.tsx b/packages/renderer/src/App.tsx index 5281442..9082195 100644 --- a/packages/renderer/src/App.tsx +++ b/packages/renderer/src/App.tsx @@ -7,6 +7,7 @@ import ServerConnection from './components/ServerConnection'; import EphemeralState from './contexts/EphemeralState/EphemeralState'; import PersistentState from '/@/contexts/PersistentState/PersistentState'; import Router from './Router'; +import { ToastContainer } from 'react-toastify'; export default function App() { const [transparent, setTransparent] = useState(false); @@ -64,6 +65,32 @@ export default function App() { border-radius: 16px; padding: 0px 8px; } + /* width */ + ::-webkit-scrollbar { + width: 16px; + height: 16px; + } + + /* Track */ + ::-webkit-scrollbar-track { + background: unset + } + + /* Handle */ + ::-webkit-scrollbar-thumb { + background: white; + border-radius: 10px; + box-shadow: inset 0px -10px -10px 0px var(--neutral-3); + } + + /* Handle on hover */ + ::-webkit-scrollbar-thumb:hover { + background: #555; + } + + ::-webkit-scrollbar-thumb:active { + background: var(--neutral-9); + } `}
+ +
); diff --git a/packages/renderer/src/components/ChatInput.tsx b/packages/renderer/src/components/ChatInput.tsx new file mode 100644 index 0000000..129db7a --- /dev/null +++ b/packages/renderer/src/components/ChatInput.tsx @@ -0,0 +1,245 @@ +import { useEffect, useRef, useState } from "react"; +import { v4 } from 'uuid'; +import { GrFormClose } from 'react-icons/gr'; +import { IoMdClose } from "react-icons/io"; +import testImage from '../../assets/pfp.jpg' +import useFileUpload, { Upload } from "../hooks/useFileUpload"; +import useChannel from "../hooks/useChannel"; +import { useApi } from "../lib/useApi"; + +const exampleImages = [ + { + id: v4(), + name: 'image.jpg', + data: testImage, + } +] + +export default function ChatInput() { + const textBoxRef = useRef(null); + const { channel } = useChannel(); + + const [attachments, setAttachments] = useState([]); + + const { newFile, getFileInfo } = useFileUpload(); + + const PADDING = 8; + const CHATBOX_SIZE = 64; + + const addAttachment = (id: string) => { + setAttachments(attachments => [...attachments, id]) + } + + const removeAttachment = (id: string) => { + setAttachments(attachments => attachments.filter(a => a !== id)); + } + + // useEffect(() => { + // addAttachment(newFile('image.jpg', testImage)); + // }, []); + + const pasta: React.ClipboardEventHandler = (event) => { + + let pastedFiles = false; + + for (let idx = 0; idx < event.clipboardData.items.length; idx ++) { + const item = event.clipboardData.items[idx]; + const file = event.clipboardData.files[idx]; + const type = event.clipboardData.types[idx] + + if (item.kind === 'file') { + var blob = item.getAsFile(); + if(blob === null) continue; + + addAttachment(newFile(file.name, file.type, blob)); + + pastedFiles = true; + } + } + + if(pastedFiles) { + event.preventDefault(); + } + } + + const { send } = useApi({}); + + const keyPress = (event: any) => { + if(event.code === 'Enter' && !event.shiftKey) { + event.stopPropagation(); + event.preventDefault(); + event.bubbles = false; + + for(const attachment of attachments) { + const info = getFileInfo(attachment); + if(!info?.processed) { + return true; + } + } + + const [file, ...restFiles] = attachments.map(a => getFileInfo(a)?.externalId) as string[]; + const text = textBoxRef.current?.innerHTML ?? ''; + + if(text === '' && file === undefined) { + return true; + } + + if(channel === null) return true; + + const newMessage: NewMessageRequest = { + uid: v4(), + text, + channel, + timestamp: new Date().getTime(), + file + } + + console.log(file, restFiles); + + send('message:message', newMessage); + + for(const file of restFiles) { + const newMessage: NewMessageRequest = { + uid: v4(), + text: '', + channel, + timestamp: new Date().getTime(), + file + } + send('message:message', newMessage); + } + + setAttachments([]); + if(textBoxRef.current !== null) { + textBoxRef.current.innerHTML = ''; + } + + return true; + } + } + + return ( +
+
+ {attachments.length > 0 && ( +
+
+ {attachments.map(attachment => { + const info = getFileInfo(attachment); + if(!info) return Poop; + return ( + removeAttachment(attachment)} + > + ); + })} +
+
+ )} +
+
{ + textBoxRef.current?.focus(); + }} + style={{ + margin: PADDING + 'px', + marginRight: '0px', + borderRadius: ((CHATBOX_SIZE - PADDING*2) / 2) + 'px', + background: 'var(--neutral-5)', + gridArea: 'message', + display: 'grid', + placeItems: 'center center', + padding: '8px 16px', + minHeight: '48px', + boxSizing: 'border-box', + cursor: 'text', + overflow: 'auto', + }} + onPaste={pasta} + > +
+
+
+ ) +} + +function AttachmentBox(props: { + attachment: Upload, + onClose: React.MouseEventHandler +}) { + return ( +
+
+
+ +
{props.attachment.name}
+ {/* */} +
+
+ ) +} \ No newline at end of file diff --git a/packages/renderer/src/hooks/useFileUpload.ts b/packages/renderer/src/hooks/useFileUpload.ts new file mode 100644 index 0000000..48eb836 --- /dev/null +++ b/packages/renderer/src/hooks/useFileUpload.ts @@ -0,0 +1,142 @@ +import { useCallback, useEffect, useState } from "react"; +import { useApi } from "../lib/useApi"; +import { v4 } from 'uuid'; +import { useLog } from "../components/useLog"; + +const b64 = async (data: Uint8Array) => { + const base64url = await new Promise((r) => { + const reader = new FileReader(); + reader.onload = () => r(reader.result); + reader.readAsDataURL(new Blob([data])); + }); + // @ts-ignore + return base64url.split(",", 2)[1]; +}; + +export interface Upload { + internalId: string; + externalId: string | null; + data: Blob; + reader: any; + progress: number; + name: string; + sent: number; + rcvd: number; + uploaded: boolean; + processed: boolean; +} + +// 400 kb +const CHUNK_SIZE = 4_000; + +export default function useFileUpload() { + const [uploads, setUploads] = useState([]); + + const updateUpload = (clientId: string, newData: Partial) => { + setUploads(uploads => uploads.map(upload => { + if(upload.internalId !== clientId) return upload; + return { + ...upload, + ...newData, + }; + })); + }; + + const { send } = useApi({ + 'file:new'(data: NewFileResponse) { + updateUpload(data.clientId, { externalId: data.serverId }); + }, + 'file:chunk'(data: FileChunkResponse) { + updateUpload(data.clientId, { rcvd: data.chunk, progress: data.progress }); + }, + 'file:end'(data: FileEndResponse) { + updateUpload(data.clientId, { processed: true }); + } + }, []); + + type Shit = { + chunk: Uint8Array; + done: boolean; + } + + const sendNextChunk = async (upload: Upload) => { + if (upload.externalId === null) return; + + const {chunk, done}: Shit = await new Promise(async (res) => { + const { value, done } = await upload.reader.read(); + res({ + chunk: value, + done + }) + }); + + if(chunk === undefined && done) { + updateUpload(upload.internalId, { uploaded: true }); + const fileEndRequest: FileEndRequest = { + serverId: upload.externalId, + } + send('file:end', fileEndRequest) + return; + } + + const chunkb64 = await b64(chunk); + + updateUpload(upload.internalId, { sent: upload.sent + 1 }); + + const chunkReq: FileChunkRequest = { + chunk: upload.sent, + data: chunkb64, + serverId: upload.externalId, + }; + + send('file:chunk', chunkReq); + } + + useEffect(() => { + for(const upload of uploads) { + if(upload.rcvd === upload.sent && !upload.uploaded) { + sendNextChunk(upload); + } + } + }, [uploads]) + + const newFile = useCallback((name: string, type: string, blob: Blob) => { + const id = v4(); + const newFileReq: NewFileRequest = { + clientId: id, + length: blob.size, + name, + type, + }; + + // @ts-ignore + const reader = blob.stream().getReader(); + + setUploads(uploads => [...uploads, { + internalId: id, + externalId: null, + data: blob, + reader: reader, + progress: 0, + name, + sent: 0, + rcvd: 0, + uploaded: false, + processed: false, + }]); + send('file:new', newFileReq); + return id; + }, []); + + const getInfo = useCallback((clientId: string) => { + const file = uploads.find(upload => upload.internalId === clientId); + return file ?? null; + }, [uploads]) + + // useLog(uploads, 'uploads'); + + return { + newFile, + getFileInfo: getInfo + } +} \ No newline at end of file diff --git a/packages/renderer/src/pages/Channels.tsx b/packages/renderer/src/pages/Channels.tsx index b187eec..5ad8d9a 100644 --- a/packages/renderer/src/pages/Channels.tsx +++ b/packages/renderer/src/pages/Channels.tsx @@ -58,7 +58,7 @@ export default function Channels() { useEffect(() => { if(channels.length === 0) return; if(channel !== null) return; - setChannel(channels[1].uid, channels[1].type); + setChannel(channels[0].uid, channels[0].type); }, [channel, channels]); useEffect(() => { diff --git a/packages/renderer/src/pages/Chat.tsx b/packages/renderer/src/pages/Chat.tsx index 3d9a857..2927099 100644 --- a/packages/renderer/src/pages/Chat.tsx +++ b/packages/renderer/src/pages/Chat.tsx @@ -7,6 +7,7 @@ import { MdSend } from 'react-icons/md'; import useChannel from '../hooks/useChannel'; import useClientId from '../hooks/useClientId'; import useSessionToken from '../hooks/useSessionToken'; +import ChatInput from '../components/ChatInput'; function createMessage(from: string, text: string, channel: string, t = 0): IMessage { @@ -21,13 +22,9 @@ function createMessage(from: string, text: string, export default () => { const [messages, setMessages] = useState([]); - const [hist, setHist] = useState(false); const { sessionToken } = useSessionToken(); - const CHATBOX_SIZE = 64; const PADDING = 8; - - const textBoxRef = useRef(null); const { channel, setChannel } = useChannel(); const { clientId } = useClientId(); @@ -36,38 +33,38 @@ export default () => { 'message:message'(data: IMessage) { if(data.channel !== channel) return; - setMessages([...messages, data]); + setMessages(messages => ([...messages, data])); }, 'message:recent'(data: { messages: IMessage[] }) { setMessages(data.messages.reverse()); }, - }, [messages]); + }, [channel]); useEffect(() => { send('message:recent', { channel }); - }, [channel, sessionToken]); + }, [channel]); - const sendMessage = useCallback(() => { - if(textBoxRef.current === null) return; - if(channel === null) return; - if(clientId === null) return; - if(sessionToken === null) return; - send( - 'message:message', - createMessage( - clientId, - textBoxRef.current.innerText, - channel, - ) - ); - textBoxRef.current.innerText = ''; - }, [channel, sessionToken]); + // const sendMessage = useCallback(() => { + // if(textBoxRef.current === null) return; + // if(channel === null) return; + // if(clientId === null) return; + // if(sessionToken === null) return; + // send( + // 'message:message', + // createMessage( + // clientId, + // textBoxRef.current.innerText, + // channel, + // ) + // ); + // textBoxRef.current.innerText = ''; + // }, [channel, sessionToken]); - const keyDown = useCallback((evt: any) => { - if(evt.key === 'Enter') { - sendMessage(); - } - }, [sendMessage]); + // const keyDown = useCallback((evt: any) => { + // if(evt.key === 'Enter') { + // sendMessage(); + // } + // }, [sendMessage]); return (
{ width: '100%', display: 'grid', background: 'var(--neutral-4)', - gridTemplateColumns: `1fr ${CHATBOX_SIZE}px`, - gridTemplateRows: `1fr ${CHATBOX_SIZE}px`, + gridTemplateColumns: `1fr min-content`, + gridTemplateRows: `1fr min-content`, gridTemplateAreas: '"content content" "message send"', }} > @@ -97,45 +94,29 @@ export default () => { ))}
-
{ - textBoxRef.current?.focus(); - }}style={{ - margin: PADDING + 'px', - marginRight: '0px', - borderRadius: ((CHATBOX_SIZE - PADDING*2) / 2) + 'px', - background: 'var(--neutral-5)', - gridArea: 'message', - display: 'grid', - placeItems: 'center center', - padding: '0px 16px', - cursor: 'text', - overflow: 'auto', - }}> -
-
+ + + + ); +}; + + +function SendButton() { + return ( +
-
{
- ); -}; \ No newline at end of file + ) +} \ No newline at end of file diff --git a/packages/renderer/src/pages/Message.tsx b/packages/renderer/src/pages/Message.tsx index b602828..98359a7 100644 --- a/packages/renderer/src/pages/Message.tsx +++ b/packages/renderer/src/pages/Message.tsx @@ -1,14 +1,9 @@ import { useContext, useEffect } from 'react'; import TimeAgo from 'react-timeago'; import { ClientsListContext } from '../contexts/EphemeralState/ClientsListState'; +import useHover from '../hooks/useHover'; -export interface IMessage { - uid: string; - timestamp: number; - from: string; - text: string; - channel: string; -} +export type IMessage = NewMessageResponse; interface MessageProps { message: IMessage @@ -16,54 +11,71 @@ interface MessageProps { const firstLineIndent = '10px'; const multiLineIndent = '16px'; -const rightMessagePagging = '16px'; +const rightMessagePadding = '16px'; export function Message({ message, }: MessageProps) { const { clientName } = useContext(ClientsListContext); + const [hoverRef, hover] = useHover(); return ( -
+
u === 'second' ? 'Now' : ('' + t + u[0])} > - -
- {clientName[message.from]} +
+ + {clientName[message.from]} + + + {message.text} +
-
- {message.text} -
- + {!!message.file && ( +
+ +
+ )} +
); } \ No newline at end of file diff --git a/packages/renderer/tsconfig.json b/packages/renderer/tsconfig.json index 085a55e..384ef61 100644 --- a/packages/renderer/tsconfig.json +++ b/packages/renderer/tsconfig.json @@ -8,7 +8,7 @@ "strict": true, "isolatedModules": true, - "types" : ["node"], + "types" : [], "baseUrl": ".", "paths": { "#preload": [ diff --git a/packages/server/public/migrations/10-files.sql b/packages/server/public/migrations/10-files.sql new file mode 100644 index 0000000..ca5fc1d --- /dev/null +++ b/packages/server/public/migrations/10-files.sql @@ -0,0 +1,27 @@ +CREATE TABLE `files` ( + `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, + `data` longblob NOT NULL, + `author` varchar(36) NOT NULL, + `t_uploaded` bigint unsigned NOT NULL, + `t_created` bigint unsigned NOT NULL, + `t_deleted` bigint unsigned NULL, + `type` varchar(255) NOT NULL +); + +ALTER TABLE `files` +ADD `uid` varchar(36) NOT NULL AFTER `id`; + +ALTER TABLE `files` +ADD UNIQUE `uid` (`uid`); + +ALTER TABLE `files` +ADD FOREIGN KEY (`author`) REFERENCES `clients` (`uid`); + +ALTER TABLE `messages` +ADD `attachment` varchar(36) COLLATE 'utf8mb4_general_ci' NULL AFTER `text`; + +ALTER TABLE `files` +CHANGE `data` `data` longblob NULL AFTER `uid`; + +ALTER TABLE `messages` +ADD FOREIGN KEY (`attachment`) REFERENCES `files` (`uid`); \ No newline at end of file diff --git a/packages/server/src/constants.ts b/packages/server/src/constants.ts index 783c702..16217b8 100644 --- a/packages/server/src/constants.ts +++ b/packages/server/src/constants.ts @@ -1,6 +1,13 @@ +import { ensureDirSync } from 'fs-extra'; +import { resolve } from 'path'; +export const STORAGE_PATH = resolve('../../../storage'); export const DB_HOST = 'localhost'; export const DB_USER = 'root'; export const DB_PASSWORD = 'example'; -export const DB_NAME = 'corner'; \ No newline at end of file +export const DB_NAME = 'corner'; + + + +ensureDirSync(STORAGE_PATH); \ No newline at end of file diff --git a/packages/server/src/db/query.ts b/packages/server/src/db/query.ts index 49f45ec..268de87 100644 --- a/packages/server/src/db/query.ts +++ b/packages/server/src/db/query.ts @@ -8,7 +8,7 @@ export default function(sqlFile: any, ...args: any[]): Promise { if(!err) return resolve(results); console.error(err.errno, err.sqlMessage); console.error('--- Query ---'); - console.error(err.sql); + console.error(err.sql?.substring(0, 10000)); reject(err); }); }); diff --git a/packages/server/src/db/snippets/message/new.sql b/packages/server/src/db/snippets/message/new.sql index cd346a3..9893aa8 100644 --- a/packages/server/src/db/snippets/message/new.sql +++ b/packages/server/src/db/snippets/message/new.sql @@ -1,3 +1,9 @@ -INSERT INTO messages - (`text`, sender_uid, `uid`, `t_sent`, channel_uid) - VALUES ( ?, ?, ?, ?, ? ); \ No newline at end of file +INSERT INTO messages ( + `text`, + sender_uid, + `uid`, + `t_sent`, + channel_uid, + attachment +) +VALUES ( ?, ?, ?, ?, ?, ? ); \ No newline at end of file diff --git a/packages/server/src/db/snippets/message/recent.sql b/packages/server/src/db/snippets/message/recent.sql index dc843fe..71fadea 100644 --- a/packages/server/src/db/snippets/message/recent.sql +++ b/packages/server/src/db/snippets/message/recent.sql @@ -4,9 +4,12 @@ SELECT clients.uid as 'from', messages.`text` as 'text', messages.channel_uid, - messages.uid as uid + messages.uid as uid, + files.type as file_type, + files.uid as file_uid FROM messages JOIN clients ON messages.sender_uid=clients.uid +LEFT JOIN files ON messages.attachment=files.uid WHERE messages.channel_uid=? ORDER BY -messages.t_sent LIMIT 100; \ No newline at end of file diff --git a/packages/server/src/http/file.ts b/packages/server/src/http/file.ts new file mode 100644 index 0000000..0feb262 --- /dev/null +++ b/packages/server/src/http/file.ts @@ -0,0 +1,25 @@ +import { Router } from 'express'; +import database from '../lib/dbHelpers/database'; +import { resolve } from 'path'; +import { STORAGE_PATH } from '../constants'; +import { createReadStream, lstatSync } from 'fs'; + +const router = Router(); + +router.get('/:uid', async (req, res) => { + const info = await database.get.file.by.uid(req.params.uid); + res.contentType = info.type; + if(info.data !== null) { + res.end(info.data); + return; + } else { + // res.end('new hype'); + const path = resolve(STORAGE_PATH, req.params.uid); + const size = lstatSync(path).size; + const stream = createReadStream(path); + res.header('Content-Length', '' + size); + stream.pipe(res); + } +}) + +export default router; \ No newline at end of file diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 296b4d2..378b989 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -1,10 +1,19 @@ import router from './lib/router'; import { expose, reply } from './lib/WebSocketServer'; +// ws import message from './routers/message'; import channel from './routers/channel'; import client from './routers/client'; import totp from './routers/totp'; +import session from './routers/session'; +import voice from './routers/voice'; +import file from './routers/file'; +import express from 'express'; +import expressWs from 'express-ws'; + +// http +import fileRouter from './http/file'; const api = router({ up() { @@ -21,16 +30,27 @@ const api = router({ totp: totp, session: session, sessions: session, - voice: voice + voice: voice, + file: file, + files: file, }); -expose(api, 3000); +// var express = require('express'); +// var app = express(); +// var expressWs = require('express-ws')(app); + +const app = express(); +const ewss = expressWs(app); + +// @ts-ignore +app.ws('/', expose(api, ewss.getWss())); +app.use('/file', fileRouter); + +app.listen(3000); // ------------- import { update } from './db/migrate'; -import session from './routers/session'; -import voice from './routers/voice'; try { update(); diff --git a/packages/server/src/lib/WebSocketServer.ts b/packages/server/src/lib/WebSocketServer.ts index 8e369c9..99244c5 100644 --- a/packages/server/src/lib/WebSocketServer.ts +++ b/packages/server/src/lib/WebSocketServer.ts @@ -1,15 +1,40 @@ import { WebSocketServer, WebSocket } from 'ws'; import { inspect } from 'util'; import { validateSessionToken } from '../routers/session'; +import chalk from 'chalk'; -export function expose(router: Function, port: number) { - const wss = new WebSocketServer({ - port: 3000, - }, () => { - console.log('ws chat server started on dev.valnet.xyz'); - }); - - wss.on('connection', (ws) => { +function str2color(str: string) { + const v = str.split('').reduce((acc, val) => acc + val.charCodeAt(0), 0); + return (v % 213) + 17 +} + +function log(prefix: string, action: string, data: object) { + if(action === 'up') return; + const strAction = action.split(':').map(v => chalk.ansi256(str2color(v))(v)).join(':'); + const filteredObject = Object.entries(data) + .filter(e => !e[0].startsWith('$')); + const keyCount = Object.keys(filteredObject).length; + if(keyCount === 0) return console.log(prefix, strAction); + + const stringify = (key: string, value: any) => { + switch(typeof value) { + case 'string': return chalk.green(key); + case 'object': + if(value === null) return chalk.blackBright(key); + else if(Array.isArray(value)) return chalk.cyanBright(`[${key}]`); + else return chalk.magenta(key); + case 'number': return chalk.yellow(key); + default: return key; + } + } + + const params = filteredObject.map(([k, v]) => stringify(k, v)) + + console.log(prefix, strAction, params.join(', ')); +} + +export function expose(router: Function, wss: WebSocketServer) { + return function(ws: WebSocket) { ws.on('message', async (str) => { try { const message = JSON.parse(str.toString()); @@ -29,23 +54,21 @@ export function expose(router: Function, port: number) { if(typeof data !== 'object') { throw new Error('action ' + action + ' payload not an object'); } - console.log('[IN]', action, data); + log(chalk.green('>>>'), action, data); const _return = await (router(action, data, ws, wss) as unknown as Promise); if(_return) { try { switch(_return.type) { case ResponseType.BROADCAST: { - console.log('[OUT_BROADCAST]', action, _return.data); + log(chalk.cyan('(\u25CF)'), action, _return.data) + // console.log('[OUT_BROADCAST]', action, _return.data); for(const client of wss.clients) { send(client, action, _return.data); } break; } case ResponseType.REPLY: { - console.log('[OUT]', action, inspect(_return.data, { - depth: 0, - colors: true, - })); + log(chalk.cyan('<<<'), action, _return.data); send(ws, action, _return.data); break; } @@ -63,7 +86,7 @@ export function expose(router: Function, port: number) { console.error(e); } }); - }); + } } enum ResponseType { diff --git a/packages/server/src/lib/dbHelpers/add/file/path/addFilePath.sql b/packages/server/src/lib/dbHelpers/add/file/path/addFilePath.sql new file mode 100644 index 0000000..629a1a0 --- /dev/null +++ b/packages/server/src/lib/dbHelpers/add/file/path/addFilePath.sql @@ -0,0 +1,18 @@ +INSERT INTO files ( + uid, + author, + type, + data, + t_created, + t_uploaded, + t_deleted +) +VALUES ( + ?, + ?, + ?, + NULL, + ?, + ?, + NULL +) \ No newline at end of file diff --git a/packages/server/src/lib/dbHelpers/add/file/path/addFilePath.ts b/packages/server/src/lib/dbHelpers/add/file/path/addFilePath.ts new file mode 100644 index 0000000..8b1e756 --- /dev/null +++ b/packages/server/src/lib/dbHelpers/add/file/path/addFilePath.ts @@ -0,0 +1,7 @@ +import query from "/@/db/query"; +import sql from './addFilePath.sql'; + +export default function addFile(uid: string, author: string, type: string) { + const now = new Date().getTime(); + return query(sql, uid, author, type, now, now); +} \ No newline at end of file diff --git a/packages/server/src/lib/dbHelpers/add/file/raw/addFileRaw.sql b/packages/server/src/lib/dbHelpers/add/file/raw/addFileRaw.sql new file mode 100644 index 0000000..b01045a --- /dev/null +++ b/packages/server/src/lib/dbHelpers/add/file/raw/addFileRaw.sql @@ -0,0 +1,18 @@ +INSERT INTO files ( + uid, + author, + type, + data, + t_created, + t_uploaded, + t_deleted +) +VALUES ( + ?, + ?, + ?, + ?, + ?, + ?, + NULL +) \ No newline at end of file diff --git a/packages/server/src/lib/dbHelpers/add/file/raw/addFileRaw.ts b/packages/server/src/lib/dbHelpers/add/file/raw/addFileRaw.ts new file mode 100644 index 0000000..a6d8d10 --- /dev/null +++ b/packages/server/src/lib/dbHelpers/add/file/raw/addFileRaw.ts @@ -0,0 +1,7 @@ +import query from "/@/db/query"; +import sql from './addFileRaw.sql'; + +export default function addFile(uid: string, author: string, data: Buffer, type: string) { + const now = new Date().getTime(); + return query(sql, uid, author, type, data, now, now); +} \ No newline at end of file diff --git a/packages/server/src/lib/dbHelpers/database.ts b/packages/server/src/lib/dbHelpers/database.ts index d211442..040e078 100644 --- a/packages/server/src/lib/dbHelpers/database.ts +++ b/packages/server/src/lib/dbHelpers/database.ts @@ -1,11 +1,24 @@ import getAllDisplayNames from './get/all/displayNames'; - +import addFileRaw from './add/file/raw/addFileRaw'; +import addFilePath from './add/file/path/addFilePath'; +import getFileByUid from './get/file/by/uid'; const database = { get: { all: { displayNames: getAllDisplayNames + }, + file: { + by: { + uid: getFileByUid + } + } + }, + add: { + file: { + raw: addFileRaw, + path: addFilePath } } }; diff --git a/packages/server/src/lib/dbHelpers/get/file/by/uid.sql b/packages/server/src/lib/dbHelpers/get/file/by/uid.sql new file mode 100644 index 0000000..199b5bb --- /dev/null +++ b/packages/server/src/lib/dbHelpers/get/file/by/uid.sql @@ -0,0 +1,3 @@ +SELECT data, type, uid +FROM files +WHERE uid=?; \ No newline at end of file diff --git a/packages/server/src/lib/dbHelpers/get/file/by/uid.ts b/packages/server/src/lib/dbHelpers/get/file/by/uid.ts new file mode 100644 index 0000000..9953695 --- /dev/null +++ b/packages/server/src/lib/dbHelpers/get/file/by/uid.ts @@ -0,0 +1,6 @@ +import sql from './uid.sql'; +import query from '/@/db/query'; + +export default async function getFileByUid(uid: string) { + return ((await query(sql, uid)) ?? [])[0]; +} \ No newline at end of file diff --git a/packages/server/src/lib/generateSessionToken.ts b/packages/server/src/lib/generateSessionToken.ts index 541916a..188dfec 100644 --- a/packages/server/src/lib/generateSessionToken.ts +++ b/packages/server/src/lib/generateSessionToken.ts @@ -7,7 +7,7 @@ export const generateSessionToken = async (clientId: string) => { for (let i = 0; i < 64; i++) { token += rb32(); } - console.log('created session token', clientId, token); + // scnd min hr day year const year = 1000 * 60 * 60 * 24 * 365; const expiration = Date.now() + year; diff --git a/packages/server/src/routers/file.ts b/packages/server/src/routers/file.ts new file mode 100644 index 0000000..65a81be --- /dev/null +++ b/packages/server/src/routers/file.ts @@ -0,0 +1,143 @@ +import router from "../lib/router"; +import { reply } from "../lib/WebSocketServer"; +import { v4 } from 'uuid'; +import tmp from 'tmp'; +import { writeFile, createWriteStream, WriteStream, readFile, createReadStream } from 'fs'; +import database from "../lib/dbHelpers/database"; +import { resolve } from 'path'; +import { STORAGE_PATH } from "../constants"; + +function createFile(id: string): WriteStream { + return createWriteStream(resolve(STORAGE_PATH, id)); +} + +interface ServerUpload { + clientId: string; + serverId: string; + length: number; + authorId: string; + chunk: number; + progress: number; + type: string; + // tmp file + path: string; + writeStream: WriteStream, + remove: Function; +} + + + +const tempFiles: ServerUpload[] = []; + +export default router({ + async 'new'(data: NewFileRequest) { + if(typeof data.$clientId === 'undefined') return; + const serverId = v4(); + const temp = tmp.fileSync(); + const writeStream = createWriteStream(temp.name, 'base64'); + tempFiles.push({ + clientId: data.clientId, + authorId: data.$clientId, + length: data.length, + path: temp.name, + writeStream, + remove: temp.removeCallback, + serverId, + chunk: 0, + progress: 0, + type: data.type, + }); + const res: NewFileResponse = { + serverId, + clientId: data.clientId + }; + return reply(res); + }, + async 'chunk'(data: FileChunkRequest) { + const upload = tempFiles.find(upload => upload.serverId === data.serverId); + if(!upload) return; + if(data.chunk !== upload.chunk) return; + if(upload.writeStream.bytesWritten >= upload.length) return; + + await new Promise((res, rej) => { + upload.writeStream.write(data.data, (err) => { + if(err) rej(err); + res(undefined); + }); + }); + + // if(upload.buffer.length === upload.length) { + // await new Promise((res, rej) => { + // upload.writeStream.close((err) => { + // if(err) rej(err); + // console.log('upload file closed!'); + // res(undefined); + // }); + // }); + // } + + upload.chunk ++; + upload.progress = upload.writeStream.bytesWritten; + // upload.buffer += data.data; + + // await new Promise(res => setTimeout(res, 300)); + + const res: FileChunkResponse = { + chunk: upload.chunk, + serverId: upload.serverId, + clientId: upload.clientId, + progress: (upload.progress / upload.length) + } + + return reply(res); + + }, + async 'end'(data: FileEndRequest) { + const upload = tempFiles.find(upload => upload.serverId === data.serverId); + if(!upload) return; + // if(upload.buffer.length !== upload.length) return; + await new Promise(res => upload.writeStream.close(res)); + + if(upload.length > 100_000_000) { + const read = createReadStream(upload.path); + const write = createFile(upload.serverId); + const pipe = read.pipe(write); + await new Promise((res) => { + pipe.on('finish', () => { + res(undefined) + }); + }); + + await database.add.file.path( + upload.serverId, + upload.authorId, + upload.type + ); + } else { + const file: Buffer = await new Promise((res, rej) => { + readFile(upload.path, (err, data) => { + if(err) rej(err); + res(data); + }) + }); + + await database.add.file.raw( + upload.serverId, + upload.authorId, + file, + upload.type + ); + } + + const res: FileEndResponse = { + serverId: upload.serverId, + clientId: upload.clientId, + url: `https://dev.valnet.xyz/files/${upload.serverId}` + } + return reply(res); + + } +}); + +// 012345 67 0123 4567 01 234567 +// 012345 01 2345 0123 45 012345 \ No newline at end of file diff --git a/packages/server/src/routers/message.ts b/packages/server/src/routers/message.ts index 6b3c0c5..1ddace1 100644 --- a/packages/server/src/routers/message.ts +++ b/packages/server/src/routers/message.ts @@ -3,13 +3,26 @@ import router from '../lib/router'; import newMessage from '../db/snippets/message/new.sql'; import recentMessages from '../db/snippets/message/recent.sql'; import { broadcast, reply } from '../lib/WebSocketServer'; +import database from '../lib/dbHelpers/database'; + +function transformMessage(text: string) { + if(text === '/shrug') { + return '¯\\_(ツ)_/¯'; + } + else return text; +} export default router({ - async message(data: any) { - if(!('$clientId' in data)) { + async message(data: NewMessageRequest) { + if(!('$clientId' in data) || data.$clientId === undefined) { console.error('unauthenticated message rejected.'); return null; } + + const file = data.file !== undefined ? + await database.get.file.by.uid(data.file) : + undefined; + const response = await query( newMessage, data.text, @@ -17,11 +30,26 @@ export default router({ data.uid, data.timestamp, data.channel, + data.file ?? null ); if(response === null) return; - - data.from = data.$clientId; - return broadcast(data); + + const res: NewMessageResponse = { + uid: data.uid, + from: data.$clientId, + text: data.text, + timestamp: data.timestamp, + channel: data.channel, + } + + if(file !== undefined) { + res.file = { + type: file.type, + url: `https://dev.valnet.xyz/file/${file.uid}` + } + } + + return broadcast(res); }, async recent(data: any) { if(!('$clientId' in data)) { @@ -30,13 +58,32 @@ export default router({ } const messages = await query(recentMessages, data.channel); if(messages === null) return; + + function convert(row: any) { + if(row.file_uid === null) { + return { + from: row.from, + uid: row.uid, + timestamp: row.t_sent, + text: row.text, + } + } else { + return { + from: row.from, + uid: row.uid, + timestamp: row.t_sent, + text: row.text, + file: { + type: row.file_type, + url: `https://dev.valnet.xyz/file/${row.file_uid}` + } + } + } + } + return reply({ - messages: messages.map(v => ({ - from: v.from, - uid: v.uid, - timestamp: v.t_sent, - text: v.text, - })), + messages: messages.map(convert), + channel: data.channel }); }, }); \ No newline at end of file diff --git a/packages/server/src/routers/session.ts b/packages/server/src/routers/session.ts index 7a4b113..41a2f60 100644 --- a/packages/server/src/routers/session.ts +++ b/packages/server/src/routers/session.ts @@ -26,7 +26,6 @@ export default router({ err: 'Incorrect username or auth code' }); const validTotp = await validateClientTotp(clientId, totp); - console.log(username, clientId, validTotp); if(!validTotp) return reply({ err: 'Incorrect username or auth code' }); diff --git a/packages/server/src/routers/voice.ts b/packages/server/src/routers/voice.ts index 8e3a0fc..5dab0e5 100644 --- a/packages/server/src/routers/voice.ts +++ b/packages/server/src/routers/voice.ts @@ -62,7 +62,6 @@ export default router({ async leave(data: any) { const { $clientId } = data; const removed = filterInPlace(participants, (v) => v.clientId !== $clientId); - console.log('removed', removed); return broadcast(removed[0]); }, }) \ No newline at end of file diff --git a/scripts/watch.js b/scripts/watch.js index 406718e..21351c9 100644 --- a/scripts/watch.js +++ b/scripts/watch.js @@ -34,7 +34,7 @@ const setupServerPackageWatcher = () => { let spawnProcess = null; const processDied = () => { - logger.error('Server has died.', {timestamp: true}); + logger.error('Server has died.', {timestamp: false}); spawnProcess = null; }; @@ -56,10 +56,14 @@ const setupServerPackageWatcher = () => { spawnProcess = spawn(node, ['./index.cjs'], { cwd: './packages/server/dist', + env: { + ...process.env, + FORCE_COLOR: "true" + } }); /** Proxy all logs */ - spawnProcess.stdout.on('data', d => d.toString().trim() && d.toString().trim().split('\n').forEach(str => logger.info(str, {timestamp: true}))); + spawnProcess.stdout.on('data', d => d.toString().trim() && d.toString().trim().split('\n').forEach(str => logger.info(str, {timestamp: false}))); /** Proxy error logs but stripe some noisy messages. See {@link stderrFilterPatterns} */ spawnProcess.stderr.on('data', d => { @@ -67,7 +71,7 @@ const setupServerPackageWatcher = () => { if (!data) return; const mayIgnore = stderrFilterPatterns.some((r) => r.test(data)); if (mayIgnore) return; - data.split('\n').forEach(d => logger.error(d, {timestamp: true})); + data.split('\n').forEach(d => logger.error(d, {timestamp: false})); }); /** Stops the watch script when the application has been quit */ @@ -117,10 +121,15 @@ const setupMainPackageWatcher = ({resolvedUrls}) => { } /** Spawn new electron process */ - spawnProcess = spawn(String(electronPath), ['.']); + spawnProcess = spawn(String(electronPath), ['.'], { + env: { + ...process.env, + FORCE_COLOR: "true" + } + }); /** Proxy all logs */ - spawnProcess.stdout.on('data', d => d.toString().trim() && logger.warn(d.toString(), {timestamp: true})); + spawnProcess.stdout.on('data', d => d.toString().trim() && logger.warn(d.toString(), {timestamp: false})); /** Proxy error logs but stripe some noisy messages. See {@link stderrFilterPatterns} */ spawnProcess.stderr.on('data', d => { @@ -128,7 +137,7 @@ const setupMainPackageWatcher = ({resolvedUrls}) => { if (!data) return; const mayIgnore = stderrFilterPatterns.some((r) => r.test(data)); if (mayIgnore) return; - logger.error(data, {timestamp: true}); + logger.error(data, {timestamp: false}); }); /** Stops the watch script when the application has been quit */ diff --git a/types/api/file.d.ts b/types/api/file.d.ts new file mode 100644 index 0000000..7df5df7 --- /dev/null +++ b/types/api/file.d.ts @@ -0,0 +1,36 @@ + +interface NewFileRequest { + clientId: string; + length: number; + name: string; + type: string; + $clientId?: string; +} + +interface NewFileResponse { + clientId: string; + serverId: string; +} + +interface FileChunkRequest { + chunk: number; + serverId: string; + data: string; +} + +interface FileChunkResponse { + chunk: number; + serverId: string; + clientId: string; + progress: number; +} + +interface FileEndRequest { + serverId: string; +} + +interface FileEndResponse { + serverId: string; + clientId: string; + url: string; +} \ No newline at end of file diff --git a/rtc-test/EventEmitter.mjs b/types/api/generic.d.ts similarity index 100% rename from rtc-test/EventEmitter.mjs rename to types/api/generic.d.ts diff --git a/types/api/message.d.ts b/types/api/message.d.ts new file mode 100644 index 0000000..44c0dc1 --- /dev/null +++ b/types/api/message.d.ts @@ -0,0 +1,35 @@ + +interface NewMessageRequest { + file?: string; + text: string; + $clientId?: string; + uid: string; + timestamp: number; + channel: string; +} + +interface NewMessageResponse { + uid: string; + from: string; + text: string; + timestamp: number; + channel: string; + file?: { + url: string; + type: string; + } +} + +interface RecentMessagesResponse { + channel: string; +} + +interface RecentMessagesResponse { + messages: { + uid: string; + from: string; + text: string; + timestamp: number; + }[]; + channel: string; +} \ No newline at end of file