From f85e0878ad276ac97027ebf4d772ed65168ef8ad Mon Sep 17 00:00:00 2001 From: katarzynakaz Date: Thu, 19 Feb 2026 19:00:11 +0000 Subject: [PATCH 1/7] Submit Sprint 3: Middleware and Websockets --- .DS_Store | Bin 0 -> 10244 bytes .gitignore | 1 + .vscode/settings.json | 3 + backend/.gitignore | 1 + backend/package-lock.json | 1044 ++++++++++++++++++++++++++ backend/package.json | 7 + backend/server.mjs | 154 ++++ backend/serverWEBSCOKETNOTES.mjs | 277 +++++++ backend/websocket.mjs | 311 ++++++++ backend/websocketexercises.md | 54 ++ frontend/chat.js | 182 +++++ frontend/index.html | 53 ++ frontend/style.css | 121 +++ frontend/websocket.js | 317 ++++++++ sprint3 middleware/mw1.js | 105 +++ sprint3 middleware/mw2.js | 46 ++ sprint3 middleware/package-lock.json | 854 +++++++++++++++++++++ sprint3 middleware/package.json | 16 + 18 files changed, 3546 insertions(+) create mode 100644 .DS_Store create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 backend/.gitignore create mode 100644 backend/package-lock.json create mode 100644 backend/package.json create mode 100644 backend/server.mjs create mode 100644 backend/serverWEBSCOKETNOTES.mjs create mode 100644 backend/websocket.mjs create mode 100644 backend/websocketexercises.md create mode 100644 frontend/chat.js create mode 100644 frontend/index.html create mode 100644 frontend/style.css create mode 100644 frontend/websocket.js create mode 100644 sprint3 middleware/mw1.js create mode 100644 sprint3 middleware/mw2.js create mode 100644 sprint3 middleware/package-lock.json create mode 100644 sprint3 middleware/package.json diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..97bdb83b513ba568f608be9a3a43081223c0e76c GIT binary patch literal 10244 zcmeHNU2GIp6uxKrH$w+Hg)WqZcDJlrg$f1QN(E%wZL$28bX!^qvfbSoU}QQ|c4oI| zMOS@NLi~%y7&Y-piOPfV2Y;d_21OryP}E?Ii5QI<3?v#8qYs{Y?<}^vZA^?LE}2Q@ zp1J4Tb7#+)^Ub&S?h-YcindOqbIj?ZV)9=iYc`d6D{p6O~IDd)+0^9iS|{^c)hyq$Pr2Og;uQXiybsZP3wLh2)Hi; zGCMD@qlHl!b|#WAXjCPwxv0L(MJ3IulxO?<2X+n)$uVVa20wo9n4?>!y;DosC}T>M zjbUy|TIPG#*Jj!h&x(`VMO)YHPltv$&_UnJ1kqL)9e~@w8IXy z6_|#GZ7bzO*knUG&d*0FXY-@O{LnVn zZSZ%9%4u;gz46w#?evamCf=Kd5UKb3cTs(Zs%okmfxpMUSyVm|hwTILl-4_rmB=UR z@oyJJ_DoSvXX;jU$GT+fqeewHGddPIK0aRSVkq|rLgA(SGW71PS$4&8845RZwS;6= zM^=$8vXzXG6qz8$$m8T0@-jI^-Y1`tugUl1Joya(1So@YsDv7*g8&4f0hYotxE_|n zTIhmq*a&^F8}`6nh=B^DV8R$2f(+aRS-1!8h5O)Pcmy7W$KYvr7M_FS@DjWUZ^7H} z4txw>z&CIXzJ>GfGhBd+@P|+;lnLcRteaEE|U|1ISiQ5F$gfp|+M>mEa$o=fp&ibLXb!qc1qG;hhK_DnA_tT*gbeRq(}>-2gbVL3 zYYGGu0a41k%_{@4Qi2$im~U$ncERnm-zdvUDWZ|vZmPpBaXXz6oQ7gc{I9C~EICJh zAQ#D>SmkqI9@Ii3G(#J#!J0J@EFo1PE0tra6+P1K=Q((g#a3@yx-B{m8;TSvs z55hz61Uv~(u^K-P&%+DwBD@MG;dOWe-h)%{0i1>p;UoAIzJl-IC-^0=mbc}qm`nJz zu$D8?lxbMzezZ^oh-4q)*23*<=ZYf5g1G*f7JXL$Eh3`jN%S`p>=`ipT6B$gU7fot!t7yJm*tYVqaSle$#?TF e`;P%LZK-+de{cQIs=MGz5dTwK(Qk_X9oJvt=WBWZ literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6f3a291 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5501 +} \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 0000000..e0129a3 --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,1044 @@ +{ + "name": "backend", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "cors": "^2.8.6", + "express": "^5.2.1", + "websocket": "^1.0.35" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bufferutil": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", + "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC" + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/websocket": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz", + "integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==", + "license": "Apache-2.0", + "dependencies": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.63", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/websocket/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/websocket/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", + "engines": { + "node": ">=0.10.32" + } + } + } +} diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..6583ac1 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "cors": "^2.8.6", + "express": "^5.2.1", + "websocket": "^1.0.35" + } +} diff --git a/backend/server.mjs b/backend/server.mjs new file mode 100644 index 0000000..1900795 --- /dev/null +++ b/backend/server.mjs @@ -0,0 +1,154 @@ +import express from "express"; +import cors from "cors"; +import { on } from "events"; +// import { text } from "body-parser"; issue with this so replaced with express + +const app = express(); +// const port = 3000; +// because server was not working +const port = process.env.PORT || 3000; +app.use(cors()); + +const messages = [ + { + id: 1, + username: "annonymous", + msgText: "First chat message", + timestamp: Date.now(), + }, +]; + +//now with added poling since last id +// from fe +// const keepFetchingMessages = async () => { +// const lastSeenId = messages.length > 0 ? messages[messages.length - 1].id : null; +// const queryString = lastSeenId ? `?since=${lastSeenId}` : ""; +// const server = url +// const urlofserv = `${server}messages${queryString}`; +// const rawResponse = await fetch(urlofserv); +// const response = await rawResponse.json(); +// messages.push(...response); +// // render(); +// seeAllMessages(); +// setTimeout(keepFetchingMessages, 100); +// } + +// }); +//get all messages +app.get("/", (req, res) => { + res.json(messages); +}); + +//get recent messages poll +app.get("/messages", (req, res) => { + //sincetimestapm Teach your backend how to answer “since when” queries. + const since = parseInt(req.query.since); + if (since) { + const onlyRecentMsgs = messages.filter((msg) => msg.timestamp > since); + res.json(onlyRecentMsgs); + return; + } + //keep if no since and show all + res.json(messages); +}); + +//messages with long polling only +const callbacksForNewMessages = []; +app.get("/long-poll", (req, res) => { + let messagesToSend = []; + + //since from messages get + const since = parseInt(req.query.since); + if (since) { + messagesToSend = messages.filter((msg) => msg.timestamp > since); + } + + //from coursework pasted + // Now, if 'since' was provided but no NEW messages were found, + // messagesToSend.length will be 0, and the server will WAIT. + if (messagesToSend.length === 0) { + callbacksForNewMessages.push((value) => res.send(value)); + } else { + res.send(messagesToSend); + } +}); + +//add msg to chat +app.post("/", (req, res) => { + const bodyBytes = []; + req.on("data", (chunk) => bodyBytes.push(...chunk)); + req.on("end", () => { + const bodyString = String.fromCharCode(...bodyBytes); + let body; + try { + body = JSON.parse(bodyString); + } catch (error) { + console.error(`Failed to parse body ${bodyString} as JSON: ${error}`); + res.status(400).send("Expected body to be JSON."); + return; + } + if ( + typeof body != "object" || + !("username" in body) || + !("msgText" in body) + ) { + console.error( + `Failed to extract username and message text from body: ${bodyString}` + ); + res + .status(400) + .send( + "Expected body to be a JSON object containing keys username and message text." + ); + return; + } + //here add the checks on backedn 400 + + body.msgText = body.msgText.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); + body.username = body.username.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); + + if (!body.msgText || !body.username) { + res.status(400).send("Please add a quote and an username."); + return; + } + + if (body.msgText.length > 400 || body.username.length > 40) { + res + .status(400) + .send( + "Message text must be up to 400 chars and username must be less than 40 chars." + ); + return; + } + + // to add new id to message + const newId = messages.length + 1; + + // messages.push({ + // id: newId, + // msgText: body.msgText, + // username: body.username, + // timestamp: Date.now(), + // }); + //newMessage obnj to send to long poll so instead of above + const newMessage = { + id: newId, + msgText: body.msgText, + username: body.username, + timestamp: Date.now(), + }; + + messages.push(newMessage); + + while (callbacksForNewMessages.length > 0) { + const callback = callbacksForNewMessages.pop(); + callback([newMessage]); + } + + res.send("ok"); + }); +}); + +app.listen(port, () => { + console.error(`Chat server listening on port ${port}`); +}); diff --git a/backend/serverWEBSCOKETNOTES.mjs b/backend/serverWEBSCOKETNOTES.mjs new file mode 100644 index 0000000..64541e6 --- /dev/null +++ b/backend/serverWEBSCOKETNOTES.mjs @@ -0,0 +1,277 @@ +import express from "express"; +import cors from "cors"; +import { on } from "events"; +// import { text } from "body-parser"; issue with this so replaced with express +import { server as WebSocketServer } from "websocket"; + +const app = express(); +// const port = 3000; +// because server was not working +const port = process.env.PORT || 3000; +app.use(cors()); + +const server = http.createServer(app); +const webSocketServer = new WebSocketServer({ httpServer: server }); + +const messages = [ + { + id: 1, + username: "annonymous", + msgText: "First chat message", + timestamp: Date.now(), + }, +]; + +//now with added poling since last id +// from fe +// const keepFetchingMessages = async () => { +// const lastSeenId = messages.length > 0 ? messages[messages.length - 1].id : null; +// const queryString = lastSeenId ? `?since=${lastSeenId}` : ""; +// const server = url +// const urlofserv = `${server}messages${queryString}`; +// const rawResponse = await fetch(urlofserv); +// const response = await rawResponse.json(); +// messages.push(...response); +// // render(); +// seeAllMessages(); +// setTimeout(keepFetchingMessages, 100); +// } + +// }); +//get all messages +app.get("/", (req, res) => { + res.json(messages); +}); + +//get recent messages poll +app.get("/messages", (req, res) => { + //sincetimestapm Teach your backend how to answer “since when” queries. + const since = parseInt(req.query.since); + if (since) { + const onlyRecentMsgs = messages.filter((msg) => msg.timestamp > since); + res.json(onlyRecentMsgs); + return; + } + //keep if no since and show all + res.json(messages); +}); + +//messages with long polling only +const callbacksForNewMessages = []; +app.get("/long-poll", (req, res) => { + let messagesToSend = []; + + //since from messages get + const since = parseInt(req.query.since); + if (since) { + messagesToSend = messages.filter((msg) => msg.timestamp > since); + } + + //from coursework pasted + // Now, if 'since' was provided but no NEW messages were found, + // messagesToSend.length will be 0, and the server will WAIT. + if (messagesToSend.length === 0) { + callbacksForNewMessages.push((value) => res.send(value)); + } else { + res.send(messagesToSend); + } +}); + +//add msg to chat +app.post("/", (req, res) => { + const bodyBytes = []; + req.on("data", (chunk) => bodyBytes.push(...chunk)); + req.on("end", () => { + const bodyString = String.fromCharCode(...bodyBytes); + let body; + try { + body = JSON.parse(bodyString); + } catch (error) { + console.error(`Failed to parse body ${bodyString} as JSON: ${error}`); + res.status(400).send("Expected body to be JSON."); + return; + } + if ( + typeof body != "object" || + !("username" in body) || + !("msgText" in body) + ) { + console.error( + `Failed to extract username and message text from body: ${bodyString}` + ); + res + .status(400) + .send( + "Expected body to be a JSON object containing keys username and message text." + ); + return; + } + //here add the checks on backedn 400 + + body.msgText = body.msgText.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); + body.username = body.username.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); + + if (!body.msgText || !body.username) { + res.status(400).send("Please add a quote and an username."); + return; + } + + if (body.msgText.length > 400 || body.username.length > 40) { + res + .status(400) + .send( + "Message text must be up to 400 chars and username must be less than 40 chars." + ); + return; + } + + // to add new id to message + const newId = messages.length + 1; + + // messages.push({ + // id: newId, + // msgText: body.msgText, + // username: body.username, + // timestamp: Date.now(), + // }); + //newMessage obnj to send to long poll so instead of above + const newMessage = { + id: newId, + msgText: body.msgText, + username: body.username, + timestamp: Date.now(), + }; + + messages.push(newMessage); + + while (callbacksForNewMessages.length > 0) { + const callback = callbacksForNewMessages.pop(); + callback([newMessage]); + } + + res.send("ok"); + }); +}); + +app.listen(port, () => { + console.error(`Chat server listening on port ${port}`); +}); + + +//websocket instance +websocket.addEventListener("open", () => { + log("CONNECTED"); + pingInterval = setInterval(() => { + log(`SENT: ping: ${counter}`); + websocket.send("ping"); + }, 1000); +}); + +//send msg t server +websocket.addEventListener("open", () => { + log("CONNECTED"); + pingInterval = setInterval(() => { + log(`SENT: ping: ${counter}`); + websocket.send("ping"); + }, 1000); +}); + +//receive msg from server +websocket.addEventListener("message", (e) => { + log(`RECEIVED: ${e.data}: ${counter}`); + counter++; +}); + +//server send s json websocket.addEventListener("message", (e) => { + const message = JSON.parse(e.data); + log(`RECEIVED: ${message.iteration}: ${message.content}`); + counter++; +}); + +websocket.addEventListener("close", () => { + log("DISCONNECTED"); + clearInterval(pingInterval); +}); + + + + + +// notes from websoket +//on server js we do this: +// this is for ws package not npm notes + +// const WebSocket = require("ws"); +// const server = new WebSocket.Server({ port: 8080 }); + +//first event is connection from client +// server.on('connection') + +//when connection made access to web s object and callback +//this is ws library only again, api is diffferent + +// server.on("connection", (socket) => { //ws automatically accepts a connection + +// // we can listen to incoming messages - text and handle in callback +// socket.on("message", (message) => { +// //also send message back to clinet +// socket.send(`Message received: ${message}`); +// }); +// }); + +//using websocket package it is: +//receives a request object +// webSocketServer.on("request", (request) => { + +// //call accept to open connection +// const connection = request.accept(null, request.origin); + +// //sendUTF to send text +// connection.sendUTF("Hello"); + +// connection.on("message", (message) => { +// console.log("Received:", message.utf8Data); +// }); +// }); + +// following coursework +import { server as WebSocketServer } from "websocket"; +const server = http.createServer(app); +const webSocketServer = new WebSocketServer({ httpServer: server }); + +// npm install websocket +//following this + +//when client wants to connect to webscoket server sends a request object +webSocketServer.on("request", (request) => { + //call accept to open connection + //accept client connection and gives connection consts to talk to client + const connection = request.accept(null, request.origin); + + //sendUTF to send text + //sends msg immediately from server when connected + connection.sendUTF("Hello from server"); + + //this is a listener for messages from client + connection.on("message", (message) => { + console.log("Msg from client", message.utf8Data); + }); +}); + + + +//on client side code we have a built in websocket class built in +// instantiate with url that points to server +const socket = new WebSocket("ws://localhost:8080"); + +//trigger handshake to open connection +// and we listen to it as event +socket.onmessage = ({ data }) => { + console.log("Message from server ", data); +}; + +document.querySelector(".send-btn").addEventListener("click", () => { + socket.send("Hello from client!"); +}); + +//full duplex connection diff --git a/backend/websocket.mjs b/backend/websocket.mjs new file mode 100644 index 0000000..24953a0 --- /dev/null +++ b/backend/websocket.mjs @@ -0,0 +1,311 @@ +//imports +import express from "express"; +import cors from "cors"; + +const app = express(); + +// following coursework +import { server as WebSocketServer } from "websocket"; + +//ReferenceError: http is not defined +import http from "http"; + +const server = http.createServer(app); +const webSocketServer = new WebSocketServer({ httpServer: server }); + +const port = 3000; +app.use(cors()); + +// npm install websocket +//following this + +//when client wants to connect to webscoket server sends a request object +webSocketServer.on("request", (request) => { + //call accept to open connection + //accept client connection and gives connection consts to talk to client + const connection = request.accept(null, request.origin); + + //sendUTF to send text + //sends msg immediately from server when connected + connection.sendUTF("Hello from server"); + + //this is a listener for messages from client + connection.on("message", (message) => { + console.log("Msg from client", message.utf8Data); + }); +}); + +// import express from "express"; +// import cors from "cors"; +// import { on } from "events"; +// import { text } from "body-parser"; issue with this so replaced with express + +// const app = express(); +// const port = 3000; +// because server was not working +// const port = process.env.PORT || 3000; +// app.use(cors()); + +const messages = [ + { + id: 1, + username: "Kaska", + msgText: "Hey people, how are you doing?", + timestamp: Date.now(), + likesCount: 0, + dislikesCount: 0, + }, +]; + +//now with added poling since last id +// from fe +// const keepFetchingMessages = async () => { +// const lastSeenId = messages.length > 0 ? messages[messages.length - 1].id : null; +// const queryString = lastSeenId ? `?since=${lastSeenId}` : ""; +// const server = url +// const urlofserv = `${server}messages${queryString}`; +// const rawResponse = await fetch(urlofserv); +// const response = await rawResponse.json(); +// messages.push(...response); +// // render(); +// seeAllMessages(); +// setTimeout(keepFetchingMessages, 100); +// } + +// }); +//get all messages +app.get("/", (req, res) => { + res.json(messages); +}); + +//get recent messages poll +app.get("/messages", (req, res) => { + //sincetimestapm Teach your backend how to answer “since when” queries. + const since = parseInt(req.query.since); + if (since) { + const onlyRecentMsgs = messages.filter((msg) => msg.timestamp > since); + res.json(onlyRecentMsgs); + return; + } + //keep if no since and show all + res.json(messages); +}); + +//messages with long polling only +const callbacksForNewMessages = []; +app.get("/long-poll", (req, res) => { + let messagesToSend = []; + + //since from messages get + const since = parseInt(req.query.since); + if (since) { + messagesToSend = messages.filter((msg) => msg.timestamp > since); + } + + //from coursework pasted + // Now, if 'since' was provided but no NEW messages were found, + // messagesToSend.length will be 0, and the server will WAIT. + if (messagesToSend.length === 0) { + callbacksForNewMessages.push((value) => res.send(value)); + } else { + res.send(messagesToSend); + } +}); + +//add msg to chat +app.post("/", (req, res) => { + const bodyBytes = []; + req.on("data", (chunk) => bodyBytes.push(...chunk)); + req.on("end", () => { + const bodyString = String.fromCharCode(...bodyBytes); + let body; + try { + body = JSON.parse(bodyString); + } catch (error) { + console.error(`Failed to parse body ${bodyString} as JSON: ${error}`); + res.status(400).send("Expected body to be JSON."); + return; + } + if ( + typeof body != "object" || + !("username" in body) || + !("msgText" in body) + ) { + console.error( + `Failed to extract username and message text from body: ${bodyString}` + ); + res + .status(400) + .send( + "Expected body to be a JSON object containing keys username and message text." + ); + return; + } + //here add the checks on backedn 400 + + body.msgText = body.msgText.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); + body.username = body.username.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); + + if (!body.msgText || !body.username) { + res.status(400).send("Please add a quote and an username."); + return; + } + + if (body.msgText.length > 400 || body.username.length > 40) { + res + .status(400) + .send( + "Message text must be up to 400 chars and username must be less than 40 chars." + ); + return; + } + + // to add new id to message + const newId = messages.length + 1; + + const newMessage = { + id: newId, + msgText: body.msgText, + username: body.username, + timestamp: Date.now(), + //updated initialised + likesCount: 0, + dislikesCount: 0, + }; + + messages.push(newMessage); + + while (callbacksForNewMessages.length > 0) { + const callback = callbacksForNewMessages.pop(); + callback([newMessage]); + } + + res.send("ok"); + }); +}); + +//add liking disliking route +app.post("/vote", (req, res) => { + const bodyBytes = []; + req.on("data", (chunk) => bodyBytes.push(...chunk)); + req.on("end", () => { + const bodyString = String.fromCharCode(...bodyBytes); + let body; + try { + body = JSON.parse(bodyString); + } catch (error) { + console.error(`Failed to parse body ${bodyString} as JSON: ${error}`); + res.status(400).send("Expected body to be JSON."); + return; + } + if ( + typeof body != "object" || + // !("username" in body) || + // !("msgText" in body) + //getting by id and vote type + !("id" in body) || + !("vote" in body) + ) { + console.error( + // `Failed to extract username and message text from body: ${bodyString}` + `Failed to extract id and vote type.` + ); + res + .status(400) + .send( + "Expected body to be a JSON object containing keys uis and vote type." + ); + return; + } + //this part is post new message only + + // body.msgText = body.msgText.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); + // body.username = body.username.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); + + // if (!body.msgText || !body.username) { + // res.status(400).send("Please add a quote and an username."); + // return; + // } + + // if (body.msgText.length > 400 || body.username.length > 40) { + // res + // .status(400) + // .send( + // "Message text must be up to 400 chars and username must be less than 40 chars." + // ); + // return; + // } + + // // to add new id to message + // const newId = messages.length + 1; + + // //add likes and dislikes + + // const newMessage = { + // id: newId, + // msgText: body.msgText, + // username: body.username, + // timestamp: Date.now(), + // //updated initialised + // likesCount: 0, + // dislikesCount: 0, + // }; + + // messages.push(newMessage); + + // while (callbacksForNewMessages.length > 0) { + // const callback = callbacksForNewMessages.pop(); + // callback([newMessage]); + // } + + //grab currently liked disliked message + + //add like and dislike + const likeOrDislike = () => { + for (const message of messages) { + if (message.id === body.id) { + return message; + } + // } return "No message with this id"; bug wrong because returns string and 404 doesnt show + } + return null; + }; + const currentyLikedDislikedMsg = likeOrDislike(); + + if (!currentyLikedDislikedMsg) { + res.status(404).send("No message with this id."); + return; + } + + if (body.vote === "like") { + currentyLikedDislikedMsg.likesCount += 1; + currentyLikedDislikedMsg.timestamp = Date.now(); + } else if (body.vote === "dislike") { + currentyLikedDislikedMsg.dislikesCount += 1; + currentyLikedDislikedMsg.timestamp = Date.now(); + } else { + //here to add invalid vote type + res.status(400).send("Invalid vote type."); + return; + } + + //copy from the other post to update + while (callbacksForNewMessages.length > 0) { + const callback = callbacksForNewMessages.pop(); + // callback([newMessage]); + callback([currentyLikedDislikedMsg]); + } + + // res.send("ok"); // this is wrong and message instead + res.json(currentyLikedDislikedMsg); + }); +}); + +// this was not working with websocket + +// app.listen(port, () => { +// console.error(`Chat server listening on port ${port}`); +// }); +server.listen(port, () => { + console.log(`Server running at http://localhost:${port}`); +}); diff --git a/backend/websocketexercises.md b/backend/websocketexercises.md new file mode 100644 index 0000000..177b509 --- /dev/null +++ b/backend/websocketexercises.md @@ -0,0 +1,54 @@ +Exercises + +How will you make sure that your client won’t miss any new messages, after getting the initial messages? Write down your strategy. + +When the client loads, first fetch all messages array with the get request like in the previous task. +Then open WS to listen rather than doing the long polling to receive new messages in real-time. Use the implemented timestamp so only the new meesages get populated. + +Think: What advantages does each approach have? + +Http - It works and is relaible, after setting up not too complicated to test. +Websocket - instant messages with no need to send repeated requests. + +Why might we want to change our implementation to use a WebSocket for sending messages? + +Makes it more real time and less delay for the users. + +Why might we want to keep using POST requests for sending messages? + +It's more straightforward and works for occasionally sent messages, like in ths fake chat app. + +Why might we want to support both on our server? Why might we only want to support one on our server? + +Using both gives flexibility if ws is not available. +One makes the implementation easier. + +Think about what information a client would need to provide to a server in order to like/dislike a message. +Which message is liked/disliked, is it like or dislike. + +Think about what information a server would need to provide to a client in order to display how many likes/dislikes a message has. +Which messge it is, how many likes and dislikes it has. + +Think about what information a server would need to provide to a client in order to update how many likes/dislikes a message has. +Message is and update like and dislike numbers. + +Write down some advantages and disadvantages of a server -> client update being “+1 compared to before” or “now =10”. +Just change is noted with + so less data compared to =. + +- may not be up to date. + +Choose which approach you want to take. +Show total number of likes and dislikes. + +Implement liking and disliking messages: +If a message has a non-zero number of likes or dislikes, the frontend needs to show this. +The frontend needs to expose some way for a user to like or dislike any message. +Heart to like and count number next to for likes, thumbs down for dislike. + +When a user likes or dislikes a message, the frontend needs to tell the backend about this, and the backend needs to notify all clients of this. +When a frontend is notified by a backend about a new like or dislike, it needs to update the UI to show this. +You may do this in your polling implementation, WebSockets implementation, or both. +Websocket server listens on new messages and also changed to timestamp part of message. If message is liked/disliked, send message gets updated timestamp. grabbed by id, since timestamp is new message rendered with updated count shows to user. +so: +fetch all old get http +open websocket - new message and like/dislike = new timestamp -> show diff --git a/frontend/chat.js b/frontend/chat.js new file mode 100644 index 0000000..f543b24 --- /dev/null +++ b/frontend/chat.js @@ -0,0 +1,182 @@ +const chatFeedDiv = document.getElementById("chat-feed"); +const fullMsgInChat = document.querySelector(".div-for-each-msg"); +const msgInChat = document.querySelector(".msg-text-in-chat-view"); +const usernameInChat = document.querySelector(".username-in-chat-view"); + +const refreshBtn = document.getElementById("refresh-btn"); +const sendBtn = document.getElementById("send-msg-btn"); +const pollBtn = document.getElementById("poll-btn"); + +const addMsgUsernameInput = document.getElementById("add-msg-username"); +const addMsgTextInput = document.getElementById("add-msg-text"); +const confirmToUser = document.getElementById("confirm-to-user"); + +// const url = "http://localhost:3000"; +const url = "https://katchatapp.hosting.codeyourfuture.io"; + +// generatequote and show to user +const seeAllMessages = async () => { + // const selectedQuote = pickFromArray(quotes); + const response = await fetch(url); + const allMessages = await response.json(); + + //this fixes the messages not showing on live sitwe + state.messages = allMessages; + // quote.innerHTML = selectedQuote.quote; + // author.innerHTML = selectedQuote.author; + // change greetingi am keeping everythign as is and jusy want to + // document.getElementById("greeting").innerHTML = "Your quote is:"; + // document.getElementById("greeting").style = "size: 1rem"; + chatFeedDiv.innerHTML = ""; + allMessages.forEach((msg) => { + chatFeedDiv.innerHTML += ` +
+

${msg.msgText}

+

${msg.username}

+
`; + }); +}; + +//show to user on click of new quote +refreshBtn.addEventListener("click", seeAllMessages); + +const sendMsg = async () => { + //removing whitespace or special characters + const addMsgText = addMsgTextInput.value + .trim() + .replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); + const addMsgUsername = addMsgUsernameInput.value + .trim() + .replace(/[^a-zA-Z0-9,.;:?! ]/g, "") + .toUpperCase(); + + //check if empty or too long + if (!addMsgText || !addMsgUsername) { + confirmToUser.innerHTML = "Please add message text and your username."; + return; + } + + if (addMsgText.length > 400 || addMsgUsername.length > 40) { + confirmToUser.innerHTML = + "Message must be up to 400 chars and username must be less than 40 chars."; + return; + } + + // so macthes backend (typeof body != "object" || !("quote" in body) || !("author" in body)) + const addingMsg = { + msgText: addMsgText, + username: addMsgUsername, + }; + + const responseFromAdd = await fetch(url, { + method: "POST", + headers: { + //so backedn can parde body as json + "Content-Type": "application/json", + }, + //turn obj into str typeof body != "object" + body: JSON.stringify(addingMsg), + }); + + // quotes.push(newQuoteAuthor, newQuoteText); - rhtis alsready in the backed so no need + // tell user + if (responseFromAdd.ok === true) { + confirmToUser.innerHTML = "Your message has been sent."; + //clear input + addMsgTextInput.value = ""; + addMsgUsernameInput.value = ""; + } else { + //take error message frrom response grab from heree + // console.error(`Failed to extract quote and author from post body: ${bodyString}`); + // res.status(400).send("Expected body to be a JSON object containing keys quote and author."); + // return; + const errorToShow = await responseFromAdd.text(); + confirmToUser.innerHTML = `${errorToShow} Please try again.`; + } +}; + +sendBtn.addEventListener("click", sendMsg); + +// auto refresh +// setInterval(seeAllMessages, 2000); + +//polling coursework +// const keepFetchingMessages = async () => { +// const lastMessageTime = state.messages.length > 0 ? state.messages[state.messages.length - 1].timestamp : null; +// const queryString = lastMessageTime ? `?since=${lastMessageTime}` : ""; +// const url = `${server}/messages${queryString}`; +// const rawResponse = await fetch(url); +// const response = await rawResponse.json(); +// state.messages.push(...response); +// render(); +// setTimeout(keepFetchingMessages, 100); +// } + +let messages = []; +const state = { messages: [] }; + +//render defining from coursework example build from +const render = () => { + chatFeedDiv.innerHTML = ""; + state.messages.forEach((msg) => { + chatFeedDiv.innerHTML += ` +
+

${msg.msgText}

+

${msg.username}

+
`; + }); +}; + +//polling coursework +const keepFetchingMessages = async () => { + const lastMessageTime = + state.messages.length > 0 + ? state.messages[state.messages.length - 1].timestamp + : null; + const queryString = lastMessageTime ? `?since=${lastMessageTime}` : ""; + const urlQueryMod = `${url}/messages${queryString}`; + const rawResponse = await fetch(urlQueryMod); + const response = await rawResponse.json(); + state.messages.push(...response); + render(); + setTimeout(keepFetchingMessages, 100); +}; + +pollBtn.addEventListener("click", keepFetchingMessages); + +//test long poll +const longPollBtn = document.getElementById("long-poll-btn"); + +const testLongPoll = async () => { + const lastMessageTime = + state.messages.length > 0 + ? state.messages[state.messages.length - 1].timestamp + : null; + const queryString = lastMessageTime ? `?since=${lastMessageTime}` : ""; + const urlQueryMod = `${url}/long-poll${queryString}`; + const rawResponse = await fetch(urlQueryMod); + const response = await rawResponse.json(); + state.messages.push(...response); + render(); + testLongPoll(); +}; +longPollBtn.addEventListener("click", testLongPoll); + +//addiitonal privacy feature hide messages +const hideMessages = document.getElementById("hide-btn"); + +hideMessages.addEventListener("click", () => { + if (chatFeedDiv.style.display === "none") { + chatFeedDiv.style.display = "block"; + hideMessages.textContent = "Hide chat"; + } else { + chatFeedDiv.style.display = "none"; + hideMessages.textContent = "Show chat"; + } +}); + +// seeAllMessages(); +// but with long poll +seeAllMessages().then(() => { + testLongPoll(); +}); diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..2e10855 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,53 @@ + + + + + + Tremendous Chat + + + +
+

Chat feed

+
+
+

+

+
+
+ +
+ +
+

Send a message

+ + + +

+
+ + + + + + + + diff --git a/frontend/style.css b/frontend/style.css new file mode 100644 index 0000000..53322ae --- /dev/null +++ b/frontend/style.css @@ -0,0 +1,121 @@ +@import url("https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500;700&display=swap"); + +html { + background-color: aliceblue; + display: flex; + justify-content: center; + align-items: center; + font-family: "Roboto Mono", monospace; +} + +.container { + background-color: #f9fffeaa; + padding: 1rem; + border-radius: 0.2rem; + border: solid 1px #adadad; + box-shadow: 0px 0px 1px #b1b1b8; + margin-top: 2rem; + max-width: 1000px; + min-width: 600px; +} + +.div-for-each-msg { + display: flex; + flex-direction: column; + border-bottom: solid 1px #adadad; +} + +.msg-in-chat-view { + font-size: 1rem; + color: #4a4a4a; + font-weight: 400; +} + +.username-in-chat-view { + font-size: 1rem; + color: #4a4a4a; + font-weight: 200; +} + +.timestamp { + font-size: 0.75rem; + color: #4a4a4a; + font-weight: 200; +} + +.add-message { + display: flex; + flex-direction: column; + gap: 0.75rem; + margin-top: 1rem; +} + +#greeting, +h2 { + font-size: 1.75rem; + color: #4a4a4a; + font-weight: 400; + border-bottom: solid 2px #adadad; +} + +h2 { + font-size: 1.25rem; +} + +button { + background-color: #6299f8; + color: rgb(34, 34, 34); + padding: 10px 20px; + border-radius: 0.2rem; + font-size: 1rem; + cursor: pointer; + font-weight: 500; + margin-top: 1rem; + font-family: "Roboto Mono", monospace; +} + +.secondary-btn { + background-color: white; + border: solid 1px #5f5f5f; + font-family: "Roboto Mono", monospace; +} + +button:hover { + background-color: #6299f844; + font-weight: 600; +} + +.input { + padding: 10px; + border-radius: 0.2rem; + border: solid 1px #75758d; + font-size: 1rem; +} + +#add-msg-text { + vertical-align: top; + min-height: 100px; + + line-height: 1.4; +} + +.likedislike-div { + display: flex; + flex-direction: row; + gap: 0.75rem; + margin-bottom: 1rem; +} + +.likes-count, +.dislikes-count, +.like-btn, +.dislike-btn { + font-size: 0.75rem; + color: #4a4a4a; + margin-top: 0.5rem; +} + +.like-btn, +.dislike-btn { + background-color: white; +} diff --git a/frontend/websocket.js b/frontend/websocket.js new file mode 100644 index 0000000..8108833 --- /dev/null +++ b/frontend/websocket.js @@ -0,0 +1,317 @@ +const chatFeedDiv = document.getElementById("chat-feed"); +const fullMsgInChat = document.querySelector(".div-for-each-msg"); +const msgInChat = document.querySelector(".msg-text-in-chat-view"); +const usernameInChat = document.querySelector(".username-in-chat-view"); + +const refreshBtn = document.getElementById("refresh-btn"); +const sendBtn = document.getElementById("send-msg-btn"); +const pollBtn = document.getElementById("poll-btn"); +const testWsBtn = document.getElementById("test-ws"); + +const addMsgUsernameInput = document.getElementById("add-msg-username"); +const addMsgTextInput = document.getElementById("add-msg-text"); +const confirmToUser = document.getElementById("confirm-to-user"); + +const url = "http://localhost:3000"; + +//WS UPDATE +const socket = new WebSocket("ws://localhost:3000"); +// const url = "https://katchatapp.hosting.codeyourfuture.io"; + +socket.onmessage = ({ data }) => { + console.log("Message from server ", data); +}; + +testWsBtn.addEventListener("click", () => { + socket.send("Hello from client!"); +}); + +// generatequote and show to user +const seeAllMessages = async () => { + // const selectedQuote = pickFromArray(quotes); + const response = await fetch(url); + const allMessages = await response.json(); + + //this fixes the messages not showing on live sitwe + state.messages = allMessages; + // quote.innerHTML = selectedQuote.quote; + // author.innerHTML = selectedQuote.author; + // change greetingi am keeping everythign as is and jusy want to + // document.getElementById("greeting").innerHTML = "Your quote is:"; + // document.getElementById("greeting").style = "size: 1rem"; + + //WEBSOCKET this bit replaced by render() to show the newly liked and disliked + // chatFeedDiv.innerHTML = ""; + // allMessages.forEach((msg) => { + // chatFeedDiv.innerHTML += ` + //
+ //

${msg.msgText}

+ //

${msg.username}

+ //
`; + // }); + render(); +}; + +//show to user on click of new quote +refreshBtn.addEventListener("click", seeAllMessages); + +const sendMsg = async () => { + //removing whitespace or special characters + const addMsgText = addMsgTextInput.value + .trim() + .replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); + const addMsgUsername = addMsgUsernameInput.value + .trim() + .replace(/[^a-zA-Z0-9,.;:?! ]/g, "") + .toUpperCase(); + + //check if empty or too long + if (!addMsgText || !addMsgUsername) { + confirmToUser.innerHTML = "Please add message text and your username."; + return; + } + + if (addMsgText.length > 400 || addMsgUsername.length > 40) { + confirmToUser.innerHTML = + "Message must be up to 400 chars and username must be less than 40 chars."; + return; + } + + // so macthes backend (typeof body != "object" || !("quote" in body) || !("author" in body)) + const addingMsg = { + msgText: addMsgText, + username: addMsgUsername, + }; + + const responseFromAdd = await fetch(url, { + method: "POST", + headers: { + //so backedn can parde body as json + "Content-Type": "application/json", + }, + //turn obj into str typeof body != "object" + body: JSON.stringify(addingMsg), + }); + + if (responseFromAdd.ok === true) { + confirmToUser.innerHTML = "Your message has been sent."; + + addMsgTextInput.value = ""; + addMsgUsernameInput.value = ""; + } else { + const errorToShow = await responseFromAdd.text(); + confirmToUser.innerHTML = `${errorToShow} Please try again.`; + } +}; + +sendBtn.addEventListener("click", sendMsg); + +// mesasages from the back end +// const messages = [ +// { +// id: 1, +// username: "annonymous", +// msgText: "First chat message", +// timestamp: Date.now(), +// WS UPDATE +// likesCount: 0, +// dislikesCount: 0, +// }, +// ]; +// should become + +const state = { messages: [] }; // store fecthesd messages here + +// this is previosu render +// const render = () => { +// chatFeedDiv.innerHTML = ""; +// state.messages.forEach((msg) => { +// chatFeedDiv.innerHTML += ` +//
+//

${msg.msgText}

+//

${msg.username}

+//
`; +// }); +// }; + +//WS UPDATE render with added likes and dislikes and by id +//issue with render => not being hoisted so changed to normal function +function render() { + chatFeedDiv.innerHTML = ""; + state.messages.forEach((msg) => { + chatFeedDiv.innerHTML += ` +
+

${msg.msgText}

+

${msg.username}

+
+ + + +

Disliked ${msg.dislikesCount}

+
+
`; + }); + + // add event listener to like and dislike buttons + const likeBtn = document.querySelectorAll(".like-btn"); + const dislikeBtn = document.querySelectorAll(".dislike-btn"); + + // find all buttons so all are ijn array + likeBtn.forEach((btn, index) => { + btn.addEventListener("click", async () => { + // likesCount.forEach((like) => { + + // sfrom backedn - implement id and like it was in the adding msg + // const addingMsg = { + // msgText: addMsgText, + // username: addMsgUsername, + // }; + // here getting object + + // const responseFromAdd = await fetch(url, { + // method: "POST", + // headers: { + // //so backedn can parde body as json + // "Content-Type": "application/json", + // }, + // //turn obj into str typeof body != "object" + // body: JSON.stringify(addingMsg), + // }); + + //grab message by index + const message = state.messages[index]; + + const votingData = { + id: message.id, + vote: "like", + }; + + const responseFromVote = await fetch(`${url}/vote`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(votingData), + }); + + if (responseFromVote.ok === true) { + console.log("Vote sent successfully"); + } else { + console.log("Vote failed"); + } + }); + }); + + //dislieke + dislikeBtn.forEach((btn, index) => + btn.addEventListener("click", async (event) => { + console.log("Dislike button clicked"); + + const message = state.messages[index]; + + const votingData = { + id: message.id, + vote: "dislike", + }; + + const responseFromVote = await fetch(`${url}/vote`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(votingData), + }); + + if (responseFromVote.ok === true) { + console.log("Vote sent successfully"); + } else { + console.log("Vote failed"); + } + }) + ); +} + +//polling coursework +const keepFetchingMessages = async () => { + const lastMessageTime = + state.messages.length > 0 + ? state.messages[state.messages.length - 1].timestamp + : null; + const queryString = lastMessageTime ? `?since=${lastMessageTime}` : ""; + const urlQueryMod = `${url}/messages${queryString}`; + const rawResponse = await fetch(urlQueryMod); + const response = await rawResponse.json(); + state.messages.push(...response); + render(); + setTimeout(keepFetchingMessages, 100); +}; + +pollBtn.addEventListener("click", keepFetchingMessages); + +//test long poll +const longPollBtn = document.getElementById("long-poll-btn"); + +const testLongPoll = async () => { + const lastMessageTime = + state.messages.length > 0 + ? state.messages[state.messages.length - 1].timestamp + : null; + const queryString = lastMessageTime ? `?since=${lastMessageTime}` : ""; + const urlQueryMod = `${url}/long-poll${queryString}`; + const rawResponse = await fetch(urlQueryMod); + const response = await rawResponse.json(); + + //websocket but with duplicated message when liked + //check by id of each message if already rendered state + // so need to do a check before remove this: + // state.messages.push(...response); + + response.forEach((incomingMsgFromServer) => { + // inside old messages on screen msgAlreadyOnScreen + const isDuplicate = state.messages.some( + (msgAlreadyOnScreen) => msgAlreadyOnScreen.id === incomingMsgFromServer.id + ); + + if (!isDuplicate) { + // non duplicated add it to the list + // instead of all getting pushed after check + // state.messages.push(...response) + // or incomingMsgFromServer); + state.messages.push(...response); + // if duplicate - loop to increase count + } else { + // if on screen show new count shown + state.messages.forEach((msgOnScreen) => { + if (msgOnScreen.id === incomingMsgFromServer.id) { + //show new count + msgOnScreen.likesCount = incomingMsgFromServer.likesCount; + msgOnScreen.dislikesCount = incomingMsgFromServer.dislikesCount; + msgOnScreen.timestamp = incomingMsgFromServer.timestamp; + } + }); + } + }); + + render(); + testLongPoll(); +}; +longPollBtn.addEventListener("click", testLongPoll); + +//addiitonal privacy feature hide messages from screen +const hideMessages = document.getElementById("hide-btn"); + +hideMessages.addEventListener("click", () => { + if (chatFeedDiv.style.display === "none") { + chatFeedDiv.style.display = "block"; + hideMessages.textContent = "Hide chat"; + } else { + chatFeedDiv.style.display = "none"; + hideMessages.textContent = "Show chat"; + } +}); + +// seeAllMessages(); +// but with long poll +seeAllMessages().then(() => { + testLongPoll(); +}); diff --git a/sprint3 middleware/mw1.js b/sprint3 middleware/mw1.js new file mode 100644 index 0000000..683bda1 --- /dev/null +++ b/sprint3 middleware/mw1.js @@ -0,0 +1,105 @@ +// There must be an endpoint which handles POST requests. +// A middleware should look for a header with name X-Username. If this is set, it will modify req to add a username property set to this value. If it is not set, the property should be set to null. +// A middleware should parse the request POST body as a JSON array. It should modify req to add a body property to this value. If the POST body was not a JSON array, or the array contains non-string elements, it should reject the request. +// The response should look like: + +const express = require("express"); +// const cors = require("cors"); + +const app = express(); +app.use(express()); + +// app.use(cors()); +//helpers middleware functions +// A middleware should look for a header with name X-Username. +// If this is set, it will modify req to add a username property set to this value. +// If it is not set, the property should be set to null. + +// const requestTime = function (req, res, next) { +// req.requestTime = Date.now(); +// next(); +// }; +// const addUsernameProp = function (req, res, next) { +// const username = req.get("X-Username"); + +// if (username) { +// req.username = username; +// } else { +// req.username = null; +// } + +// next(); +// }; + +const addUsernameProp = function (req, res, next) { + const username = req.get("X-Username"); + + username ? (req.username = username) : (req.username = null); + + next(); +}; + +//to turn to obj +const getBody = function (req, res, next) { + let data = ""; + req.on("data", (chunk) => { + data += chunk; + }); + req.on("end", () => { + req.body = JSON.parse(data); + next(); + }); +}; +// A middleware should parse the request POST body as a JSON array. It should modify req to add a body property to this value. +// If the POST body was not a JSON array, or the array contains non-string elements, it should reject the request. +const parseBody = function (req, res, next) { + const body = req.body; + getBody(); + //check if is array and all strings typeof wrong for array + if (!Array.isArray(body)) { + return res.send("Not an array"); + } + + body.forEach((elementInArr) => { + if (typeof elementInArr !== "string") { + return res.status(400).send("Not all are strings"); + } + }); + next(); +}; + +app.use(addUsernameProp); +app.use(parseBody); + +// app.get("/", (req, res) => { +// let responseText = "Hello World!
"; +// responseText += `Requested at: ${req.requestTime}`; +// res.send(responseText); +// }); + +// You are authenticated as Gemma. + +// You have requested information about 0 subjects. + +app.post("/", (req, res) => { + let responseText = ""; + + if (req.username) { + responseText += `You are authenticated as ${req.username}.

`; + } else { + responseText += `You are not authenticated.

`; + } + + const subject = req.body; + const numberOfS = subject.length === 1 ? "subject" : "subjects"; + const showSubject = subject.join(", "); + const numOfSubj = subject.length; + responseText += `You have requested information about ${numOfSubj} ${numberOfS}: ${showSubject}.`; + + res.send(responseText); +}); + +const port = 3000; +app.listen(port, () => { + console.log(`Server running at http://localhost:${port}`); +}); diff --git a/sprint3 middleware/mw2.js b/sprint3 middleware/mw2.js new file mode 100644 index 0000000..6c1a834 --- /dev/null +++ b/sprint3 middleware/mw2.js @@ -0,0 +1,46 @@ +const express = require("express"); +const cors = require("cors"); + +const app = express(); +app.use(express.json()); +app.use(cors()); + +app.post("/", (req, res) => { + let responseText = ""; + + if (req.username) { + responseText += `You are authenticated as ${req.username}.

`; + } else { + responseText += `You are not authenticated.

`; + } + + const subject = req.body; + const numberOfS = subject.length === 1 ? "subject" : "subjects"; + const showSubject = subject.join(", "); + const numOfSubj = subject.length; + responseText += `You have requested information about ${numOfSubj} ${numberOfS}: ${showSubject}.`; + + res.send(responseText); +}); + +const port = 3000; +app.listen(port, () => { + console.log(`Server running at http://localhost:${port}`); +}); + +// Make a copy of your previous middleware application. + +// Delete the middleware you wrote that handles the JSON request POST body. + +// Switch to instead the JSON middleware built in to Express. +// app.use(express.json()); instead of parseBody + +// Debug why the curl command suggested in the previous exercise doesn’t work. Fix this problem by modifying the curl command. +// content-type application json not present +// -H "Content-Type: application/json" + +// curl -X POST \ +// -H "Content-Type: application/json" \ +// -H "X-Username: Ahmed" \ +// -d '["Birds", "Bats", "Lizards", "Bees"]' \ +// http://localhost:3000 diff --git a/sprint3 middleware/package-lock.json b/sprint3 middleware/package-lock.json new file mode 100644 index 0000000..15865a1 --- /dev/null +++ b/sprint3 middleware/package-lock.json @@ -0,0 +1,854 @@ +{ + "name": "sprint3-middleware", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sprint3-middleware", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "cors": "^2.8.6", + "express": "^5.2.1" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/sprint3 middleware/package.json b/sprint3 middleware/package.json new file mode 100644 index 0000000..5bff427 --- /dev/null +++ b/sprint3 middleware/package.json @@ -0,0 +1,16 @@ +{ + "name": "sprint3-middleware", + "version": "1.0.0", + "main": "m1.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "cors": "^2.8.6", + "express": "^5.2.1" + } +} From 1e34082002d8b0ce0a0e311115438e384e9ba719 Mon Sep 17 00:00:00 2001 From: katarzynakaz Date: Sun, 22 Feb 2026 10:03:15 +0000 Subject: [PATCH 2/7] completed requested fixes, removed comments, moved parseBody --- .DS_Store | Bin 10244 -> 10244 bytes sprint3 middleware/mw1.js | 48 +++++++------------------------------- sprint3 middleware/mw2.js | 13 ++++++++--- 3 files changed, 18 insertions(+), 43 deletions(-) diff --git a/.DS_Store b/.DS_Store index 97bdb83b513ba568f608be9a3a43081223c0e76c..8300c75b03453577741bd6ed8f6dcb877b00db8e 100644 GIT binary patch delta 67 zcmZn(XbG6$&&atkU^hP_=Vl%OLq1g@hGK>Sh9ZVchCGH624e;VhFl;!1qgE(QW?q_ T5`l85oAuz>% diff --git a/sprint3 middleware/mw1.js b/sprint3 middleware/mw1.js index 683bda1..d76740b 100644 --- a/sprint3 middleware/mw1.js +++ b/sprint3 middleware/mw1.js @@ -1,45 +1,23 @@ +// This is the problem: // There must be an endpoint which handles POST requests. // A middleware should look for a header with name X-Username. If this is set, it will modify req to add a username property set to this value. If it is not set, the property should be set to null. // A middleware should parse the request POST body as a JSON array. It should modify req to add a body property to this value. If the POST body was not a JSON array, or the array contains non-string elements, it should reject the request. // The response should look like: const express = require("express"); -// const cors = require("cors"); +const cors = require("cors"); const app = express(); -app.use(express()); - -// app.use(cors()); -//helpers middleware functions -// A middleware should look for a header with name X-Username. -// If this is set, it will modify req to add a username property set to this value. -// If it is not set, the property should be set to null. - -// const requestTime = function (req, res, next) { -// req.requestTime = Date.now(); -// next(); -// }; -// const addUsernameProp = function (req, res, next) { -// const username = req.get("X-Username"); - -// if (username) { -// req.username = username; -// } else { -// req.username = null; -// } - -// next(); -// }; +app.use(cors()); const addUsernameProp = function (req, res, next) { const username = req.get("X-Username"); - username ? (req.username = username) : (req.username = null); + req.username = username ?? null; next(); }; -//to turn to obj const getBody = function (req, res, next) { let data = ""; req.on("data", (chunk) => { @@ -50,12 +28,13 @@ const getBody = function (req, res, next) { next(); }); }; + +//Here is the specifications of what it should do: // A middleware should parse the request POST body as a JSON array. It should modify req to add a body property to this value. // If the POST body was not a JSON array, or the array contains non-string elements, it should reject the request. const parseBody = function (req, res, next) { const body = req.body; - getBody(); - //check if is array and all strings typeof wrong for array + if (!Array.isArray(body)) { return res.send("Not an array"); } @@ -69,19 +48,8 @@ const parseBody = function (req, res, next) { }; app.use(addUsernameProp); -app.use(parseBody); - -// app.get("/", (req, res) => { -// let responseText = "Hello World!
"; -// responseText += `Requested at: ${req.requestTime}`; -// res.send(responseText); -// }); - -// You are authenticated as Gemma. - -// You have requested information about 0 subjects. -app.post("/", (req, res) => { +app.post("/", getBody, parseBody, (req, res) => { let responseText = ""; if (req.username) { diff --git a/sprint3 middleware/mw2.js b/sprint3 middleware/mw2.js index 6c1a834..339216a 100644 --- a/sprint3 middleware/mw2.js +++ b/sprint3 middleware/mw2.js @@ -4,6 +4,15 @@ const cors = require("cors"); const app = express(); app.use(express.json()); app.use(cors()); +app.use(addUsernameProp); + +const addUsernameProp = function (req, res, next) { + const username = req.get("X-Username"); + + req.username = username ?? null; + + next(); +}; app.post("/", (req, res) => { let responseText = ""; @@ -28,9 +37,7 @@ app.listen(port, () => { console.log(`Server running at http://localhost:${port}`); }); -// Make a copy of your previous middleware application. - -// Delete the middleware you wrote that handles the JSON request POST body. +// Here is the problem: // Switch to instead the JSON middleware built in to Express. // app.use(express.json()); instead of parseBody From 61d267ac5bae4fcc0d9c19f4e388921110a98dcf Mon Sep 17 00:00:00 2001 From: katarzynakaz Date: Sun, 22 Feb 2026 15:32:47 +0000 Subject: [PATCH 3/7] branch cleaned up --- .DS_Store | Bin 10244 -> 0 bytes .gitignore | 3 +- .vscode/settings.json | 3 - README.md | 3 - backend/.gitignore | 1 - backend/package-lock.json | 1044 ----------------- backend/package.json | 7 - backend/server.mjs | 154 --- frontend/style.css | 121 -- .../{ => sprint3-custom-middleware}/mw1.js | 0 .../{ => sprint3-library-middleware}/mw2.js | 0 {frontend => sprint3 prep tasks}/chat.js | 0 {frontend => sprint3 prep tasks}/index.html | 0 .../serverWEBSCOKETNOTES.mjs | 0 .../websocket copy.mjs | 0 {frontend => sprint3 prep tasks}/websocket.js | 0 sprint3 prep tasks/websocket.mjs | 311 +++++ .../websocketexercises.md | 0 18 files changed, 313 insertions(+), 1334 deletions(-) delete mode 100644 .DS_Store delete mode 100644 .vscode/settings.json delete mode 100644 README.md delete mode 100644 backend/.gitignore delete mode 100644 backend/package-lock.json delete mode 100644 backend/package.json delete mode 100644 backend/server.mjs delete mode 100644 frontend/style.css rename sprint3 middleware/{ => sprint3-custom-middleware}/mw1.js (100%) rename sprint3 middleware/{ => sprint3-library-middleware}/mw2.js (100%) rename {frontend => sprint3 prep tasks}/chat.js (100%) rename {frontend => sprint3 prep tasks}/index.html (100%) rename {backend => sprint3 prep tasks}/serverWEBSCOKETNOTES.mjs (100%) rename backend/websocket.mjs => sprint3 prep tasks/websocket copy.mjs (100%) rename {frontend => sprint3 prep tasks}/websocket.js (100%) create mode 100644 sprint3 prep tasks/websocket.mjs rename {backend => sprint3 prep tasks}/websocketexercises.md (100%) diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 8300c75b03453577741bd6ed8f6dcb877b00db8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10244 zcmeHNU2GIp6uxKrH$w+HEnO%J?QU7M3Ka^pl?uqVTd@3=bX!^qvfbSoU}Sct?96V_ zimv*kg!mVYF>2zI5|szz5B@|=42nMZps2wZ6EPY!7)UfGMjt%)-dSvSOH2$Tl*}}9 z&)jqFxijbN`R3buw}cSr%Baf-2@*m?JgHPYcind6eZIbMn;#xUS$XF@U?HJxGV647)PNC3h#l-3hpN26u%5 z?C$6nWWx!Bx%65u16~G-GeFnw6(m98BuVy9T)$fhv>Z>28oKSYw0sFdNoiSmg;*h0 zinm4gSz}Qr?quw)czP%8^;?#a$X~}3+jVU$rqo6((~0Y*mSzsRs^O4bgSx3&V_jCp zRBh%PvWm*sNK)`(& zkeLO69WIW_u(OebL8B^Z%SZKfJ}PNOr99K$Kd^mpNRBBrS$zE7QAf8-d%KplQO1-i z8^hd?w9IX3joJs3mXS%Bc1)>G#*Jjgh&x)RVMO&iHPltv$+Tq{+bmnB)9e^>w1W<{ z6`6*HZ7c0W*knUG&d*0F8C&bqa8f<1b>q}_Z9#}29h)_GLF2WHmbP}R=~}<3_f(Zq zT_e}Za|W}zttSj^U`$t?eLLcIQZrTE+&heUHXVJBuBD}FXqNiTcuG4}qf}Sb&GY+* zG70@aMtAN|LZZm$YVZdova8kB`^77|MSHq3|Mp8G8Tr9J^w<424^`T0%0b zC(B7U*-S=Anv9bp{uci>%k z4?cyj;5#@2-@{q>70$tV_)91i%7k*EPG}Ss3L&9gxJd{L8-zY#P#EElF61FK%=`IP zh~rhkivl5q8+{Z~!m+6#6}Yu$!^Tb12U4eJQ4r0p_05?Zq{v#irt?Ax@`}wD>BQn^ z^*#wDtln8o6xJ$2ZT^CKIWT~L&<++tG!I>+f&x@LMMpO-lmki`LWXy*Yr<|h!i9Gi zHwOZWfGFkNmSq81DM1WM%(tx>yWn;@ZkA=G6w%0Sx71^oxSg&DPD8OJUaBfTP0o;? z$a(TNR{1QL4Rz22Ezl0Dv8K1eHi$w$3}9W4KmwAiwk@peG}v$#+>OAI06sB z!|(_^1y92>tj3SRi|`V>46nm+coW`&58woR1SjER_yj(OZ{P>`1%4~24>0{vYKb_}tIJOm7V57f=` z`6kmMWg5A`Hw7(Hi?6{~NQ=y&$v1%(nPw6#GS|tp$W(>2$dm=N$aHIcQ_><0A=2RU zO`=8S(BmtjMOQyHExN|bPm4d3bL3BaVU&XeGo}qKP6aBD81Y--^drXk^Za#keIP7} z3SkU79f|P`q{jbGj78O4K#euTCZkA>IwQvwBuG+tt>T}7R5^w#$u!Xld^qoV?Uj{* z68<1Q diff --git a/.gitignore b/.gitignore index c2658d7..28f1ba7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules/ +node_modules +.DS_Store \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 6f3a291..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "liveServer.settings.port": 5501 -} \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 2d6b50c..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Coursework - -Exercises to practice and solidify your understanding of the Decomposition module of the Software Development Course. diff --git a/backend/.gitignore b/backend/.gitignore deleted file mode 100644 index 3c3629e..0000000 --- a/backend/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/backend/package-lock.json b/backend/package-lock.json deleted file mode 100644 index e0129a3..0000000 --- a/backend/package-lock.json +++ /dev/null @@ -1,1044 +0,0 @@ -{ - "name": "backend", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "cors": "^2.8.6", - "express": "^5.2.1", - "websocket": "^1.0.35" - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/bufferutil": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", - "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/d": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", - "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", - "license": "ISC", - "dependencies": { - "es5-ext": "^0.10.64", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", - "hasInstallScript": true, - "license": "ISC", - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", - "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", - "license": "ISC", - "dependencies": { - "d": "^1.0.2", - "ext": "^1.7.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "license": "ISC", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "license": "ISC", - "dependencies": { - "type": "^2.7.2" - } - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "license": "MIT" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "license": "ISC" - }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "license": "MIT", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", - "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", - "license": "ISC" - }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "license": "MIT", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/websocket": { - "version": "1.0.35", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz", - "integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==", - "license": "Apache-2.0", - "dependencies": { - "bufferutil": "^4.0.1", - "debug": "^2.2.0", - "es5-ext": "^0.10.63", - "typedarray-to-buffer": "^3.1.5", - "utf-8-validate": "^5.0.2", - "yaeti": "^0.0.6" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/websocket/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/websocket/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/yaeti": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "license": "MIT", - "engines": { - "node": ">=0.10.32" - } - } - } -} diff --git a/backend/package.json b/backend/package.json deleted file mode 100644 index 6583ac1..0000000 --- a/backend/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "dependencies": { - "cors": "^2.8.6", - "express": "^5.2.1", - "websocket": "^1.0.35" - } -} diff --git a/backend/server.mjs b/backend/server.mjs deleted file mode 100644 index 1900795..0000000 --- a/backend/server.mjs +++ /dev/null @@ -1,154 +0,0 @@ -import express from "express"; -import cors from "cors"; -import { on } from "events"; -// import { text } from "body-parser"; issue with this so replaced with express - -const app = express(); -// const port = 3000; -// because server was not working -const port = process.env.PORT || 3000; -app.use(cors()); - -const messages = [ - { - id: 1, - username: "annonymous", - msgText: "First chat message", - timestamp: Date.now(), - }, -]; - -//now with added poling since last id -// from fe -// const keepFetchingMessages = async () => { -// const lastSeenId = messages.length > 0 ? messages[messages.length - 1].id : null; -// const queryString = lastSeenId ? `?since=${lastSeenId}` : ""; -// const server = url -// const urlofserv = `${server}messages${queryString}`; -// const rawResponse = await fetch(urlofserv); -// const response = await rawResponse.json(); -// messages.push(...response); -// // render(); -// seeAllMessages(); -// setTimeout(keepFetchingMessages, 100); -// } - -// }); -//get all messages -app.get("/", (req, res) => { - res.json(messages); -}); - -//get recent messages poll -app.get("/messages", (req, res) => { - //sincetimestapm Teach your backend how to answer “since when” queries. - const since = parseInt(req.query.since); - if (since) { - const onlyRecentMsgs = messages.filter((msg) => msg.timestamp > since); - res.json(onlyRecentMsgs); - return; - } - //keep if no since and show all - res.json(messages); -}); - -//messages with long polling only -const callbacksForNewMessages = []; -app.get("/long-poll", (req, res) => { - let messagesToSend = []; - - //since from messages get - const since = parseInt(req.query.since); - if (since) { - messagesToSend = messages.filter((msg) => msg.timestamp > since); - } - - //from coursework pasted - // Now, if 'since' was provided but no NEW messages were found, - // messagesToSend.length will be 0, and the server will WAIT. - if (messagesToSend.length === 0) { - callbacksForNewMessages.push((value) => res.send(value)); - } else { - res.send(messagesToSend); - } -}); - -//add msg to chat -app.post("/", (req, res) => { - const bodyBytes = []; - req.on("data", (chunk) => bodyBytes.push(...chunk)); - req.on("end", () => { - const bodyString = String.fromCharCode(...bodyBytes); - let body; - try { - body = JSON.parse(bodyString); - } catch (error) { - console.error(`Failed to parse body ${bodyString} as JSON: ${error}`); - res.status(400).send("Expected body to be JSON."); - return; - } - if ( - typeof body != "object" || - !("username" in body) || - !("msgText" in body) - ) { - console.error( - `Failed to extract username and message text from body: ${bodyString}` - ); - res - .status(400) - .send( - "Expected body to be a JSON object containing keys username and message text." - ); - return; - } - //here add the checks on backedn 400 - - body.msgText = body.msgText.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); - body.username = body.username.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); - - if (!body.msgText || !body.username) { - res.status(400).send("Please add a quote and an username."); - return; - } - - if (body.msgText.length > 400 || body.username.length > 40) { - res - .status(400) - .send( - "Message text must be up to 400 chars and username must be less than 40 chars." - ); - return; - } - - // to add new id to message - const newId = messages.length + 1; - - // messages.push({ - // id: newId, - // msgText: body.msgText, - // username: body.username, - // timestamp: Date.now(), - // }); - //newMessage obnj to send to long poll so instead of above - const newMessage = { - id: newId, - msgText: body.msgText, - username: body.username, - timestamp: Date.now(), - }; - - messages.push(newMessage); - - while (callbacksForNewMessages.length > 0) { - const callback = callbacksForNewMessages.pop(); - callback([newMessage]); - } - - res.send("ok"); - }); -}); - -app.listen(port, () => { - console.error(`Chat server listening on port ${port}`); -}); diff --git a/frontend/style.css b/frontend/style.css deleted file mode 100644 index 53322ae..0000000 --- a/frontend/style.css +++ /dev/null @@ -1,121 +0,0 @@ -@import url("https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500;700&display=swap"); - -html { - background-color: aliceblue; - display: flex; - justify-content: center; - align-items: center; - font-family: "Roboto Mono", monospace; -} - -.container { - background-color: #f9fffeaa; - padding: 1rem; - border-radius: 0.2rem; - border: solid 1px #adadad; - box-shadow: 0px 0px 1px #b1b1b8; - margin-top: 2rem; - max-width: 1000px; - min-width: 600px; -} - -.div-for-each-msg { - display: flex; - flex-direction: column; - border-bottom: solid 1px #adadad; -} - -.msg-in-chat-view { - font-size: 1rem; - color: #4a4a4a; - font-weight: 400; -} - -.username-in-chat-view { - font-size: 1rem; - color: #4a4a4a; - font-weight: 200; -} - -.timestamp { - font-size: 0.75rem; - color: #4a4a4a; - font-weight: 200; -} - -.add-message { - display: flex; - flex-direction: column; - gap: 0.75rem; - margin-top: 1rem; -} - -#greeting, -h2 { - font-size: 1.75rem; - color: #4a4a4a; - font-weight: 400; - border-bottom: solid 2px #adadad; -} - -h2 { - font-size: 1.25rem; -} - -button { - background-color: #6299f8; - color: rgb(34, 34, 34); - padding: 10px 20px; - border-radius: 0.2rem; - font-size: 1rem; - cursor: pointer; - font-weight: 500; - margin-top: 1rem; - font-family: "Roboto Mono", monospace; -} - -.secondary-btn { - background-color: white; - border: solid 1px #5f5f5f; - font-family: "Roboto Mono", monospace; -} - -button:hover { - background-color: #6299f844; - font-weight: 600; -} - -.input { - padding: 10px; - border-radius: 0.2rem; - border: solid 1px #75758d; - font-size: 1rem; -} - -#add-msg-text { - vertical-align: top; - min-height: 100px; - - line-height: 1.4; -} - -.likedislike-div { - display: flex; - flex-direction: row; - gap: 0.75rem; - margin-bottom: 1rem; -} - -.likes-count, -.dislikes-count, -.like-btn, -.dislike-btn { - font-size: 0.75rem; - color: #4a4a4a; - margin-top: 0.5rem; -} - -.like-btn, -.dislike-btn { - background-color: white; -} diff --git a/sprint3 middleware/mw1.js b/sprint3 middleware/sprint3-custom-middleware/mw1.js similarity index 100% rename from sprint3 middleware/mw1.js rename to sprint3 middleware/sprint3-custom-middleware/mw1.js diff --git a/sprint3 middleware/mw2.js b/sprint3 middleware/sprint3-library-middleware/mw2.js similarity index 100% rename from sprint3 middleware/mw2.js rename to sprint3 middleware/sprint3-library-middleware/mw2.js diff --git a/frontend/chat.js b/sprint3 prep tasks/chat.js similarity index 100% rename from frontend/chat.js rename to sprint3 prep tasks/chat.js diff --git a/frontend/index.html b/sprint3 prep tasks/index.html similarity index 100% rename from frontend/index.html rename to sprint3 prep tasks/index.html diff --git a/backend/serverWEBSCOKETNOTES.mjs b/sprint3 prep tasks/serverWEBSCOKETNOTES.mjs similarity index 100% rename from backend/serverWEBSCOKETNOTES.mjs rename to sprint3 prep tasks/serverWEBSCOKETNOTES.mjs diff --git a/backend/websocket.mjs b/sprint3 prep tasks/websocket copy.mjs similarity index 100% rename from backend/websocket.mjs rename to sprint3 prep tasks/websocket copy.mjs diff --git a/frontend/websocket.js b/sprint3 prep tasks/websocket.js similarity index 100% rename from frontend/websocket.js rename to sprint3 prep tasks/websocket.js diff --git a/sprint3 prep tasks/websocket.mjs b/sprint3 prep tasks/websocket.mjs new file mode 100644 index 0000000..24953a0 --- /dev/null +++ b/sprint3 prep tasks/websocket.mjs @@ -0,0 +1,311 @@ +//imports +import express from "express"; +import cors from "cors"; + +const app = express(); + +// following coursework +import { server as WebSocketServer } from "websocket"; + +//ReferenceError: http is not defined +import http from "http"; + +const server = http.createServer(app); +const webSocketServer = new WebSocketServer({ httpServer: server }); + +const port = 3000; +app.use(cors()); + +// npm install websocket +//following this + +//when client wants to connect to webscoket server sends a request object +webSocketServer.on("request", (request) => { + //call accept to open connection + //accept client connection and gives connection consts to talk to client + const connection = request.accept(null, request.origin); + + //sendUTF to send text + //sends msg immediately from server when connected + connection.sendUTF("Hello from server"); + + //this is a listener for messages from client + connection.on("message", (message) => { + console.log("Msg from client", message.utf8Data); + }); +}); + +// import express from "express"; +// import cors from "cors"; +// import { on } from "events"; +// import { text } from "body-parser"; issue with this so replaced with express + +// const app = express(); +// const port = 3000; +// because server was not working +// const port = process.env.PORT || 3000; +// app.use(cors()); + +const messages = [ + { + id: 1, + username: "Kaska", + msgText: "Hey people, how are you doing?", + timestamp: Date.now(), + likesCount: 0, + dislikesCount: 0, + }, +]; + +//now with added poling since last id +// from fe +// const keepFetchingMessages = async () => { +// const lastSeenId = messages.length > 0 ? messages[messages.length - 1].id : null; +// const queryString = lastSeenId ? `?since=${lastSeenId}` : ""; +// const server = url +// const urlofserv = `${server}messages${queryString}`; +// const rawResponse = await fetch(urlofserv); +// const response = await rawResponse.json(); +// messages.push(...response); +// // render(); +// seeAllMessages(); +// setTimeout(keepFetchingMessages, 100); +// } + +// }); +//get all messages +app.get("/", (req, res) => { + res.json(messages); +}); + +//get recent messages poll +app.get("/messages", (req, res) => { + //sincetimestapm Teach your backend how to answer “since when” queries. + const since = parseInt(req.query.since); + if (since) { + const onlyRecentMsgs = messages.filter((msg) => msg.timestamp > since); + res.json(onlyRecentMsgs); + return; + } + //keep if no since and show all + res.json(messages); +}); + +//messages with long polling only +const callbacksForNewMessages = []; +app.get("/long-poll", (req, res) => { + let messagesToSend = []; + + //since from messages get + const since = parseInt(req.query.since); + if (since) { + messagesToSend = messages.filter((msg) => msg.timestamp > since); + } + + //from coursework pasted + // Now, if 'since' was provided but no NEW messages were found, + // messagesToSend.length will be 0, and the server will WAIT. + if (messagesToSend.length === 0) { + callbacksForNewMessages.push((value) => res.send(value)); + } else { + res.send(messagesToSend); + } +}); + +//add msg to chat +app.post("/", (req, res) => { + const bodyBytes = []; + req.on("data", (chunk) => bodyBytes.push(...chunk)); + req.on("end", () => { + const bodyString = String.fromCharCode(...bodyBytes); + let body; + try { + body = JSON.parse(bodyString); + } catch (error) { + console.error(`Failed to parse body ${bodyString} as JSON: ${error}`); + res.status(400).send("Expected body to be JSON."); + return; + } + if ( + typeof body != "object" || + !("username" in body) || + !("msgText" in body) + ) { + console.error( + `Failed to extract username and message text from body: ${bodyString}` + ); + res + .status(400) + .send( + "Expected body to be a JSON object containing keys username and message text." + ); + return; + } + //here add the checks on backedn 400 + + body.msgText = body.msgText.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); + body.username = body.username.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); + + if (!body.msgText || !body.username) { + res.status(400).send("Please add a quote and an username."); + return; + } + + if (body.msgText.length > 400 || body.username.length > 40) { + res + .status(400) + .send( + "Message text must be up to 400 chars and username must be less than 40 chars." + ); + return; + } + + // to add new id to message + const newId = messages.length + 1; + + const newMessage = { + id: newId, + msgText: body.msgText, + username: body.username, + timestamp: Date.now(), + //updated initialised + likesCount: 0, + dislikesCount: 0, + }; + + messages.push(newMessage); + + while (callbacksForNewMessages.length > 0) { + const callback = callbacksForNewMessages.pop(); + callback([newMessage]); + } + + res.send("ok"); + }); +}); + +//add liking disliking route +app.post("/vote", (req, res) => { + const bodyBytes = []; + req.on("data", (chunk) => bodyBytes.push(...chunk)); + req.on("end", () => { + const bodyString = String.fromCharCode(...bodyBytes); + let body; + try { + body = JSON.parse(bodyString); + } catch (error) { + console.error(`Failed to parse body ${bodyString} as JSON: ${error}`); + res.status(400).send("Expected body to be JSON."); + return; + } + if ( + typeof body != "object" || + // !("username" in body) || + // !("msgText" in body) + //getting by id and vote type + !("id" in body) || + !("vote" in body) + ) { + console.error( + // `Failed to extract username and message text from body: ${bodyString}` + `Failed to extract id and vote type.` + ); + res + .status(400) + .send( + "Expected body to be a JSON object containing keys uis and vote type." + ); + return; + } + //this part is post new message only + + // body.msgText = body.msgText.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); + // body.username = body.username.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); + + // if (!body.msgText || !body.username) { + // res.status(400).send("Please add a quote and an username."); + // return; + // } + + // if (body.msgText.length > 400 || body.username.length > 40) { + // res + // .status(400) + // .send( + // "Message text must be up to 400 chars and username must be less than 40 chars." + // ); + // return; + // } + + // // to add new id to message + // const newId = messages.length + 1; + + // //add likes and dislikes + + // const newMessage = { + // id: newId, + // msgText: body.msgText, + // username: body.username, + // timestamp: Date.now(), + // //updated initialised + // likesCount: 0, + // dislikesCount: 0, + // }; + + // messages.push(newMessage); + + // while (callbacksForNewMessages.length > 0) { + // const callback = callbacksForNewMessages.pop(); + // callback([newMessage]); + // } + + //grab currently liked disliked message + + //add like and dislike + const likeOrDislike = () => { + for (const message of messages) { + if (message.id === body.id) { + return message; + } + // } return "No message with this id"; bug wrong because returns string and 404 doesnt show + } + return null; + }; + const currentyLikedDislikedMsg = likeOrDislike(); + + if (!currentyLikedDislikedMsg) { + res.status(404).send("No message with this id."); + return; + } + + if (body.vote === "like") { + currentyLikedDislikedMsg.likesCount += 1; + currentyLikedDislikedMsg.timestamp = Date.now(); + } else if (body.vote === "dislike") { + currentyLikedDislikedMsg.dislikesCount += 1; + currentyLikedDislikedMsg.timestamp = Date.now(); + } else { + //here to add invalid vote type + res.status(400).send("Invalid vote type."); + return; + } + + //copy from the other post to update + while (callbacksForNewMessages.length > 0) { + const callback = callbacksForNewMessages.pop(); + // callback([newMessage]); + callback([currentyLikedDislikedMsg]); + } + + // res.send("ok"); // this is wrong and message instead + res.json(currentyLikedDislikedMsg); + }); +}); + +// this was not working with websocket + +// app.listen(port, () => { +// console.error(`Chat server listening on port ${port}`); +// }); +server.listen(port, () => { + console.log(`Server running at http://localhost:${port}`); +}); diff --git a/backend/websocketexercises.md b/sprint3 prep tasks/websocketexercises.md similarity index 100% rename from backend/websocketexercises.md rename to sprint3 prep tasks/websocketexercises.md From 32f1b52118ba623690bbccddde5abfbb17b98a13 Mon Sep 17 00:00:00 2001 From: katarzynakaz Date: Sun, 22 Feb 2026 16:17:30 +0000 Subject: [PATCH 4/7] sprint 3 task updated, tree changed and changes in serverWEBSOCKETNOTES.mjs implemented --- sprint3 middleware/package-lock.json | 854 ------------------ sprint3 middleware/package.json | 16 - sprint3 prep tasks/serverWEBSCOKETNOTES.mjs | 164 +--- .../mw2.js | 0 .../mw1.js | 0 5 files changed, 20 insertions(+), 1014 deletions(-) delete mode 100644 sprint3 middleware/package-lock.json delete mode 100644 sprint3 middleware/package.json rename {sprint3 middleware/sprint3-library-middleware => sprint3-cots-middleware}/mw2.js (100%) rename {sprint3 middleware/sprint3-custom-middleware => sprint3-custom-middleware}/mw1.js (100%) diff --git a/sprint3 middleware/package-lock.json b/sprint3 middleware/package-lock.json deleted file mode 100644 index 15865a1..0000000 --- a/sprint3 middleware/package-lock.json +++ /dev/null @@ -1,854 +0,0 @@ -{ - "name": "sprint3-middleware", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "sprint3-middleware", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "cors": "^2.8.6", - "express": "^5.2.1" - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - } - } -} diff --git a/sprint3 middleware/package.json b/sprint3 middleware/package.json deleted file mode 100644 index 5bff427..0000000 --- a/sprint3 middleware/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "sprint3-middleware", - "version": "1.0.0", - "main": "m1.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "license": "ISC", - "description": "", - "dependencies": { - "cors": "^2.8.6", - "express": "^5.2.1" - } -} diff --git a/sprint3 prep tasks/serverWEBSCOKETNOTES.mjs b/sprint3 prep tasks/serverWEBSCOKETNOTES.mjs index 64541e6..1c83832 100644 --- a/sprint3 prep tasks/serverWEBSCOKETNOTES.mjs +++ b/sprint3 prep tasks/serverWEBSCOKETNOTES.mjs @@ -1,13 +1,11 @@ import express from "express"; import cors from "cors"; import { on } from "events"; -// import { text } from "body-parser"; issue with this so replaced with express import { server as WebSocketServer } from "websocket"; const app = express(); -// const port = 3000; -// because server was not working const port = process.env.PORT || 3000; + app.use(cors()); const server = http.createServer(app); @@ -22,54 +20,36 @@ const messages = [ }, ]; -//now with added poling since last id -// from fe -// const keepFetchingMessages = async () => { -// const lastSeenId = messages.length > 0 ? messages[messages.length - 1].id : null; -// const queryString = lastSeenId ? `?since=${lastSeenId}` : ""; -// const server = url -// const urlofserv = `${server}messages${queryString}`; -// const rawResponse = await fetch(urlofserv); -// const response = await rawResponse.json(); -// messages.push(...response); -// // render(); -// seeAllMessages(); -// setTimeout(keepFetchingMessages, 100); -// } +const getRecentMessages = (since) => { + return messages.filter((msg) => msg.timestamp > since); +}; -// }); -//get all messages app.get("/", (req, res) => { res.json(messages); }); -//get recent messages poll +//get recent messages polling app.get("/messages", (req, res) => { - //sincetimestapm Teach your backend how to answer “since when” queries. + //This is the problem: + // sincetimestapm Teach your backend how to answer “since when” queries. const since = parseInt(req.query.since); if (since) { - const onlyRecentMsgs = messages.filter((msg) => msg.timestamp > since); - res.json(onlyRecentMsgs); + res.json(getRecentMessages(since)); return; } - //keep if no since and show all res.json(messages); }); -//messages with long polling only +//messages with long polling const callbacksForNewMessages = []; app.get("/long-poll", (req, res) => { + const since = parseInt(req.query.since); let messagesToSend = []; - //since from messages get - const since = parseInt(req.query.since); if (since) { - messagesToSend = messages.filter((msg) => msg.timestamp > since); + messagesToSend = getRecentMessages(since); } - //from coursework pasted - // Now, if 'since' was provided but no NEW messages were found, - // messagesToSend.length will be 0, and the server will WAIT. if (messagesToSend.length === 0) { callbacksForNewMessages.push((value) => res.send(value)); } else { @@ -106,7 +86,6 @@ app.post("/", (req, res) => { ); return; } - //here add the checks on backedn 400 body.msgText = body.msgText.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); body.username = body.username.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); @@ -116,7 +95,7 @@ app.post("/", (req, res) => { return; } - if (body.msgText.length > 400 || body.username.length > 40) { + if (body.msgText.length > 400 || body.username.length >= 40) { res .status(400) .send( @@ -125,16 +104,8 @@ app.post("/", (req, res) => { return; } - // to add new id to message const newId = messages.length + 1; - // messages.push({ - // id: newId, - // msgText: body.msgText, - // username: body.username, - // timestamp: Date.now(), - // }); - //newMessage obnj to send to long poll so instead of above const newMessage = { id: newId, msgText: body.msgText, @@ -157,8 +128,6 @@ app.listen(port, () => { console.error(`Chat server listening on port ${port}`); }); - -//websocket instance websocket.addEventListener("open", () => { log("CONNECTED"); pingInterval = setInterval(() => { @@ -167,111 +136,18 @@ websocket.addEventListener("open", () => { }, 1000); }); -//send msg t server -websocket.addEventListener("open", () => { - log("CONNECTED"); - pingInterval = setInterval(() => { - log(`SENT: ping: ${counter}`); - websocket.send("ping"); - }, 1000); -}); - -//receive msg from server websocket.addEventListener("message", (e) => { - log(`RECEIVED: ${e.data}: ${counter}`); - counter++; + log(`RECEIVED: ${e.data}: ${counter}`); + counter++; }); -//server send s json websocket.addEventListener("message", (e) => { - const message = JSON.parse(e.data); - log(`RECEIVED: ${message.iteration}: ${message.content}`); - counter++; +websocket.addEventListener("message", (e) => { + const message = JSON.parse(e.data); + log(`RECEIVED: ${message.iteration}: ${message.content}`); + counter++; }); websocket.addEventListener("close", () => { - log("DISCONNECTED"); - clearInterval(pingInterval); -}); - - - - - -// notes from websoket -//on server js we do this: -// this is for ws package not npm notes - -// const WebSocket = require("ws"); -// const server = new WebSocket.Server({ port: 8080 }); - -//first event is connection from client -// server.on('connection') - -//when connection made access to web s object and callback -//this is ws library only again, api is diffferent - -// server.on("connection", (socket) => { //ws automatically accepts a connection - -// // we can listen to incoming messages - text and handle in callback -// socket.on("message", (message) => { -// //also send message back to clinet -// socket.send(`Message received: ${message}`); -// }); -// }); - -//using websocket package it is: -//receives a request object -// webSocketServer.on("request", (request) => { - -// //call accept to open connection -// const connection = request.accept(null, request.origin); - -// //sendUTF to send text -// connection.sendUTF("Hello"); - -// connection.on("message", (message) => { -// console.log("Received:", message.utf8Data); -// }); -// }); - -// following coursework -import { server as WebSocketServer } from "websocket"; -const server = http.createServer(app); -const webSocketServer = new WebSocketServer({ httpServer: server }); - -// npm install websocket -//following this - -//when client wants to connect to webscoket server sends a request object -webSocketServer.on("request", (request) => { - //call accept to open connection - //accept client connection and gives connection consts to talk to client - const connection = request.accept(null, request.origin); - - //sendUTF to send text - //sends msg immediately from server when connected - connection.sendUTF("Hello from server"); - - //this is a listener for messages from client - connection.on("message", (message) => { - console.log("Msg from client", message.utf8Data); - }); + log("DISCONNECTED"); + clearInterval(pingInterval); }); - - - -//on client side code we have a built in websocket class built in -// instantiate with url that points to server -const socket = new WebSocket("ws://localhost:8080"); - -//trigger handshake to open connection -// and we listen to it as event -socket.onmessage = ({ data }) => { - console.log("Message from server ", data); -}; - -document.querySelector(".send-btn").addEventListener("click", () => { - socket.send("Hello from client!"); -}); - -//full duplex connection diff --git a/sprint3 middleware/sprint3-library-middleware/mw2.js b/sprint3-cots-middleware/mw2.js similarity index 100% rename from sprint3 middleware/sprint3-library-middleware/mw2.js rename to sprint3-cots-middleware/mw2.js diff --git a/sprint3 middleware/sprint3-custom-middleware/mw1.js b/sprint3-custom-middleware/mw1.js similarity index 100% rename from sprint3 middleware/sprint3-custom-middleware/mw1.js rename to sprint3-custom-middleware/mw1.js From 01785a2bc864bd18cb60a7fcc041f22c513aa5ae Mon Sep 17 00:00:00 2001 From: katarzynakaz Date: Sun, 22 Feb 2026 16:35:47 +0000 Subject: [PATCH 5/7] comments removed --- ...COKETNOTES.mjs => serverWEBSCOKETNOTES.md} | 1 + sprint3 prep tasks/websocket.mjs | 121 ++---------------- 2 files changed, 12 insertions(+), 110 deletions(-) rename sprint3 prep tasks/{serverWEBSCOKETNOTES.mjs => serverWEBSCOKETNOTES.md} (99%) diff --git a/sprint3 prep tasks/serverWEBSCOKETNOTES.mjs b/sprint3 prep tasks/serverWEBSCOKETNOTES.md similarity index 99% rename from sprint3 prep tasks/serverWEBSCOKETNOTES.mjs rename to sprint3 prep tasks/serverWEBSCOKETNOTES.md index 1c83832..d9ff4e6 100644 --- a/sprint3 prep tasks/serverWEBSCOKETNOTES.mjs +++ b/sprint3 prep tasks/serverWEBSCOKETNOTES.md @@ -1,6 +1,7 @@ import express from "express"; import cors from "cors"; import { on } from "events"; +import http from "http"; import { server as WebSocketServer } from "websocket"; const app = express(); diff --git a/sprint3 prep tasks/websocket.mjs b/sprint3 prep tasks/websocket.mjs index 24953a0..b9fbffc 100644 --- a/sprint3 prep tasks/websocket.mjs +++ b/sprint3 prep tasks/websocket.mjs @@ -16,36 +16,16 @@ const webSocketServer = new WebSocketServer({ httpServer: server }); const port = 3000; app.use(cors()); -// npm install websocket -//following this - -//when client wants to connect to webscoket server sends a request object webSocketServer.on("request", (request) => { - //call accept to open connection - //accept client connection and gives connection consts to talk to client const connection = request.accept(null, request.origin); - //sendUTF to send text - //sends msg immediately from server when connected connection.sendUTF("Hello from server"); - //this is a listener for messages from client connection.on("message", (message) => { console.log("Msg from client", message.utf8Data); }); }); -// import express from "express"; -// import cors from "cors"; -// import { on } from "events"; -// import { text } from "body-parser"; issue with this so replaced with express - -// const app = express(); -// const port = 3000; -// because server was not working -// const port = process.env.PORT || 3000; -// app.use(cors()); - const messages = [ { id: 1, @@ -57,22 +37,10 @@ const messages = [ }, ]; -//now with added poling since last id -// from fe -// const keepFetchingMessages = async () => { -// const lastSeenId = messages.length > 0 ? messages[messages.length - 1].id : null; -// const queryString = lastSeenId ? `?since=${lastSeenId}` : ""; -// const server = url -// const urlofserv = `${server}messages${queryString}`; -// const rawResponse = await fetch(urlofserv); -// const response = await rawResponse.json(); -// messages.push(...response); -// // render(); -// seeAllMessages(); -// setTimeout(keepFetchingMessages, 100); -// } - -// }); +const getRecentMessages = (sinceTime) => { + return messages.filter((msg) => msg.timestamp > sinceTime); +}; + //get all messages app.get("/", (req, res) => { res.json(messages); @@ -80,14 +48,11 @@ app.get("/", (req, res) => { //get recent messages poll app.get("/messages", (req, res) => { - //sincetimestapm Teach your backend how to answer “since when” queries. - const since = parseInt(req.query.since); if (since) { - const onlyRecentMsgs = messages.filter((msg) => msg.timestamp > since); + const onlyRecentMsgs = getRecentMessages(since); res.json(onlyRecentMsgs); return; } - //keep if no since and show all res.json(messages); }); @@ -96,15 +61,11 @@ const callbacksForNewMessages = []; app.get("/long-poll", (req, res) => { let messagesToSend = []; - //since from messages get const since = parseInt(req.query.since); if (since) { - messagesToSend = messages.filter((msg) => msg.timestamp > since); + messagesToSend = getRecentMessages(since); } - //from coursework pasted - // Now, if 'since' was provided but no NEW messages were found, - // messagesToSend.length will be 0, and the server will WAIT. if (messagesToSend.length === 0) { callbacksForNewMessages.push((value) => res.send(value)); } else { @@ -112,7 +73,6 @@ app.get("/long-poll", (req, res) => { } }); -//add msg to chat app.post("/", (req, res) => { const bodyBytes = []; req.on("data", (chunk) => bodyBytes.push(...chunk)); @@ -141,7 +101,6 @@ app.post("/", (req, res) => { ); return; } - //here add the checks on backedn 400 body.msgText = body.msgText.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); body.username = body.username.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); @@ -151,7 +110,7 @@ app.post("/", (req, res) => { return; } - if (body.msgText.length > 400 || body.username.length > 40) { + if (body.msgText.length > 400 || body.username.length >= 40) { res .status(400) .send( @@ -198,18 +157,8 @@ app.post("/vote", (req, res) => { res.status(400).send("Expected body to be JSON."); return; } - if ( - typeof body != "object" || - // !("username" in body) || - // !("msgText" in body) - //getting by id and vote type - !("id" in body) || - !("vote" in body) - ) { - console.error( - // `Failed to extract username and message text from body: ${bodyString}` - `Failed to extract id and vote type.` - ); + if (typeof body != "object" || !("id" in body) || !("vote" in body)) { + console.error(`Failed to extract id and vote type.`); res .status(400) .send( @@ -217,48 +166,6 @@ app.post("/vote", (req, res) => { ); return; } - //this part is post new message only - - // body.msgText = body.msgText.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); - // body.username = body.username.trim().replace(/[^a-zA-Z0-9,.;:?! ]/g, ""); - - // if (!body.msgText || !body.username) { - // res.status(400).send("Please add a quote and an username."); - // return; - // } - - // if (body.msgText.length > 400 || body.username.length > 40) { - // res - // .status(400) - // .send( - // "Message text must be up to 400 chars and username must be less than 40 chars." - // ); - // return; - // } - - // // to add new id to message - // const newId = messages.length + 1; - - // //add likes and dislikes - - // const newMessage = { - // id: newId, - // msgText: body.msgText, - // username: body.username, - // timestamp: Date.now(), - // //updated initialised - // likesCount: 0, - // dislikesCount: 0, - // }; - - // messages.push(newMessage); - - // while (callbacksForNewMessages.length > 0) { - // const callback = callbacksForNewMessages.pop(); - // callback([newMessage]); - // } - - //grab currently liked disliked message //add like and dislike const likeOrDislike = () => { @@ -266,7 +173,6 @@ app.post("/vote", (req, res) => { if (message.id === body.id) { return message; } - // } return "No message with this id"; bug wrong because returns string and 404 doesnt show } return null; }; @@ -289,23 +195,18 @@ app.post("/vote", (req, res) => { return; } - //copy from the other post to update while (callbacksForNewMessages.length > 0) { const callback = callbacksForNewMessages.pop(); - // callback([newMessage]); callback([currentyLikedDislikedMsg]); } - // res.send("ok"); // this is wrong and message instead res.json(currentyLikedDislikedMsg); }); }); -// this was not working with websocket - +// this was not working with websocket so the first line is changed to server listening // app.listen(port, () => { -// console.error(`Chat server listening on port ${port}`); -// }); + server.listen(port, () => { console.log(`Server running at http://localhost:${port}`); }); From b03bb13a09f2a210ec1745e71318df2bcf57e5e5 Mon Sep 17 00:00:00 2001 From: katarzynakaz Date: Mon, 23 Feb 2026 11:36:57 +0000 Subject: [PATCH 6/7] global id counter, get recent messages updated with since, routes cleaned, innerhtml, moved listener to div --- sprint3 prep tasks/index.html | 4 +- sprint3 prep tasks/serverWEBSCOKETNOTES.md | 154 ---------- sprint3 prep tasks/websocket copy.mjs | 311 --------------------- sprint3 prep tasks/websocket.js | 181 ++---------- sprint3 prep tasks/websocket.mjs | 24 +- 5 files changed, 42 insertions(+), 632 deletions(-) delete mode 100644 sprint3 prep tasks/serverWEBSCOKETNOTES.md delete mode 100644 sprint3 prep tasks/websocket copy.mjs diff --git a/sprint3 prep tasks/index.html b/sprint3 prep tasks/index.html index 2e10855..236d0eb 100644 --- a/sprint3 prep tasks/index.html +++ b/sprint3 prep tasks/index.html @@ -18,8 +18,8 @@

Chat feed

- + - - +
diff --git a/sprint3 prep tasks/websocket.js b/sprint3 prep tasks/websocket.js index 421ebca..f846a2b 100644 --- a/sprint3 prep tasks/websocket.js +++ b/sprint3 prep tasks/websocket.js @@ -108,17 +108,30 @@ chatFeedDiv.addEventListener("click", async (event) => { function render() { chatFeedDiv.innerHTML = ""; state.messages.forEach((msg) => { - chatFeedDiv.innerHTML += ` -
-

${msg.msgText}

-

${msg.username}

-
- - - -

Disliked ${msg.dislikesCount}

-
-
`; + const messageDiv = document.createElement("div"); + messageDiv.className = "div-for-each-msg"; + messageDiv.dataset.id = msg.id; + + const msgText = document.createElement("p"); + msgText.className = "msg-in-chat-view"; + msgText.textContent = msg.msgText; + + const username = document.createElement("p"); + username.className = "username-in-chat-view"; + username.textContent = msg.username; + + const buttonDiv = document.createElement("div"); + buttonDiv.className = "likedislike-div"; + + buttonDiv.innerHTML = ` + + + +

Disliked ${msg.dislikesCount}

+ `; + + messageDiv.append(msgText, username, buttonDiv); + chatFeedDiv.appendChild(messageDiv); }); }