From b0a3edb8f72873d950812eeb2bc71a7bd52ff37e Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Thu, 20 May 2021 10:24:45 +0100 Subject: [PATCH 001/113] EUI-2976 Introduced a basic mechanism for accepting "socket" connections via socket.io and responding to various messages from the client. The code is messy and all inline at the moment but it needs to get into the repo sooner rather than later. --- .gitignore | 2 + app.js | 7 +-- package.json | 9 +-- server.js | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++ yarn.lock | 131 +++++++++++++++++++++++++++++++++++++-- 5 files changed, 302 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 4d2b608f..969e69a5 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,5 @@ out/ bin/ build/ +config/local-development.yaml +.env diff --git a/app.js b/app.js index 3d2182bf..26a0d565 100644 --- a/app.js +++ b/app.js @@ -1,7 +1,6 @@ const healthcheck = require('@hmcts/nodejs-healthcheck'); const express = require('express'); const logger = require('morgan'); -const bodyParser = require('body-parser'); const config = require('config'); const debug = require('debug')('ccd-case-activity-api:app'); const enableAppInsights = require('./app/app-insights/app-insights'); @@ -43,9 +42,9 @@ if (config.util.getEnv('NODE_ENV') === 'test') { debug(`starting application with environment: ${config.util.getEnv('NODE_ENV')}`); app.use(corsHandler); -app.use(bodyParser.json()); -app.use(bodyParser.urlencoded({ extended: false })); -app.use(bodyParser.text()); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); +app.use(express.text()); app.use(authCheckerUserOnlyFilter); app.use('/', activity); diff --git a/package.json b/package.json index c493a725..6a1d9954 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,6 @@ "name": "ccd-case-activity-api", "version": "0.0.2", "private": true, - "engines": { - "node": "^12.14.1", - "yarn": "^1.12.3" - }, "scripts": { "setup": "cross-env NODE_PATH=. node --version", "start": "cross-env NODE_PATH=. node server.js", @@ -40,7 +36,6 @@ "@hmcts/nodejs-logging": "^3.0.1", "@hmcts/properties-volume": "^0.0.9", "applicationinsights": "^1.0.5", - "body-parser": "^1.18.2", "config": "^1.26.1", "connect-timeout": "^1.9.0", "cross-env": "^5.2.0", @@ -54,7 +49,9 @@ "nocache": "^2.1.0", "node-cache": "^5.1.0", "node-cron": "^1.2.1", - "node-fetch": "^2.6.1" + "node-fetch": "^2.6.1", + "socket.io": "^4.1.2", + "socket.io-router-middleware": "^1.1.2" }, "devDependencies": { "chai": "^4.1.2", diff --git a/server.js b/server.js index 22c822ff..d80bb9a1 100755 --- a/server.js +++ b/server.js @@ -24,6 +24,174 @@ app.set('port', port); var server = http.createServer(app); +/** + * Set up a socket.io router. + */ +const io = require('socket.io')(server, { + allowEIO3: true +}); +const IORouter = new require('socket.io-router-middleware'); +const iorouter = new IORouter(); + +// const socketsWatchingCases = {}; +const caseStatuses = {}; + +// Add router paths +iorouter.on('/socket', (socket, ctx, next) => { + const payload = ctx.request.payload; + if (payload) { + if (payload.edit) { + handleEdit(socket, payload); + } else if (payload.view) { + handleView(socket, payload); + } else if (payload.watch) { + handleWatch(socket, payload.watch); + } + } + // ctx.response = { hello: 'from server' }; + // socket.emit('response', ctx); + // Don't forget to call next() at the end to enable passing to other middlewares + next(); +}); +// On client connection attach the router +io.on('connection', function (socket) { + socket.use((packet, next) => { + // Call router.attach() with the client socket as the first parameter + iorouter.attach(socket, packet, next); + }); +}); + +function handleEdit(socket, payload) { + const caseId = payload.edit; + const stoppedViewing = stopViewingCases(socket.id); + if (stoppedViewing.length > 0) { + stoppedViewing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); + } + const stoppedEditing = stopEditingCases(socket.id, caseId); + if (stoppedEditing.length > 0) { + stoppedEditing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); + } + watchCase(socket, caseId); + const caseStatus = caseStatuses[caseId] || { viewers: [], editors: [] }; + caseStatuses[caseId] = caseStatus; + const matchingEditor = caseStatus.editors.find(e => e.id === payload.user.id); + const notify = stoppedViewing.concat(stoppedEditing); + if (!matchingEditor) { + caseStatus.editors.push({ ...payload.user, socketId: socket.id }); + notify.push(caseId); + } + if (notify.length > 0) { + notifyWatchers(socket, [ ...new Set(notify) ]); + } + socket.emit('response', getCaseStatuses([caseId])); +} + +function handleView(socket, payload) { + const caseId = payload.view; + const stoppedViewing = stopViewingCases(socket.id, caseId); + if (stoppedViewing.length > 0) { + stoppedViewing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); + } + const stoppedEditing = stopEditingCases(socket.id); + if (stoppedEditing.length > 0) { + stoppedEditing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); + } + watchCase(socket, caseId); + const caseStatus = caseStatuses[caseId] || { viewers: [], editors: [] }; + caseStatuses[caseId] = caseStatus; + const matchingViewer = caseStatus.viewers.find(v => v.id === payload.user.id); + const notify = stoppedViewing.concat(stoppedEditing); + if (!matchingViewer) { + caseStatus.viewers.push({ ...payload.user, socketId: socket.id }); + notify.push(caseId); + } + if (notify.length > 0) { + notifyWatchers(socket, [ ...new Set(notify) ]); + } + socket.emit('response', getCaseStatuses([caseId])); +} + +function handleWatch(socket, caseIds) { + watchCases(socket, caseIds); + socket.emit('cases', getCaseStatuses(caseIds)); +} + +function watchCases(socket, caseIds) { + caseIds.forEach(caseId => { + watchCase(socket, caseId); + }); +} +function watchCase(socket, caseId) { + socket.join(`case:${caseId}`); +} +function stopWatchingCase(socket, caseId) { + socket.leave(`case:${caseId}`); +} +function notifyWatchers(socket, caseIds) { + caseIds.sort().forEach(caseId => { + socket.to(`case:${caseId}`).emit('cases', getCaseStatuses([caseId])); + }); +} + +function getCaseStatuses(caseIds) { + return caseIds.reduce((obj, caseId) => { + const cs = caseStatuses[caseId]; + if (cs) { + obj[caseId] = { + viewers: [ ...cs.viewers.map(w => w.id) ], + editors: [ ...cs.editors.map(e => e.id) ] + }; + } + return obj; + }, {}); +} + +function stopViewingOrEditing(socketId, exceptCaseId) { + const stoppedViewing = stopViewingCases(socketId, exceptCaseId); + const stoppedEditing = stopEditingCases(socketId, exceptCaseId); + return { stoppedViewing, stoppedEditing }; +} +function stopViewingCases(socketId, exceptCaseId) { + const affectedCases = []; + Object.keys(caseStatuses).filter(key => key !== exceptCaseId).forEach(key => { + const c = caseStatuses[key]; + const viewer = c.viewers.find(v => v.socketId === socketId); + if (viewer) { + c.viewers.splice(c.viewers.indexOf(viewer), 1); + affectedCases.push(key); + } + }); + return affectedCases; +} +function stopEditingCases(socketId, exceptCaseId) { + const affectedCases = []; + Object.keys(caseStatuses).filter(key => key !== exceptCaseId).forEach(key => { + const c = caseStatuses[key]; + const editor = c.editors.find(e => e.socketId === socketId); + if (editor) { + c.editors.splice(c.editors.indexOf(editor), 1); + affectedCases.push(key); + } + }); + return affectedCases; +} + +const connections = []; +io.sockets.on("connection", (socket) => { + connections.push(socket); + console.log(" %s sockets is connected", connections.length); + // console.log('connections[0]', connections[0]); + socket.on("disconnect", () => { + console.log(socket.id, 'has disconnected'); + stopViewingOrEditing(socket.id); + connections.splice(connections.indexOf(socket), 1); + }); + socket.on("sending message", (message) => { + console.log("Message is received :", message); + io.sockets.emit("new message", { message: message }); + }); +}); + /** * Listen on provided port, on all network interfaces. */ diff --git a/yarn.lock b/yarn.lock index 8865da7d..a0bbe630 100644 --- a/yarn.lock +++ b/yarn.lock @@ -236,16 +236,36 @@ resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== +"@types/component-emitter@^1.2.10": + version "1.2.10" + resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.10.tgz#ef5b1589b9f16544642e473db5ea5639107ef3ea" + integrity sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg== + +"@types/cookie@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.0.tgz#14f854c0f93d326e39da6e3b6f34f7d37513d108" + integrity sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg== + "@types/cookiejar@*": version "2.1.1" resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.1.tgz#90b68446364baf9efd8e8349bb36bd3852b75b80" integrity sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw== +"@types/cors@^2.8.8": + version "2.8.10" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.10.tgz#61cc8469849e5bcdd0c7044122265c39cec10cf4" + integrity sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ== + "@types/node@*": version "13.7.7" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.7.tgz#1628e6461ba8cc9b53196dfeaeec7b07fa6eea99" integrity sha512-Uo4chgKbnPNlxQwoFmYIwctkQVkMMmsAoGGU4JKwLuvBefF0pCq4FybNSnfkfRCpC7ZW7kttcC/TrRtAJsvGtg== +"@types/node@>=10.0.0": + version "15.3.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-15.3.0.tgz#d6fed7d6bc6854306da3dea1af9f874b00783e26" + integrity sha512-8/bnjSZD86ZfpBsDlCIkNXIvm+h6wi9g7IqL+kmFkQ+Wvu3JrasgLElfiPgoo8V8vVfnEi0QVS12gbl94h9YsQ== + "@types/superagent@^3.8.3": version "3.8.7" resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-3.8.7.tgz#1f1ed44634d5459b3a672eb7235a8e7cfd97704c" @@ -254,7 +274,7 @@ "@types/cookiejar" "*" "@types/node" "*" -accepts@^1.3.7, accepts@~1.3.7: +accepts@^1.3.7, accepts@~1.3.4, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== @@ -431,6 +451,16 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-arraybuffer@0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" + integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI= + +base64id@2.0.0, base64id@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" + integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== + basic-auth@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.0.tgz#015db3f353e02e56377755f962742e8981e7bbba" @@ -448,7 +478,7 @@ bluebird@^3.3.4: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA== -body-parser@1.19.0, body-parser@^1.18.2: +body-parser@1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== @@ -690,7 +720,7 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= -component-emitter@^1.2.0, component-emitter@^1.3.0: +component-emitter@^1.2.0, component-emitter@^1.3.0, component-emitter@~1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== @@ -764,6 +794,11 @@ cookie@0.4.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookie@~0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + cookiejar@^2.1.0, cookiejar@^2.1.1, cookiejar@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" @@ -774,6 +809,14 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +cors@~2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + cross-env@^5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.2.1.tgz#b2c76c1ca7add66dc874d11798466094f551b34d" @@ -827,6 +870,13 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: dependencies: ms "^2.1.1" +debug@~4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -947,6 +997,26 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= +engine.io-parser@~4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-4.0.2.tgz#e41d0b3fb66f7bf4a3671d2038a154024edb501e" + integrity sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg== + dependencies: + base64-arraybuffer "0.1.4" + +engine.io@~5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-5.1.1.tgz#a1f97e51ddf10cbd4db8b5ff4b165aad3760cdd3" + integrity sha512-aMWot7H5aC8L4/T8qMYbLdvKlZOdJTH54FxfdFunTGvhMx1BHkJOntWArsVfgAZVwAO9LC2sryPWRcEeUzCe5w== + dependencies: + accepts "~1.3.4" + base64id "2.0.0" + cookie "~0.4.1" + cors "~2.8.5" + debug "~4.3.1" + engine.io-parser "~4.0.0" + ws "~7.4.2" + error-ex@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -2235,7 +2305,7 @@ ms@2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -ms@^2.1.1: +ms@2.1.2, ms@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== @@ -2389,6 +2459,11 @@ nyc@^15.0.0: uuid "^3.3.3" yargs "^15.0.2" +object-assign@^4: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + object-inspect@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" @@ -2860,6 +2935,11 @@ rimraf@^3.0.0: dependencies: glob "^7.1.3" +route-parser@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/route-parser/-/route-parser-0.0.5.tgz#7d1d09d335e49094031ea16991a4a79b01bbe1f4" + integrity sha1-fR0J0zXkkJQDHqFpkaSnmwG74fQ= + run-async@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8" @@ -3014,6 +3094,42 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" +socket.io-adapter@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.3.0.tgz#63090df6dd6d289b0806acff4a0b2f1952ffe37e" + integrity sha512-jdIbSFRWOkaZpo5mXy8T7rXEN6qo3bOFuq4nVeX1ZS7AtFlkbk39y153xTXEIW7W94vZfhVOux1wTU88YxcM1w== + +socket.io-parser@~4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.4.tgz#9ea21b0d61508d18196ef04a2c6b9ab630f4c2b0" + integrity sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g== + dependencies: + "@types/component-emitter" "^1.2.10" + component-emitter "~1.3.0" + debug "~4.3.1" + +socket.io-router-middleware@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/socket.io-router-middleware/-/socket.io-router-middleware-1.1.2.tgz#87fca4e826436275274ab867eec24bbd7c45f446" + integrity sha512-UmkZA0x0Ozf3Svq/q8a6We6kBaZOn5bTx/o2zgDE0+4vplFA1fdQRRYy7qzXSGPk+NhgePjQjUuqxP3c97P5IQ== + dependencies: + route-parser "^0.0.5" + +socket.io@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.1.2.tgz#f90f9002a8d550efe2aa1d320deebb9a45b83233" + integrity sha512-xK0SD1C7hFrh9+bYoYCdVt+ncixkSLKtNLCax5aEy1o3r5PaO5yQhVb97exIe67cE7lAK+EpyMytXWTWmyZY8w== + dependencies: + "@types/cookie" "^0.4.0" + "@types/cors" "^2.8.8" + "@types/node" ">=10.0.0" + accepts "~1.3.4" + base64id "~2.0.0" + debug "~4.3.1" + engine.io "~5.1.0" + socket.io-adapter "~2.3.0" + socket.io-parser "~4.0.3" + sonar-scanner@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/sonar-scanner/-/sonar-scanner-3.1.0.tgz#51c1c1101f54b98abc5d8565209b1d9232979343" @@ -3393,7 +3509,7 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -vary@~1.1.2: +vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= @@ -3486,6 +3602,11 @@ write@1.0.3: dependencies: mkdirp "^0.5.1" +ws@~7.4.2: + version "7.4.5" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.5.tgz#a484dd851e9beb6fdb420027e3885e8ce48986c1" + integrity sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g== + y18n@^4.0.0, y18n@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" From e294b18c7323ffe48f3ac8e1f0511acbfb4c6ed8 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Thu, 20 May 2021 14:43:50 +0100 Subject: [PATCH 002/113] Update server.js Very small tweak to return the user name rather than their ID in the response and case listing. --- server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index d80bb9a1..3b37aad1 100755 --- a/server.js +++ b/server.js @@ -138,8 +138,8 @@ function getCaseStatuses(caseIds) { const cs = caseStatuses[caseId]; if (cs) { obj[caseId] = { - viewers: [ ...cs.viewers.map(w => w.id) ], - editors: [ ...cs.editors.map(e => e.id) ] + viewers: [ ...cs.viewers.map(w => w.name) ], + editors: [ ...cs.editors.map(e => e.name) ] }; } return obj; From d3a0636594d044573a4d9f7d0c86b1a5da25361f Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Thu, 20 May 2021 17:17:13 +0100 Subject: [PATCH 003/113] Update server.js Separate routes for each type of action rather than a catch-all that then has to figure out what to do. Also, the user details are captured on a new 'register' route, rather than being supplied on every single message. Finally, some nicer logging to see what's going on. --- server.js | 94 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 26 deletions(-) diff --git a/server.js b/server.js index 3b37aad1..7a673257 100755 --- a/server.js +++ b/server.js @@ -33,36 +33,69 @@ const io = require('socket.io')(server, { const IORouter = new require('socket.io-router-middleware'); const iorouter = new IORouter(); -// const socketsWatchingCases = {}; +// TODO: Track this stuff in redis. const caseStatuses = {}; - -// Add router paths -iorouter.on('/socket', (socket, ctx, next) => { - const payload = ctx.request.payload; - if (payload) { - if (payload.edit) { - handleEdit(socket, payload); - } else if (payload.view) { - handleView(socket, payload); - } else if (payload.watch) { - handleWatch(socket, payload.watch); +const socketUsers = {}; + +// Pretty way of logging. +function doLog(socket, payload, group) { + let text = `${new Date().toISOString()} | ${socket.id} | ${group}`; + if (typeof payload === 'string') { + if (payload) { + text = `${text} => ${payload}`; } + console.log(text); + } else { + console.group(text); + console.log(payload); + console.groupEnd(); } - // ctx.response = { hello: 'from server' }; - // socket.emit('response', ctx); - // Don't forget to call next() at the end to enable passing to other middlewares +} + +// Set up routes for each type of message. +iorouter.on('init', (socket, ctx, next) => { + // Do nothing in here. + doLog(socket, '', 'init'); + next(); +}); + +iorouter.on('register', (socket, ctx, next) => { + doLog(socket, ctx.request.user, 'register'); + socketUsers[socket.id] = ctx.request.user; + next(); +}); + +iorouter.on('view', (socket, ctx, next) => { + const user = socketUsers[socket.id]; + doLog(socket, `${ctx.request.caseId} (${user.name})`, 'view'); + handleView(socket, ctx.request.caseId, user); next(); }); + +iorouter.on('edit', (socket, ctx, next) => { + const user = socketUsers[socket.id]; + doLog(socket, `${ctx.request.caseId} (${user.name})`, 'edit'); + handleEdit(socket, ctx.request.caseId, user); + next(); +}); + +iorouter.on('watch', (socket, ctx, next) => { + const user = socketUsers[socket.id]; + doLog(socket, `${ctx.request.caseIds} (${user.name})`, 'watch'); + handleWatch(socket, ctx.request.caseIds); + next(); +}); + // On client connection attach the router io.on('connection', function (socket) { + // console.log('io.on.connection', socket.handshake); socket.use((packet, next) => { // Call router.attach() with the client socket as the first parameter iorouter.attach(socket, packet, next); }); }); -function handleEdit(socket, payload) { - const caseId = payload.edit; +function handleEdit(socket, caseId, user) { const stoppedViewing = stopViewingCases(socket.id); if (stoppedViewing.length > 0) { stoppedViewing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); @@ -74,10 +107,10 @@ function handleEdit(socket, payload) { watchCase(socket, caseId); const caseStatus = caseStatuses[caseId] || { viewers: [], editors: [] }; caseStatuses[caseId] = caseStatus; - const matchingEditor = caseStatus.editors.find(e => e.id === payload.user.id); + const matchingEditor = caseStatus.editors.find(e => e.id === user.id); const notify = stoppedViewing.concat(stoppedEditing); if (!matchingEditor) { - caseStatus.editors.push({ ...payload.user, socketId: socket.id }); + caseStatus.editors.push({ ...user, socketId: socket.id }); notify.push(caseId); } if (notify.length > 0) { @@ -86,8 +119,7 @@ function handleEdit(socket, payload) { socket.emit('response', getCaseStatuses([caseId])); } -function handleView(socket, payload) { - const caseId = payload.view; +function handleView(socket, caseId, user) { const stoppedViewing = stopViewingCases(socket.id, caseId); if (stoppedViewing.length > 0) { stoppedViewing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); @@ -99,10 +131,10 @@ function handleView(socket, payload) { watchCase(socket, caseId); const caseStatus = caseStatuses[caseId] || { viewers: [], editors: [] }; caseStatuses[caseId] = caseStatus; - const matchingViewer = caseStatus.viewers.find(v => v.id === payload.user.id); + const matchingViewer = caseStatus.viewers.find(v => v.id === user.id); const notify = stoppedViewing.concat(stoppedEditing); if (!matchingViewer) { - caseStatus.viewers.push({ ...payload.user, socketId: socket.id }); + caseStatus.viewers.push({ ...user, socketId: socket.id }); notify.push(caseId); } if (notify.length > 0) { @@ -129,6 +161,8 @@ function stopWatchingCase(socket, caseId) { } function notifyWatchers(socket, caseIds) { caseIds.sort().forEach(caseId => { + const cs = getCaseStatuses([caseId]); + doLog(socket, cs, `notify room 'case:${caseId}'`); socket.to(`case:${caseId}`).emit('cases', getCaseStatuses([caseId])); }); } @@ -138,13 +172,16 @@ function getCaseStatuses(caseIds) { const cs = caseStatuses[caseId]; if (cs) { obj[caseId] = { - viewers: [ ...cs.viewers.map(w => w.name) ], - editors: [ ...cs.editors.map(e => e.name) ] + viewers: [ ...cs.viewers.map(w => toUser(w)) ], + editors: [ ...cs.editors.map(e => toUser(e)) ] }; } return obj; }, {}); } +function toUser(obj) { + return { id: obj.id, name: obj.name }; +} function stopViewingOrEditing(socketId, exceptCaseId) { const stoppedViewing = stopViewingCases(socketId, exceptCaseId); @@ -179,11 +216,16 @@ function stopEditingCases(socketId, exceptCaseId) { const connections = []; io.sockets.on("connection", (socket) => { connections.push(socket); - console.log(" %s sockets is connected", connections.length); + if (connections.length === 1) { + console.log("1 socket connected"); + } else { + console.log("%s sockets connected", connections.length); + } // console.log('connections[0]', connections[0]); socket.on("disconnect", () => { console.log(socket.id, 'has disconnected'); stopViewingOrEditing(socket.id); + delete socketUsers[socket.id]; connections.splice(connections.indexOf(socket), 1); }); socket.on("sending message", (message) => { From 36714d3376ecf9b3fe8b744df8721e329444ac30 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Fri, 21 May 2021 11:48:20 +0100 Subject: [PATCH 004/113] Update cors.js First check that there IS a whitelist before trying to split it. --- app/security/cors.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/security/cors.js b/app/security/cors.js index 9eadc7a2..d21ef04b 100644 --- a/app/security/cors.js +++ b/app/security/cors.js @@ -1,7 +1,8 @@ const config = require('config'); const createWhitelistValidator = (val) => { - const whitelist = config.get('security.cors_origin_whitelist').split(','); + const configValue = config.get('security.cors_origin_whitelist') || ''; + const whitelist = configValue.split(','); for (let i = 0; i < whitelist.length; i += 1) { if (val === whitelist[i]) { return true; From f190a45a2b7852d945d9a9a114d88a0ed072c99e Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Fri, 21 May 2021 12:02:26 +0100 Subject: [PATCH 005/113] Refactor Shifted all the socket logic into a app/socket/index.js. --- app/socket/index.js | 222 ++++++++++++++++++++++++++++++++++++++++++++ server.js | 211 +---------------------------------------- 2 files changed, 223 insertions(+), 210 deletions(-) create mode 100644 app/socket/index.js diff --git a/app/socket/index.js b/app/socket/index.js new file mode 100644 index 00000000..1dfc60c1 --- /dev/null +++ b/app/socket/index.js @@ -0,0 +1,222 @@ +/** + * Sets up a series of routes for a "socket" endpoint, that + * leverages socket.io and will more than likely use long polling + * instead of websockets as the latter isn't supported by Azure + * Front Door. + * + * The behaviour is the same, though. + * + * TODO: + * 1. Use redis rather than holding the details in memory. + * 2. Some sort of auth / get the credentials when the user connects. + */ +module.exports = (server) => { + const io = require('socket.io')(server, { + allowEIO3: true + }); + const IORouter = new require('socket.io-router-middleware'); + const iorouter = new IORouter(); + + // TODO: Track this stuff in redis. + const caseStatuses = {}; + const socketUsers = {}; + + // Pretty way of logging. + function doLog(socket, payload, group) { + let text = `${new Date().toISOString()} | ${socket.id} | ${group}`; + if (typeof payload === 'string') { + if (payload) { + text = `${text} => ${payload}`; + } + console.log(text); + } else { + console.group(text); + console.log(payload); + console.groupEnd(); + } + } + + // Set up routes for each type of message. + iorouter.on('init', (socket, ctx, next) => { + // Do nothing in here. + doLog(socket, '', 'init'); + next(); + }); + + iorouter.on('register', (socket, ctx, next) => { + doLog(socket, ctx.request.user, 'register'); + socketUsers[socket.id] = ctx.request.user; + next(); + }); + + iorouter.on('view', (socket, ctx, next) => { + const user = socketUsers[socket.id]; + doLog(socket, `${ctx.request.caseId} (${user.name})`, 'view'); + handleView(socket, ctx.request.caseId, user); + next(); + }); + + iorouter.on('edit', (socket, ctx, next) => { + const user = socketUsers[socket.id]; + doLog(socket, `${ctx.request.caseId} (${user.name})`, 'edit'); + handleEdit(socket, ctx.request.caseId, user); + next(); + }); + + iorouter.on('watch', (socket, ctx, next) => { + const user = socketUsers[socket.id]; + doLog(socket, `${ctx.request.caseIds} (${user.name})`, 'watch'); + handleWatch(socket, ctx.request.caseIds); + next(); + }); + + // On client connection attach the router + io.on('connection', function (socket) { + // console.log('io.on.connection', socket.handshake); + socket.use((packet, next) => { + // Call router.attach() with the client socket as the first parameter + iorouter.attach(socket, packet, next); + }); + }); + + function handleEdit(socket, caseId, user) { + const stoppedViewing = stopViewingCases(socket.id); + if (stoppedViewing.length > 0) { + stoppedViewing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); + } + const stoppedEditing = stopEditingCases(socket.id, caseId); + if (stoppedEditing.length > 0) { + stoppedEditing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); + } + watchCase(socket, caseId); + const caseStatus = caseStatuses[caseId] || { viewers: [], editors: [] }; + caseStatuses[caseId] = caseStatus; + const matchingEditor = caseStatus.editors.find(e => e.id === user.id); + const notify = stoppedViewing.concat(stoppedEditing); + if (!matchingEditor) { + caseStatus.editors.push({ ...user, socketId: socket.id }); + notify.push(caseId); + } + if (notify.length > 0) { + notifyWatchers(socket, [ ...new Set(notify) ]); + } + socket.emit('response', getCaseStatuses([caseId])); + } + + function handleView(socket, caseId, user) { + const stoppedViewing = stopViewingCases(socket.id, caseId); + if (stoppedViewing.length > 0) { + stoppedViewing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); + } + const stoppedEditing = stopEditingCases(socket.id); + if (stoppedEditing.length > 0) { + stoppedEditing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); + } + watchCase(socket, caseId); + const caseStatus = caseStatuses[caseId] || { viewers: [], editors: [] }; + caseStatuses[caseId] = caseStatus; + const matchingViewer = caseStatus.viewers.find(v => v.id === user.id); + const notify = stoppedViewing.concat(stoppedEditing); + if (!matchingViewer) { + caseStatus.viewers.push({ ...user, socketId: socket.id }); + notify.push(caseId); + } + if (notify.length > 0) { + notifyWatchers(socket, [ ...new Set(notify) ]); + } + socket.emit('response', getCaseStatuses([caseId])); + } + + function handleWatch(socket, caseIds) { + watchCases(socket, caseIds); + socket.emit('cases', getCaseStatuses(caseIds)); + } + + function watchCases(socket, caseIds) { + caseIds.forEach(caseId => { + watchCase(socket, caseId); + }); + } + function watchCase(socket, caseId) { + socket.join(`case:${caseId}`); + } + function stopWatchingCase(socket, caseId) { + socket.leave(`case:${caseId}`); + } + function notifyWatchers(socket, caseIds) { + caseIds.sort().forEach(caseId => { + const cs = getCaseStatuses([caseId]); + doLog(socket, cs, `notify room 'case:${caseId}'`); + socket.to(`case:${caseId}`).emit('cases', getCaseStatuses([caseId])); + }); + } + + function getCaseStatuses(caseIds) { + return caseIds.reduce((obj, caseId) => { + const cs = caseStatuses[caseId]; + if (cs) { + obj[caseId] = { + viewers: [ ...cs.viewers.map(w => toUser(w)) ], + editors: [ ...cs.editors.map(e => toUser(e)) ] + }; + } + return obj; + }, {}); + } + function toUser(obj) { + return { id: obj.id, name: obj.name }; + } + + function stopViewingOrEditing(socketId, exceptCaseId) { + const stoppedViewing = stopViewingCases(socketId, exceptCaseId); + const stoppedEditing = stopEditingCases(socketId, exceptCaseId); + return { stoppedViewing, stoppedEditing }; + } + function stopViewingCases(socketId, exceptCaseId) { + const affectedCases = []; + Object.keys(caseStatuses).filter(key => key !== exceptCaseId).forEach(key => { + const c = caseStatuses[key]; + const viewer = c.viewers.find(v => v.socketId === socketId); + if (viewer) { + c.viewers.splice(c.viewers.indexOf(viewer), 1); + affectedCases.push(key); + } + }); + return affectedCases; + } + function stopEditingCases(socketId, exceptCaseId) { + const affectedCases = []; + Object.keys(caseStatuses).filter(key => key !== exceptCaseId).forEach(key => { + const c = caseStatuses[key]; + const editor = c.editors.find(e => e.socketId === socketId); + if (editor) { + c.editors.splice(c.editors.indexOf(editor), 1); + affectedCases.push(key); + } + }); + return affectedCases; + } + + const connections = []; + io.sockets.on("connection", (socket) => { + connections.push(socket); + if (connections.length === 1) { + console.log("1 socket connected"); + } else { + console.log("%s sockets connected", connections.length); + } + // console.log('connections[0]', connections[0]); + socket.on("disconnect", () => { + console.log(socket.id, 'has disconnected'); + stopViewingOrEditing(socket.id); + delete socketUsers[socket.id]; + connections.splice(connections.indexOf(socket), 1); + }); + socket.on("sending message", (message) => { + console.log("Message is received :", message); + io.sockets.emit("new message", { message: message }); + }); + }); + + return io; +}; \ No newline at end of file diff --git a/server.js b/server.js index 7a673257..13b4cb37 100755 --- a/server.js +++ b/server.js @@ -23,216 +23,7 @@ app.set('port', port); */ var server = http.createServer(app); - -/** - * Set up a socket.io router. - */ -const io = require('socket.io')(server, { - allowEIO3: true -}); -const IORouter = new require('socket.io-router-middleware'); -const iorouter = new IORouter(); - -// TODO: Track this stuff in redis. -const caseStatuses = {}; -const socketUsers = {}; - -// Pretty way of logging. -function doLog(socket, payload, group) { - let text = `${new Date().toISOString()} | ${socket.id} | ${group}`; - if (typeof payload === 'string') { - if (payload) { - text = `${text} => ${payload}`; - } - console.log(text); - } else { - console.group(text); - console.log(payload); - console.groupEnd(); - } -} - -// Set up routes for each type of message. -iorouter.on('init', (socket, ctx, next) => { - // Do nothing in here. - doLog(socket, '', 'init'); - next(); -}); - -iorouter.on('register', (socket, ctx, next) => { - doLog(socket, ctx.request.user, 'register'); - socketUsers[socket.id] = ctx.request.user; - next(); -}); - -iorouter.on('view', (socket, ctx, next) => { - const user = socketUsers[socket.id]; - doLog(socket, `${ctx.request.caseId} (${user.name})`, 'view'); - handleView(socket, ctx.request.caseId, user); - next(); -}); - -iorouter.on('edit', (socket, ctx, next) => { - const user = socketUsers[socket.id]; - doLog(socket, `${ctx.request.caseId} (${user.name})`, 'edit'); - handleEdit(socket, ctx.request.caseId, user); - next(); -}); - -iorouter.on('watch', (socket, ctx, next) => { - const user = socketUsers[socket.id]; - doLog(socket, `${ctx.request.caseIds} (${user.name})`, 'watch'); - handleWatch(socket, ctx.request.caseIds); - next(); -}); - -// On client connection attach the router -io.on('connection', function (socket) { - // console.log('io.on.connection', socket.handshake); - socket.use((packet, next) => { - // Call router.attach() with the client socket as the first parameter - iorouter.attach(socket, packet, next); - }); -}); - -function handleEdit(socket, caseId, user) { - const stoppedViewing = stopViewingCases(socket.id); - if (stoppedViewing.length > 0) { - stoppedViewing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); - } - const stoppedEditing = stopEditingCases(socket.id, caseId); - if (stoppedEditing.length > 0) { - stoppedEditing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); - } - watchCase(socket, caseId); - const caseStatus = caseStatuses[caseId] || { viewers: [], editors: [] }; - caseStatuses[caseId] = caseStatus; - const matchingEditor = caseStatus.editors.find(e => e.id === user.id); - const notify = stoppedViewing.concat(stoppedEditing); - if (!matchingEditor) { - caseStatus.editors.push({ ...user, socketId: socket.id }); - notify.push(caseId); - } - if (notify.length > 0) { - notifyWatchers(socket, [ ...new Set(notify) ]); - } - socket.emit('response', getCaseStatuses([caseId])); -} - -function handleView(socket, caseId, user) { - const stoppedViewing = stopViewingCases(socket.id, caseId); - if (stoppedViewing.length > 0) { - stoppedViewing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); - } - const stoppedEditing = stopEditingCases(socket.id); - if (stoppedEditing.length > 0) { - stoppedEditing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); - } - watchCase(socket, caseId); - const caseStatus = caseStatuses[caseId] || { viewers: [], editors: [] }; - caseStatuses[caseId] = caseStatus; - const matchingViewer = caseStatus.viewers.find(v => v.id === user.id); - const notify = stoppedViewing.concat(stoppedEditing); - if (!matchingViewer) { - caseStatus.viewers.push({ ...user, socketId: socket.id }); - notify.push(caseId); - } - if (notify.length > 0) { - notifyWatchers(socket, [ ...new Set(notify) ]); - } - socket.emit('response', getCaseStatuses([caseId])); -} - -function handleWatch(socket, caseIds) { - watchCases(socket, caseIds); - socket.emit('cases', getCaseStatuses(caseIds)); -} - -function watchCases(socket, caseIds) { - caseIds.forEach(caseId => { - watchCase(socket, caseId); - }); -} -function watchCase(socket, caseId) { - socket.join(`case:${caseId}`); -} -function stopWatchingCase(socket, caseId) { - socket.leave(`case:${caseId}`); -} -function notifyWatchers(socket, caseIds) { - caseIds.sort().forEach(caseId => { - const cs = getCaseStatuses([caseId]); - doLog(socket, cs, `notify room 'case:${caseId}'`); - socket.to(`case:${caseId}`).emit('cases', getCaseStatuses([caseId])); - }); -} - -function getCaseStatuses(caseIds) { - return caseIds.reduce((obj, caseId) => { - const cs = caseStatuses[caseId]; - if (cs) { - obj[caseId] = { - viewers: [ ...cs.viewers.map(w => toUser(w)) ], - editors: [ ...cs.editors.map(e => toUser(e)) ] - }; - } - return obj; - }, {}); -} -function toUser(obj) { - return { id: obj.id, name: obj.name }; -} - -function stopViewingOrEditing(socketId, exceptCaseId) { - const stoppedViewing = stopViewingCases(socketId, exceptCaseId); - const stoppedEditing = stopEditingCases(socketId, exceptCaseId); - return { stoppedViewing, stoppedEditing }; -} -function stopViewingCases(socketId, exceptCaseId) { - const affectedCases = []; - Object.keys(caseStatuses).filter(key => key !== exceptCaseId).forEach(key => { - const c = caseStatuses[key]; - const viewer = c.viewers.find(v => v.socketId === socketId); - if (viewer) { - c.viewers.splice(c.viewers.indexOf(viewer), 1); - affectedCases.push(key); - } - }); - return affectedCases; -} -function stopEditingCases(socketId, exceptCaseId) { - const affectedCases = []; - Object.keys(caseStatuses).filter(key => key !== exceptCaseId).forEach(key => { - const c = caseStatuses[key]; - const editor = c.editors.find(e => e.socketId === socketId); - if (editor) { - c.editors.splice(c.editors.indexOf(editor), 1); - affectedCases.push(key); - } - }); - return affectedCases; -} - -const connections = []; -io.sockets.on("connection", (socket) => { - connections.push(socket); - if (connections.length === 1) { - console.log("1 socket connected"); - } else { - console.log("%s sockets connected", connections.length); - } - // console.log('connections[0]', connections[0]); - socket.on("disconnect", () => { - console.log(socket.id, 'has disconnected'); - stopViewingOrEditing(socket.id); - delete socketUsers[socket.id]; - connections.splice(connections.indexOf(socket), 1); - }); - socket.on("sending message", (message) => { - console.log("Message is received :", message); - io.sockets.emit("new message", { message: message }); - }); -}); +var io = require('./app/socket')(server); /** * Listen on provided port, on all network interfaces. From c72f4bbdd339224dcbd73ecb2e47a6b40a07fec9 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Mon, 24 May 2021 12:11:12 +0100 Subject: [PATCH 006/113] Redis instantiator We need two redis clients to support pub/sub so made an instantiator that is called from the existing `redis/redis-client.js` and is also now being called from `socket/redis-watcher.js`. --- app/redis/instantiator.js | 48 +++++++++++++++++++++++++++++++++++++ app/redis/redis-client.js | 46 +---------------------------------- app/socket/redis-watcher.js | 3 +++ 3 files changed, 52 insertions(+), 45 deletions(-) create mode 100644 app/redis/instantiator.js create mode 100644 app/socket/redis-watcher.js diff --git a/app/redis/instantiator.js b/app/redis/instantiator.js new file mode 100644 index 00000000..3a383b1b --- /dev/null +++ b/app/redis/instantiator.js @@ -0,0 +1,48 @@ +const config = require('config'); +const Redis = require('ioredis'); + +const ERROR = 0; +const RESULT = 1; +const ENV = config.util.getEnv('NODE_ENV'); + +module.exports = (debug) => { + const redis = new Redis({ + port: config.get('redis.port'), + host: config.get('redis.host'), + password: config.get('secrets.ccd.activity-redis-password'), + tls: config.get('redis.ssl'), + keyPrefix: config.get('redis.keyPrefix'), + // log unhandled redis errors + showFriendlyErrorStack: ENV === 'test' || ENV === 'dev', + }); + + /* redis pipeline returns a reply of the form [[op1error, op1result], [op2error, op2result], ...]. + error is null in case of success */ + redis.logPipelineFailures = (plOutcome, message) => { + if (Array.isArray(plOutcome)) { + const operationsFailureOutcome = plOutcome.map((operationOutcome) => operationOutcome[ERROR]); + const failures = operationsFailureOutcome.filter((element) => element !== null); + failures.forEach((f) => debug(`${message}: ${f}`)); + } else { + debug(`${plOutcome} is not an Array...`); + } + return plOutcome; + }; + + redis.extractPipelineResults = (pipelineOutcome) => { + const results = pipelineOutcome.map((operationOutcome) => operationOutcome[RESULT]); + debug(`pipeline results: ${results}`); + return results; + }; + + redis + .on('error', (err) => { + // eslint-disable-next-line no-console + console.log(`Redis error: ${err.message}`); + }).on('connect', () => { + // eslint-disable-next-line no-console + console.log('connected to Redis'); + }); + + return redis; +}; diff --git a/app/redis/redis-client.js b/app/redis/redis-client.js index d88faeeb..a14d64b0 100644 --- a/app/redis/redis-client.js +++ b/app/redis/redis-client.js @@ -1,47 +1,3 @@ -const config = require('config'); const debug = require('debug')('ccd-case-activity-api:redis-client'); -const Redis = require('ioredis'); -const ERROR = 0; -const RESULT = 1; -const ENV = config.util.getEnv('NODE_ENV'); - -const redis = new Redis({ - port: config.get('redis.port'), - host: config.get('redis.host'), - password: config.get('secrets.ccd.activity-redis-password'), - tls: config.get('redis.ssl'), - keyPrefix: config.get('redis.keyPrefix'), - // log unhandled redis errors - showFriendlyErrorStack: ENV === 'test' || ENV === 'dev', -}); - -/* redis pipeline returns a reply of the form [[op1error, op1result], [op2error, op2result], ...]. - error is null in case of success */ -redis.logPipelineFailures = (plOutcome, message) => { - if (Array.isArray(plOutcome)) { - const operationsFailureOutcome = plOutcome.map((operationOutcome) => operationOutcome[ERROR]); - const failures = operationsFailureOutcome.filter((element) => element !== null); - failures.forEach((f) => debug(`${message}: ${f}`)); - } else { - debug(`${plOutcome} is not an Array...`); - } - return plOutcome; -}; - -redis.extractPipelineResults = (pipelineOutcome) => { - const results = pipelineOutcome.map((operationOutcome) => operationOutcome[RESULT]); - debug(`pipeline results: ${results}`); - return results; -}; - -redis - .on('error', (err) => { - // eslint-disable-next-line no-console - console.log(`Redis error: ${err.message}`); - }).on('connect', () => { - // eslint-disable-next-line no-console - console.log('connected to Redis'); - }); - -module.exports = redis; +module.exports = require('./instantiator')(debug); diff --git a/app/socket/redis-watcher.js b/app/socket/redis-watcher.js new file mode 100644 index 00000000..50c1a4e3 --- /dev/null +++ b/app/socket/redis-watcher.js @@ -0,0 +1,3 @@ +const debug = require('debug')('ccd-case-activity-api:redis-watcher'); + +module.exports = require('../redis/instantiator')(debug); From 10b3f97476cf34e9fd54e186e60d218cb7586bb6 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Mon, 24 May 2021 12:13:56 +0100 Subject: [PATCH 007/113] Redis persistence Socket connections now persist to and retrieve from the redis database. There is further refactoring required, not least a mechanism for removing an entry when a user disconnects. There is also a second redis client to support pub/sub so any changes to the redis keys is accompanied by a redis publish that the socket's version of the activity service is subscribed to and then accordingly handles and notifies users about. --- app.js | 2 +- app/socket/activity-service.js | 120 +++++++++++++++++++++++++++++++++ app/socket/index.js | 113 ++++++++++++++++++++++--------- server.js | 10 ++- 4 files changed, 210 insertions(+), 35 deletions(-) create mode 100644 app/socket/activity-service.js diff --git a/app.js b/app.js index 26a0d565..12be98ba 100644 --- a/app.js +++ b/app.js @@ -72,4 +72,4 @@ app.use((err, req, res, next) => { }); }); -module.exports = app; +module.exports = { app, redis }; diff --git a/app/socket/activity-service.js b/app/socket/activity-service.js new file mode 100644 index 00000000..00ae6c31 --- /dev/null +++ b/app/socket/activity-service.js @@ -0,0 +1,120 @@ +const debug = require('debug')('ccd-case-activity-api:activity-service'); + +module.exports = (config, redis, ttlScoreGenerator) => { + const redisActivityKeys = { + view: (caseId) => `case:${caseId}:viewers`, + edit: (caseId) => `case:${caseId}:editors`, + base: (caseId) => `case:${caseId}` + }; + const userDetailsTtlSec = config.get('redis.userDetailsTtlSec'); + const toUserString = (user) => { + return JSON.stringify({ + id: user.uid, + forename: user.given_name, + surname: user.family_name + }); + }; + + const addActivity = (caseId, user, activity) => { + const storeUserActivity = () => { + const key = redisActivityKeys[activity](caseId); + debug(`about to store user activity with key: ${key}`); + return ['zadd', key, ttlScoreGenerator.getScore(), user.uid]; + }; + + const storeUserDetails = () => { + const userDetails = toUserString(user); + const key = `user:${user.uid}`; + debug(`about to store user details with key ${key}: ${userDetails}`); + return ['set', key, userDetails, 'EX', userDetailsTtlSec]; + }; + return redis.pipeline([ + storeUserActivity(), + storeUserDetails() + ]).exec().then(() => { + redis.publish(redisActivityKeys.base(caseId), Date.now().toString()); + }); + }; + + /** + * TODO: Implement a mechanism to remove activity. There are a few options here: + * I think this will require us to track which activities relates to which sockets + * so we can clear them whenever the socket disconnects. + * @param {*} caseId + * @param {*} user + * @param {*} activity + * @returns + */ + const removeActivity = (caseId, user, activity) => { + const removeUserActivity = () => { + const key = redisActivityKeys[activity](caseId); + debug(`about to remove user activity with key: ${key}`); + return ['zrem', key, user.uid]; + }; + + return redis.pipeline([ + removeUserActivity() + ]).exec().then(() => { + redis.publish(redisActivityKeys.base(caseId), Date.now().toString()); + }); + }; + + const getActivityForCases = async (caseIds) => { + const uniqueUserIds = []; + let caseViewers = []; + let caseEditors = []; + const now = Date.now(); + const getUserDetails = () => redis.pipeline(uniqueUserIds.map((userId) => ['get', `user:${userId}`])).exec(); + const extractUniqueUserIds = (result) => { + if (result) { + result.forEach(item => { + if (item && item[1]) { + item[1].forEach(userId => { + if (!uniqueUserIds.includes(userId)) { + uniqueUserIds.push(userId); + } + }); + } + }); + } + }; + const caseViewersPromise = redis + .pipeline(caseIds.map(caseId => ['zrangebyscore', `case:${caseId}:viewers`, now, '+inf'])) + .exec() + .then(result => { + redis.logPipelineFailures(result, 'caseViewersPromise'); + caseViewers = result; + extractUniqueUserIds(result); + }); + const caseEditorsPromise = redis + .pipeline(caseIds.map(caseId => ['zrangebyscore', `case:${caseId}:editors`, now, '+inf'])) + .exec() + .then(result => { + redis.logPipelineFailures(result, 'caseEditorsPromise'); + caseEditors = result; + extractUniqueUserIds(result); + }); + await Promise.all([caseViewersPromise, caseEditorsPromise]); + + const userDetails = await getUserDetails().reduce((obj, item) => { + const user = JSON.parse(item[1]); + obj[user.id] = { forename: user.forename, surname: user.surname }; + return obj; + }, {}); + + return caseIds.map((caseId, index) => { + redis.logPipelineFailures(userDetails, 'userDetails'); + const cv = caseViewers[index][1], ce = caseEditors[index][1]; + const viewers = cv ? cv.map(v => userDetails[v]) : []; + const editors = ce ? ce.map(e => userDetails[e]) : []; + return { + caseId, + viewers: viewers.filter(v => !!v), + unknownViewers: viewers.filter(v => !v).length, + editors: editors.filter(e => !!e), + unknownEditors: editors.filter(e => !e).length + }; + }); + }; + return { addActivity, removeActivity, getActivityForCases }; +}; diff --git a/app/socket/index.js b/app/socket/index.js index 1dfc60c1..3357b3e4 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -1,3 +1,10 @@ +const config = require('config'); +const ttlScoreGenerator = require('../service/ttl-score-generator'); +const redisWatcher = require('./redis-watcher'); + +const IORouter = new require('socket.io-router-middleware'); +const iorouter = new IORouter(); + /** * Sets up a series of routes for a "socket" endpoint, that * leverages socket.io and will more than likely use long polling @@ -9,13 +16,40 @@ * TODO: * 1. Use redis rather than holding the details in memory. * 2. Some sort of auth / get the credentials when the user connects. + * + * Add view activity looks like this: + * addActivity 1588201414700270 { + sub: 'leeds_et@mailinator.com', + uid: '85269805-3a70-419d-acab-193faeb89ad3', + roles: [ + 'caseworker-employment', + 'caseworker-employment-leeds', + 'caseworker' + ], + name: 'Ethos Leeds', + given_name: 'Ethos', + family_name: 'Leeds' + } view + * */ -module.exports = (server) => { +module.exports = (server, redis) => { + const activityService = require('./activity-service')(config, redis, ttlScoreGenerator); const io = require('socket.io')(server, { allowEIO3: true }); - const IORouter = new require('socket.io-router-middleware'); - const iorouter = new IORouter(); + + async function whenActivityForCases(caseIds) { + const caseActivty = await activityService.getActivityForCases(caseIds); + console.log('activity for cases', caseIds, caseActivty); + return caseActivty; + } + async function notifyWatchers(caseIds) { + caseIds = Array.isArray(caseIds) ? caseIds : [caseIds]; + caseIds.sort().forEach(async (caseId) => { + const cs = await whenActivityForCases([caseId]); + io.to(`case:${caseId}`).emit('cases', cs); + }); + } // TODO: Track this stuff in redis. const caseStatuses = {}; @@ -36,6 +70,28 @@ module.exports = (server) => { } } + redisWatcher.on('message', room => { + const caseId = room.replace('case:', ''); + console.log('redisWatcher.on.message', room, caseId); + notifyWatchers([caseId]); + // io.to(room).emit(message); + }); + + + // When a new room is created, we want to watch for changes to that case. + io.of('/').adapter.on('create-room', (room) => { + console.log(`room ${room} was created`); + if (room.indexOf('case:') === 0) { + redisWatcher.subscribe(`${room}`); + } + }); + + io.of('/').adapter.on('delete-room', (room) => { + if (room.indexOf('case:') === 0) { + redisWatcher.unsubscribe(`${room}`); + } + }); + // Set up routes for each type of message. iorouter.on('init', (socket, ctx, next) => { // Do nothing in here. @@ -53,6 +109,9 @@ module.exports = (server) => { const user = socketUsers[socket.id]; doLog(socket, `${ctx.request.caseId} (${user.name})`, 'view'); handleView(socket, ctx.request.caseId, user); + + // Try sticking it in redis. + activityService.addActivity(ctx.request.caseId, toUser(user), 'view'); next(); }); @@ -60,6 +119,9 @@ module.exports = (server) => { const user = socketUsers[socket.id]; doLog(socket, `${ctx.request.caseId} (${user.name})`, 'edit'); handleEdit(socket, ctx.request.caseId, user); + + // Try sticking it in redis. + activityService.addActivity(ctx.request.caseId, toUser(user), 'edit'); next(); }); @@ -72,7 +134,6 @@ module.exports = (server) => { // On client connection attach the router io.on('connection', function (socket) { - // console.log('io.on.connection', socket.handshake); socket.use((packet, next) => { // Call router.attach() with the client socket as the first parameter iorouter.attach(socket, packet, next); @@ -98,9 +159,8 @@ module.exports = (server) => { notify.push(caseId); } if (notify.length > 0) { - notifyWatchers(socket, [ ...new Set(notify) ]); + notifyWatchers([ ...new Set(notify) ]); } - socket.emit('response', getCaseStatuses([caseId])); } function handleView(socket, caseId, user) { @@ -122,14 +182,14 @@ module.exports = (server) => { notify.push(caseId); } if (notify.length > 0) { - notifyWatchers(socket, [ ...new Set(notify) ]); + notifyWatchers([ ...new Set(notify) ]); } - socket.emit('response', getCaseStatuses([caseId])); } - function handleWatch(socket, caseIds) { + async function handleWatch(socket, caseIds) { watchCases(socket, caseIds); - socket.emit('cases', getCaseStatuses(caseIds)); + const cs = await whenActivityForCases(caseIds); + socket.emit('cases', cs); } function watchCases(socket, caseIds) { @@ -143,28 +203,19 @@ module.exports = (server) => { function stopWatchingCase(socket, caseId) { socket.leave(`case:${caseId}`); } - function notifyWatchers(socket, caseIds) { - caseIds.sort().forEach(caseId => { - const cs = getCaseStatuses([caseId]); - doLog(socket, cs, `notify room 'case:${caseId}'`); - socket.to(`case:${caseId}`).emit('cases', getCaseStatuses([caseId])); - }); - } - - function getCaseStatuses(caseIds) { - return caseIds.reduce((obj, caseId) => { - const cs = caseStatuses[caseId]; - if (cs) { - obj[caseId] = { - viewers: [ ...cs.viewers.map(w => toUser(w)) ], - editors: [ ...cs.editors.map(e => toUser(e)) ] - }; - } - return obj; - }, {}); - } function toUser(obj) { - return { id: obj.id, name: obj.name }; + return { + sub: `${obj.name.replace(' ', '.')}@mailinator.com`, + uid: obj.id, + roles: [ + 'caseworker-employment', + 'caseworker-employment-leeds', + 'caseworker' + ], + name: obj.name, + given_name: obj.name.split(' ')[0], + family_name: obj.name.split(' ')[1] + }; } function stopViewingOrEditing(socketId, exceptCaseId) { diff --git a/server.js b/server.js index 13b4cb37..6a4447ce 100755 --- a/server.js +++ b/server.js @@ -16,14 +16,18 @@ var http = require('http'); var port = normalizePort(process.env.PORT || '3460'); console.log('Starting on port ' + port); -app.set('port', port); +app.app.set('port', port); /** * Create HTTP server. */ -var server = http.createServer(app); -var io = require('./app/socket')(server); +var server = http.createServer(app.app); + +/** + * Create the socket server. + */ +require('./app/socket')(server, app.redis); /** * Listen on provided port, on all network interfaces. From 329a8b72b7affe0e9b8fcb4eb06eae3ec2f92b51 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Mon, 24 May 2021 17:24:46 +0100 Subject: [PATCH 008/113] Track what each socket is doing The activity service now also tracks what each socket is doing - i.e., viewing or editing a case - and only allows one activity for each socket at any one time. When a socket disconnects, the activity is cleared out as it's considered that the user is no longer view/editing a case. Also sorted out a bunch of lint errors and warnings. --- .eslintrc.yml | 4 + app/job/store-cleanup-job.js | 4 +- app/redis/instantiator.js | 5 +- app/routes/validate-request.js | 4 +- app/service/activity-service.js | 3 +- app/service/ttl-score-generator.js | 8 +- app/socket/activity-service.js | 108 +++++--- app/socket/index.js | 248 ++++++------------ server.js | 1 - test/e2e/utils/activity-store-commands.js | 5 +- test/spec/app/health/health-check.spec.js | 6 +- .../spec/app/service/activity-service.spec.js | 10 +- 12 files changed, 176 insertions(+), 230 deletions(-) diff --git a/.eslintrc.yml b/.eslintrc.yml index c0b019b5..9f5ce475 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -2,3 +2,7 @@ extends: airbnb-base env: mocha: true jasmine: true +rules: + comma-dangle: 0 + arrow-body-style: 0 + no-param-reassign: [ 2, { props: false } ] diff --git a/app/job/store-cleanup-job.js b/app/job/store-cleanup-job.js index fca51559..0da3179e 100644 --- a/app/job/store-cleanup-job.js +++ b/app/job/store-cleanup-job.js @@ -1,11 +1,9 @@ const cron = require('node-cron'); const debug = require('debug')('ccd-case-activity-api:store-cleanup-job'); -const moment = require('moment'); const config = require('config'); const redis = require('../redis/redis-client'); const { logPipelineFailures } = redis; -const now = () => moment().valueOf(); const REDIS_ACTIVITY_KEY_PREFIX = config.get('redis.keyPrefix'); const scanExistingCasesKeys = (f) => { @@ -30,7 +28,7 @@ const scanExistingCasesKeys = (f) => { const getCasesWithActivities = (f) => scanExistingCasesKeys(f); -const cleanupActivitiesCommand = (key) => ['zremrangebyscore', key, '-inf', now()]; +const cleanupActivitiesCommand = (key) => ['zremrangebyscore', key, '-inf', Date.now()]; const pipeline = (cases) => { const commands = cases.map((caseKey) => cleanupActivitiesCommand(caseKey)); diff --git a/app/redis/instantiator.js b/app/redis/instantiator.js index 3a383b1b..cad8a545 100644 --- a/app/redis/instantiator.js +++ b/app/redis/instantiator.js @@ -25,6 +25,7 @@ module.exports = (debug) => { failures.forEach((f) => debug(`${message}: ${f}`)); } else { debug(`${plOutcome} is not an Array...`); + debug(`${JSON.stringify(plOutcome)}`); } return plOutcome; }; @@ -38,10 +39,10 @@ module.exports = (debug) => { redis .on('error', (err) => { // eslint-disable-next-line no-console - console.log(`Redis error: ${err.message}`); + debug(`Redis error: ${err.message}`); }).on('connect', () => { // eslint-disable-next-line no-console - console.log('connected to Redis'); + debug('connected to Redis'); }); return redis; diff --git a/app/routes/validate-request.js b/app/routes/validate-request.js index c8be4759..7f05758a 100644 --- a/app/routes/validate-request.js +++ b/app/routes/validate-request.js @@ -1,3 +1,5 @@ +const debug = require('debug')('ccd-case-activity-api:validate-request'); + const validateRequest = (schema, value) => (req, res, next) => { const { error } = schema.validate(value); const valid = error == null; @@ -6,7 +8,7 @@ const validateRequest = (schema, value) => (req, res, next) => { } else { const { details } = error; const message = details.map((i) => i.message).join(','); - console.log('error', message); + debug(`error ${message}`); res.status(400).json({ error: message }); } }; diff --git a/app/service/activity-service.js b/app/service/activity-service.js index baa40b6d..db191468 100644 --- a/app/service/activity-service.js +++ b/app/service/activity-service.js @@ -1,4 +1,3 @@ -const moment = require('moment'); const debug = require('debug')('ccd-case-activity-api:activity-service'); module.exports = (config, redis, ttlScoreGenerator) => { @@ -31,7 +30,7 @@ module.exports = (config, redis, ttlScoreGenerator) => { const uniqueUserIds = []; let caseViewers = []; let caseEditors = []; - const now = moment.now(); + const now = Date.now(); const getUserDetails = () => redis.pipeline(uniqueUserIds.map((userId) => ['get', `user:${userId}`])).exec(); const extractUniqueUserIds = (result) => { result.forEach((item) => { diff --git a/app/service/ttl-score-generator.js b/app/service/ttl-score-generator.js index 5cb4a4ad..1ddc45d5 100644 --- a/app/service/ttl-score-generator.js +++ b/app/service/ttl-score-generator.js @@ -1,10 +1,10 @@ const config = require('config'); -const moment = require('moment'); const debug = require('debug')('ccd-case-activity-api:score-generator'); exports.getScore = () => { - const now = moment(); - const score = now.add(config.get('redis.activityTtlSec'), 'seconds').valueOf(); - debug(`generated score out of current timestamp '${now.valueOf()}' plus ${config.get('redis.activityTtlSec')} sec`); + const now = Date.now(); + const ttl = parseInt(config.get('redis.activityTtlSec'), 10) || 0; + const score = now + (ttl * 1000); + debug(`generated score out of current timestamp '${now}' plus ${ttl} sec`); return score; }; diff --git a/app/socket/activity-service.js b/app/socket/activity-service.js index 00ae6c31..a4ac61ac 100644 --- a/app/socket/activity-service.js +++ b/app/socket/activity-service.js @@ -4,7 +4,9 @@ module.exports = (config, redis, ttlScoreGenerator) => { const redisActivityKeys = { view: (caseId) => `case:${caseId}:viewers`, edit: (caseId) => `case:${caseId}:editors`, - base: (caseId) => `case:${caseId}` + baseCase: (caseId) => `case:${caseId}`, + user: (userId) => `user:${userId}`, + socket: (socketId) => `socket:${socketId}` }; const userDetailsTtlSec = config.get('redis.userDetailsTtlSec'); const toUserString = (user) => { @@ -15,7 +17,7 @@ module.exports = (config, redis, ttlScoreGenerator) => { }); }; - const addActivity = (caseId, user, activity) => { + const addActivity = (caseId, user, socketId, activity) => { const storeUserActivity = () => { const key = redisActivityKeys[activity](caseId); debug(`about to store user activity with key: ${key}`); @@ -24,39 +26,53 @@ module.exports = (config, redis, ttlScoreGenerator) => { const storeUserDetails = () => { const userDetails = toUserString(user); - const key = `user:${user.uid}`; + const key = redisActivityKeys.user(user.uid); debug(`about to store user details with key ${key}: ${userDetails}`); return ['set', key, userDetails, 'EX', userDetailsTtlSec]; }; + + const storeSocketActivity = () => { + const activityKey = redisActivityKeys[activity](caseId); + const key = redisActivityKeys.socket(socketId); + const store = JSON.stringify({ + activityKey, + caseId, + userId: user.uid + }); + return ['set', key, store, 'EX', userDetailsTtlSec]; + }; + return redis.pipeline([ storeUserActivity(), + storeSocketActivity(), storeUserDetails() - ]).exec().then(() => { - redis.publish(redisActivityKeys.base(caseId), Date.now().toString()); + ]).exec().then(async () => { + redis.publish(redisActivityKeys.baseCase(caseId), Date.now().toString()); }); }; - /** - * TODO: Implement a mechanism to remove activity. There are a few options here: - * I think this will require us to track which activities relates to which sockets - * so we can clear them whenever the socket disconnects. - * @param {*} caseId - * @param {*} user - * @param {*} activity - * @returns - */ - const removeActivity = (caseId, user, activity) => { - const removeUserActivity = () => { - const key = redisActivityKeys[activity](caseId); - debug(`about to remove user activity with key: ${key}`); - return ['zrem', key, user.uid]; - }; + const getSocketActivity = async (socketId) => { + const key = redisActivityKeys.socket(socketId); + return JSON.parse(await redis.get(key)); + }; - return redis.pipeline([ - removeUserActivity() - ]).exec().then(() => { - redis.publish(redisActivityKeys.base(caseId), Date.now().toString()); - }); + const removeSocketActivity = async (socketId) => { + const activity = await getSocketActivity(socketId); + if (activity) { + const removeUserActivity = () => { + return ['zrem', activity.activityKey, activity.userId]; + }; + const removeSocketEntry = () => { + return ['del', redisActivityKeys.socket(socketId)]; + }; + return redis.pipeline([ + removeUserActivity(), + removeSocketEntry() + ]).exec().then(() => { + redis.publish(redisActivityKeys.baseCase(activity.caseId), Date.now().toString()); + }); + } + return null; }; const getActivityForCases = async (caseIds) => { @@ -64,12 +80,19 @@ module.exports = (config, redis, ttlScoreGenerator) => { let caseViewers = []; let caseEditors = []; const now = Date.now(); - const getUserDetails = () => redis.pipeline(uniqueUserIds.map((userId) => ['get', `user:${userId}`])).exec(); + const getUserDetails = () => { + if (uniqueUserIds.length > 0) { + return redis.mget(uniqueUserIds.map((userId) => redisActivityKeys.user(userId)), (err, res) => { + return res; + }); + } + return []; + }; const extractUniqueUserIds = (result) => { if (result) { - result.forEach(item => { + result.forEach((item) => { if (item && item[1]) { - item[1].forEach(userId => { + item[1].forEach((userId) => { if (!uniqueUserIds.includes(userId)) { uniqueUserIds.push(userId); } @@ -79,17 +102,17 @@ module.exports = (config, redis, ttlScoreGenerator) => { } }; const caseViewersPromise = redis - .pipeline(caseIds.map(caseId => ['zrangebyscore', `case:${caseId}:viewers`, now, '+inf'])) + .pipeline(caseIds.map((caseId) => ['zrangebyscore', redisActivityKeys.view(caseId), now, '+inf'])) .exec() - .then(result => { + .then((result) => { redis.logPipelineFailures(result, 'caseViewersPromise'); caseViewers = result; extractUniqueUserIds(result); }); const caseEditorsPromise = redis - .pipeline(caseIds.map(caseId => ['zrangebyscore', `case:${caseId}:editors`, now, '+inf'])) + .pipeline(caseIds.map((caseId) => ['zrangebyscore', redisActivityKeys.edit(caseId), now, '+inf'])) .exec() - .then(result => { + .then((result) => { redis.logPipelineFailures(result, 'caseEditorsPromise'); caseEditors = result; extractUniqueUserIds(result); @@ -97,24 +120,27 @@ module.exports = (config, redis, ttlScoreGenerator) => { await Promise.all([caseViewersPromise, caseEditorsPromise]); const userDetails = await getUserDetails().reduce((obj, item) => { - const user = JSON.parse(item[1]); + const user = JSON.parse(item); obj[user.id] = { forename: user.forename, surname: user.surname }; return obj; }, {}); return caseIds.map((caseId, index) => { redis.logPipelineFailures(userDetails, 'userDetails'); - const cv = caseViewers[index][1], ce = caseEditors[index][1]; - const viewers = cv ? cv.map(v => userDetails[v]) : []; - const editors = ce ? ce.map(e => userDetails[e]) : []; + const cv = caseViewers[index][1]; + const ce = caseEditors[index][1]; + const viewers = cv ? cv.map((v) => userDetails[v]) : []; + const editors = ce ? ce.map((e) => userDetails[e]) : []; return { caseId, - viewers: viewers.filter(v => !!v), - unknownViewers: viewers.filter(v => !v).length, - editors: editors.filter(e => !!e), - unknownEditors: editors.filter(e => !e).length + viewers: viewers.filter((v) => !!v), + unknownViewers: viewers.filter((v) => !v).length, + editors: editors.filter((e) => !!e), + unknownEditors: editors.filter((e) => !e).length }; }); }; - return { addActivity, removeActivity, getActivityForCases }; + return { + addActivity, getActivityForCases, getSocketActivity, removeSocketActivity + }; }; diff --git a/app/socket/index.js b/app/socket/index.js index 3357b3e4..4044d74d 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -1,8 +1,11 @@ +const debug = require('debug')('ccd-case-activity-api:socket'); const config = require('config'); +const IORouter = require('socket.io-router-middleware'); +const SocketIO = require('socket.io'); const ttlScoreGenerator = require('../service/ttl-score-generator'); const redisWatcher = require('./redis-watcher'); +const ActivityService = require('./activity-service'); -const IORouter = new require('socket.io-router-middleware'); const iorouter = new IORouter(); /** @@ -16,9 +19,9 @@ const iorouter = new IORouter(); * TODO: * 1. Use redis rather than holding the details in memory. * 2. Some sort of auth / get the credentials when the user connects. - * + * * Add view activity looks like this: - * addActivity 1588201414700270 { + * addActivity 1588201414700270, { sub: 'leeds_et@mailinator.com', uid: '85269805-3a70-419d-acab-193faeb89ad3', roles: [ @@ -29,30 +32,85 @@ const iorouter = new IORouter(); name: 'Ethos Leeds', given_name: 'Ethos', family_name: 'Leeds' - } view + }, '18hs67171jak', 'view' * */ module.exports = (server, redis) => { - const activityService = require('./activity-service')(config, redis, ttlScoreGenerator); - const io = require('socket.io')(server, { + const activityService = ActivityService(config, redis, ttlScoreGenerator); + const io = SocketIO(server, { allowEIO3: true }); + function toUser(obj) { + return { + sub: `${obj.name.replace(' ', '.')}@mailinator.com`, + uid: obj.id, + roles: [ + 'caseworker-employment', + 'caseworker-employment-leeds', + 'caseworker' + ], + name: obj.name, + given_name: obj.name.split(' ')[0], + family_name: obj.name.split(' ')[1] + }; + } + function watchCase(socket, caseId) { + socket.join(`case:${caseId}`); + } + function watchCases(socket, caseIds) { + caseIds.forEach((caseId) => { + watchCase(socket, caseId); + }); + } + function stopWatchingCases(socket) { + [...socket.rooms].filter((r) => r.indexOf('case:') === 0).forEach((r) => socket.leave(r)); + } async function whenActivityForCases(caseIds) { - const caseActivty = await activityService.getActivityForCases(caseIds); - console.log('activity for cases', caseIds, caseActivty); - return caseActivty; + return activityService.getActivityForCases(caseIds); } async function notifyWatchers(caseIds) { - caseIds = Array.isArray(caseIds) ? caseIds : [caseIds]; - caseIds.sort().forEach(async (caseId) => { + const ids = Array.isArray(caseIds) ? caseIds : [caseIds]; + ids.sort().forEach(async (caseId) => { const cs = await whenActivityForCases([caseId]); - io.to(`case:${caseId}`).emit('cases', cs); + io.to(`case:${caseId}`).emit('activity', cs); }); } + async function handleViewOrEdit(socket, caseId, user, activity) { + // Leave all the case rooms. + stopWatchingCases(socket); + + // Remove the activity for this socket. + activityService.removeSocketActivity(socket.id); + + // Now watch this case again. + watchCase(socket, caseId); + + // Finally, add this new activity to redis. + activityService.addActivity(caseId, toUser(user), socket.id, activity); + } + function handleEdit(socket, caseId, user) { + handleViewOrEdit(socket, caseId, user, 'edit'); + } + function handleView(socket, caseId, user) { + handleViewOrEdit(socket, caseId, user, 'view'); + } + async function handleWatch(socket, caseIds) { + // Stop watching the current cases. + stopWatchingCases(socket); + + // Remove the activity for this socket. + activityService.removeSocketActivity(socket.id); + + // Now watch the specified cases. + watchCases(socket, caseIds); + + // And immediately dispatch a message about the activity on those cases. + const cs = await whenActivityForCases(caseIds); + socket.emit('activity', cs); + } // TODO: Track this stuff in redis. - const caseStatuses = {}; const socketUsers = {}; // Pretty way of logging. @@ -62,34 +120,17 @@ module.exports = (server, redis) => { if (payload) { text = `${text} => ${payload}`; } - console.log(text); + debug(text); } else { - console.group(text); - console.log(payload); - console.groupEnd(); + debug(text); + debug(payload); } } - redisWatcher.on('message', room => { + redisWatcher.psubscribe('case:*'); + redisWatcher.on('pmessage', (_, room) => { const caseId = room.replace('case:', ''); - console.log('redisWatcher.on.message', room, caseId); notifyWatchers([caseId]); - // io.to(room).emit(message); - }); - - - // When a new room is created, we want to watch for changes to that case. - io.of('/').adapter.on('create-room', (room) => { - console.log(`room ${room} was created`); - if (room.indexOf('case:') === 0) { - redisWatcher.subscribe(`${room}`); - } - }); - - io.of('/').adapter.on('delete-room', (room) => { - if (room.indexOf('case:') === 0) { - redisWatcher.unsubscribe(`${room}`); - } }); // Set up routes for each type of message. @@ -109,9 +150,6 @@ module.exports = (server, redis) => { const user = socketUsers[socket.id]; doLog(socket, `${ctx.request.caseId} (${user.name})`, 'view'); handleView(socket, ctx.request.caseId, user); - - // Try sticking it in redis. - activityService.addActivity(ctx.request.caseId, toUser(user), 'view'); next(); }); @@ -119,9 +157,6 @@ module.exports = (server, redis) => { const user = socketUsers[socket.id]; doLog(socket, `${ctx.request.caseId} (${user.name})`, 'edit'); handleEdit(socket, ctx.request.caseId, user); - - // Try sticking it in redis. - activityService.addActivity(ctx.request.caseId, toUser(user), 'edit'); next(); }); @@ -133,141 +168,24 @@ module.exports = (server, redis) => { }); // On client connection attach the router - io.on('connection', function (socket) { + io.on('connection', (socket) => { socket.use((packet, next) => { // Call router.attach() with the client socket as the first parameter iorouter.attach(socket, packet, next); }); }); - function handleEdit(socket, caseId, user) { - const stoppedViewing = stopViewingCases(socket.id); - if (stoppedViewing.length > 0) { - stoppedViewing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); - } - const stoppedEditing = stopEditingCases(socket.id, caseId); - if (stoppedEditing.length > 0) { - stoppedEditing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); - } - watchCase(socket, caseId); - const caseStatus = caseStatuses[caseId] || { viewers: [], editors: [] }; - caseStatuses[caseId] = caseStatus; - const matchingEditor = caseStatus.editors.find(e => e.id === user.id); - const notify = stoppedViewing.concat(stoppedEditing); - if (!matchingEditor) { - caseStatus.editors.push({ ...user, socketId: socket.id }); - notify.push(caseId); - } - if (notify.length > 0) { - notifyWatchers([ ...new Set(notify) ]); - } - } - - function handleView(socket, caseId, user) { - const stoppedViewing = stopViewingCases(socket.id, caseId); - if (stoppedViewing.length > 0) { - stoppedViewing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); - } - const stoppedEditing = stopEditingCases(socket.id); - if (stoppedEditing.length > 0) { - stoppedEditing.filter(c => c !== caseId).forEach(c => stopWatchingCase(socket, c)); - } - watchCase(socket, caseId); - const caseStatus = caseStatuses[caseId] || { viewers: [], editors: [] }; - caseStatuses[caseId] = caseStatus; - const matchingViewer = caseStatus.viewers.find(v => v.id === user.id); - const notify = stoppedViewing.concat(stoppedEditing); - if (!matchingViewer) { - caseStatus.viewers.push({ ...user, socketId: socket.id }); - notify.push(caseId); - } - if (notify.length > 0) { - notifyWatchers([ ...new Set(notify) ]); - } - } - - async function handleWatch(socket, caseIds) { - watchCases(socket, caseIds); - const cs = await whenActivityForCases(caseIds); - socket.emit('cases', cs); - } - - function watchCases(socket, caseIds) { - caseIds.forEach(caseId => { - watchCase(socket, caseId); - }); - } - function watchCase(socket, caseId) { - socket.join(`case:${caseId}`); - } - function stopWatchingCase(socket, caseId) { - socket.leave(`case:${caseId}`); - } - function toUser(obj) { - return { - sub: `${obj.name.replace(' ', '.')}@mailinator.com`, - uid: obj.id, - roles: [ - 'caseworker-employment', - 'caseworker-employment-leeds', - 'caseworker' - ], - name: obj.name, - given_name: obj.name.split(' ')[0], - family_name: obj.name.split(' ')[1] - }; - } - - function stopViewingOrEditing(socketId, exceptCaseId) { - const stoppedViewing = stopViewingCases(socketId, exceptCaseId); - const stoppedEditing = stopEditingCases(socketId, exceptCaseId); - return { stoppedViewing, stoppedEditing }; - } - function stopViewingCases(socketId, exceptCaseId) { - const affectedCases = []; - Object.keys(caseStatuses).filter(key => key !== exceptCaseId).forEach(key => { - const c = caseStatuses[key]; - const viewer = c.viewers.find(v => v.socketId === socketId); - if (viewer) { - c.viewers.splice(c.viewers.indexOf(viewer), 1); - affectedCases.push(key); - } - }); - return affectedCases; - } - function stopEditingCases(socketId, exceptCaseId) { - const affectedCases = []; - Object.keys(caseStatuses).filter(key => key !== exceptCaseId).forEach(key => { - const c = caseStatuses[key]; - const editor = c.editors.find(e => e.socketId === socketId); - if (editor) { - c.editors.splice(c.editors.indexOf(editor), 1); - affectedCases.push(key); - } - }); - return affectedCases; - } - const connections = []; - io.sockets.on("connection", (socket) => { + io.sockets.on('connection', (socket) => { connections.push(socket); - if (connections.length === 1) { - console.log("1 socket connected"); - } else { - console.log("%s sockets connected", connections.length); - } - // console.log('connections[0]', connections[0]); - socket.on("disconnect", () => { - console.log(socket.id, 'has disconnected'); - stopViewingOrEditing(socket.id); + doLog(socket, '', `connected (${connections.length} total)`); + socket.on('disconnect', () => { + doLog(socket, '', `disconnected (${connections.length - 1} total)`); + activityService.removeSocketActivity(socket.id); delete socketUsers[socket.id]; connections.splice(connections.indexOf(socket), 1); }); - socket.on("sending message", (message) => { - console.log("Message is received :", message); - io.sockets.emit("new message", { message: message }); - }); }); return io; -}; \ No newline at end of file +}; diff --git a/server.js b/server.js index 6a4447ce..88df8c5c 100755 --- a/server.js +++ b/server.js @@ -13,7 +13,6 @@ var http = require('http'); /** * Get port from environment and store in Express. */ - var port = normalizePort(process.env.PORT || '3460'); console.log('Starting on port ' + port); app.app.set('port', port); diff --git a/test/e2e/utils/activity-store-commands.js b/test/e2e/utils/activity-store-commands.js index dd14149e..a0e89a44 100644 --- a/test/e2e/utils/activity-store-commands.js +++ b/test/e2e/utils/activity-store-commands.js @@ -1,12 +1,11 @@ var redis = require('../../../app/redis/redis-client') -var moment = require('moment') exports.getAllCaseViewers = (caseId) => redis.zrangebyscore(`case:${caseId}:viewers`, '-inf', '+inf') -exports.getNotExpiredCaseViewers = (caseId) => redis.zrangebyscore(`case:${caseId}:viewers`, moment().valueOf(), '+inf') +exports.getNotExpiredCaseViewers = (caseId) => redis.zrangebyscore(`case:${caseId}:viewers`, Date.now(), '+inf') exports.getAllCaseEditors = (caseId) => redis.zrangebyscore(`case:${caseId}:editors`, '-inf', '+inf') -exports.getNotExpiredCaseEditors = (caseId) => redis.zrangebyscore(`case:${caseId}:editors`, moment().valueOf(), '+inf') +exports.getNotExpiredCaseEditors = (caseId) => redis.zrangebyscore(`case:${caseId}:editors`, Date.now(), '+inf') exports.getUser = (id) => redis.get(`user:${id}`) \ No newline at end of file diff --git a/test/spec/app/health/health-check.spec.js b/test/spec/app/health/health-check.spec.js index 9efb6c9c..e85f7054 100644 --- a/test/spec/app/health/health-check.spec.js +++ b/test/spec/app/health/health-check.spec.js @@ -5,7 +5,7 @@ const app = require('../../../../app'); describe('health check', () => { it('should return 200 OK for health check', async () => { - await request(app) + await request(app.app) .get('/health') .expect(res => { expect(res.status).equal(200); @@ -14,7 +14,7 @@ describe('health check', () => { }); it('should return 200 OK for liveness health check', async () => { - await request(app) + await request(app.app) .get('/health/liveness') .expect(res => { expect(res.status).equal(200); @@ -23,7 +23,7 @@ describe('health check', () => { }); it('should return 200 OK for readiness health check', async () => { - await request(app) + await request(app.app) .get('/health/readiness') .expect(res => { expect(res.status).equal(200); diff --git a/test/spec/app/service/activity-service.spec.js b/test/spec/app/service/activity-service.spec.js index 6ef91266..ef73e385 100644 --- a/test/spec/app/service/activity-service.spec.js +++ b/test/spec/app/service/activity-service.spec.js @@ -2,7 +2,6 @@ var redis = require('../../../../app/redis/redis-client'); var config = require('config'); var ttlScoreGenerator = require('../../../../app/service/ttl-score-generator'); var activityService = require('../../../../app/service/activity-service')(config, redis, ttlScoreGenerator); -var moment = require('moment'); var chai = require("chai"); var sinon = require("sinon"); var sinonChai = require("sinon-chai"); @@ -54,7 +53,8 @@ describe("activity service", () => { }); it("getActivities should create a redis pipeline with the correct redis commands for getViewers", (done) => { - sandbox.stub(moment, 'now').returns(TIMESTAMP); + sandbox.stub(Date, 'now').returns(TIMESTAMP); + // sandbox.stub(moment, 'now').returns(TIMESTAMP); sandbox.stub(config, 'get').returns(USER_DETAILS_TTL); sandbox.stub(redis, "pipeline").callsFake(function (arguments) { argStr = JSON.stringify(arguments); @@ -91,7 +91,7 @@ describe("activity service", () => { }) it("getActivities should return unknown users if users detail are missing", (done) => { - sandbox.stub(moment, 'now').returns(TIMESTAMP); + sandbox.stub(Date, 'now').returns(TIMESTAMP); sandbox.stub(config, 'get').returns(USER_DETAILS_TTL); sandbox.stub(redis, "pipeline").callsFake(function (arguments) { argStr = JSON.stringify(arguments); @@ -125,7 +125,7 @@ describe("activity service", () => { }) it("getActivities should not return in the list of viewers the requesting user id", (done) => { - sandbox.stub(moment, 'now').returns(TIMESTAMP); + sandbox.stub(Date, 'now').returns(TIMESTAMP); sandbox.stub(config, 'get').returns(USER_DETAILS_TTL); sandbox.stub(redis, "pipeline").callsFake(function (arguments) { argStr = JSON.stringify(arguments); @@ -159,7 +159,7 @@ describe("activity service", () => { }) it("getActivities should not return the requesting user id in the list of unknown viewers", (done) => { - sandbox.stub(moment, 'now').returns(TIMESTAMP); + sandbox.stub(Date, 'now').returns(TIMESTAMP); sandbox.stub(config, 'get').returns(USER_DETAILS_TTL); sandbox.stub(redis, "pipeline").callsFake(function (arguments) { argStr = JSON.stringify(arguments); From 93ae2ae250e612fa9fa66f86dd0b2987a4386d1a Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Mon, 24 May 2021 17:40:38 +0100 Subject: [PATCH 009/113] Update activity-service.js Removed a redundant callback. --- app/socket/activity-service.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/socket/activity-service.js b/app/socket/activity-service.js index a4ac61ac..82b59be7 100644 --- a/app/socket/activity-service.js +++ b/app/socket/activity-service.js @@ -82,9 +82,7 @@ module.exports = (config, redis, ttlScoreGenerator) => { const now = Date.now(); const getUserDetails = () => { if (uniqueUserIds.length > 0) { - return redis.mget(uniqueUserIds.map((userId) => redisActivityKeys.user(userId)), (err, res) => { - return res; - }); + return redis.mget(uniqueUserIds.map((userId) => redisActivityKeys.user(userId))); } return []; }; @@ -140,6 +138,7 @@ module.exports = (config, redis, ttlScoreGenerator) => { }; }); }; + return { addActivity, getActivityForCases, getSocketActivity, removeSocketActivity }; From f479ba11e589f5f88e6f8201fecceb488ff96c72 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Mon, 24 May 2021 17:40:51 +0100 Subject: [PATCH 010/113] Update package.json Added an option for starting with debug output. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 6a1d9954..c3522393 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "scripts": { "setup": "cross-env NODE_PATH=. node --version", "start": "cross-env NODE_PATH=. node server.js", + "start:debug": "DEBUG=ccd-case-activity-api:* yarn start", "test": "NODE_ENV=test mocha --exit --recursive test/spec app/user", "test:end2end": "NODE_ENV=test mocha --exit test/e2e --timeout 15000", "test:smoke": "./aat/gradlew -p aat smoke", From 9396497f1b6592f4a110d8944d5236623b8a4638 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Tue, 25 May 2021 09:31:22 +0100 Subject: [PATCH 011/113] Update activity-service.js According to the docs, a pipeline is likely to be more performant so reverting back to that. For very low numbers, I think an mget will be quicker but we know we may well have thousands of concurrent users so we should account for that. --- app/socket/activity-service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/socket/activity-service.js b/app/socket/activity-service.js index 82b59be7..b572f42c 100644 --- a/app/socket/activity-service.js +++ b/app/socket/activity-service.js @@ -82,7 +82,7 @@ module.exports = (config, redis, ttlScoreGenerator) => { const now = Date.now(); const getUserDetails = () => { if (uniqueUserIds.length > 0) { - return redis.mget(uniqueUserIds.map((userId) => redisActivityKeys.user(userId))); + return redis.pipeline(uniqueUserIds.map((userId) => ['get', redisActivityKeys.user(userId)])).exec(); } return []; }; @@ -118,7 +118,7 @@ module.exports = (config, redis, ttlScoreGenerator) => { await Promise.all([caseViewersPromise, caseEditorsPromise]); const userDetails = await getUserDetails().reduce((obj, item) => { - const user = JSON.parse(item); + const user = JSON.parse(item[1]); obj[user.id] = { forename: user.forename, surname: user.surname }; return obj; }, {}); From 38f84ff76d59fdb538bd4e47e344c3934b11b685 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Tue, 25 May 2021 09:36:14 +0100 Subject: [PATCH 012/113] Update index.js Added cors properties to the socket.io creation. --- app/socket/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/socket/index.js b/app/socket/index.js index 4044d74d..126d7920 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -38,7 +38,11 @@ const iorouter = new IORouter(); module.exports = (server, redis) => { const activityService = ActivityService(config, redis, ttlScoreGenerator); const io = SocketIO(server, { - allowEIO3: true + allowEIO3: true, + cors: { + origin: '*', + methods: ['GET', 'POST'] + } }); function toUser(obj) { return { From 4bcbf057dcc7855869697a3fbb8a06cb55892a9e Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Tue, 25 May 2021 10:08:04 +0100 Subject: [PATCH 013/113] lint Trying to get lint working on Windows too. --- .eslintrc.js | 13 +++++++++++++ .eslintrc.yml | 8 -------- package.json | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 .eslintrc.js delete mode 100644 .eslintrc.yml diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..dc511611 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,13 @@ +module.exports = { + "extends": "airbnb-base", + "env": { + "mocha": true, + "jasmine": true + }, + "rules": { + "comma-dangle": 0, + "arrow-body-style": 0, + "no-param-reassign": [ 2, { props: false } ], + "linebreak-style": [ "error", process.platform === 'win32' ? 'windows' : 'unix' ] + } +} diff --git a/.eslintrc.yml b/.eslintrc.yml deleted file mode 100644 index 9f5ce475..00000000 --- a/.eslintrc.yml +++ /dev/null @@ -1,8 +0,0 @@ -extends: airbnb-base -env: - mocha: true - jasmine: true -rules: - comma-dangle: 0 - arrow-body-style: 0 - no-param-reassign: [ 2, { props: false } ] diff --git a/package.json b/package.json index c3522393..97ba0c01 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "test:a11y": "echo 'TODO: Accessibility tests'", "sonar-scan": "NODE_PATH=. sonar-scanner -X", "highLevelDataSetup": "echo './aat/gradlew -p aat highLevelDataSetup --args=$1' > ./temp.sh && sh ./temp.sh", - "lint": "NODE_PATH=. eslint app.js app/**/*.js test/**/*.js", + "lint": "cross-env NODE_PATH=. eslint app.js app/**/*.js test/**/*.js", "lint-fix": "NODE_PATH=. eslint --fix app.js app/**/*.js test/**/*.js" }, "nyc": { From 8d853afce04c1f90a23769499a13bcf5f6ee09c2 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Tue, 25 May 2021 14:10:13 +0100 Subject: [PATCH 014/113] Update instantiator.js Getting rid of a pointless debug. --- app/redis/instantiator.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/redis/instantiator.js b/app/redis/instantiator.js index cad8a545..c8b45947 100644 --- a/app/redis/instantiator.js +++ b/app/redis/instantiator.js @@ -23,9 +23,6 @@ module.exports = (debug) => { const operationsFailureOutcome = plOutcome.map((operationOutcome) => operationOutcome[ERROR]); const failures = operationsFailureOutcome.filter((element) => element !== null); failures.forEach((f) => debug(`${message}: ${f}`)); - } else { - debug(`${plOutcome} is not an Array...`); - debug(`${JSON.stringify(plOutcome)}`); } return plOutcome; }; From 569d85b6a391cf0f631f9aaeb082920815ed8d60 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Tue, 25 May 2021 14:11:37 +0100 Subject: [PATCH 015/113] Refactoring Fixed the mechanism for clearing out activity on a socket before adding new activity and then refactored `socket/activity-service.js` to make it a bit more functional and easier to test. --- app/socket/activity-service.js | 217 +++++++++++++++++++-------------- app/socket/index.js | 13 +- 2 files changed, 130 insertions(+), 100 deletions(-) diff --git a/app/socket/activity-service.js b/app/socket/activity-service.js index b572f42c..2d49f51d 100644 --- a/app/socket/activity-service.js +++ b/app/socket/activity-service.js @@ -1,75 +1,127 @@ const debug = require('debug')('ccd-case-activity-api:activity-service'); -module.exports = (config, redis, ttlScoreGenerator) => { - const redisActivityKeys = { - view: (caseId) => `case:${caseId}:viewers`, - edit: (caseId) => `case:${caseId}:editors`, - baseCase: (caseId) => `case:${caseId}`, - user: (userId) => `user:${userId}`, - socket: (socketId) => `socket:${socketId}` - }; - const userDetailsTtlSec = config.get('redis.userDetailsTtlSec'); - const toUserString = (user) => { +const redisActivityKeys = { + view: (caseId) => `case:${caseId}:viewers`, + edit: (caseId) => `case:${caseId}:editors`, + baseCase: (caseId) => `case:${caseId}`, + user: (userId) => `user:${userId}`, + socket: (socketId) => `socket:${socketId}` +}; +const utils = { + toUserString: (user) => { return JSON.stringify({ id: user.uid, forename: user.given_name, surname: user.family_name }); - }; - - const addActivity = (caseId, user, socketId, activity) => { - const storeUserActivity = () => { - const key = redisActivityKeys[activity](caseId); - debug(`about to store user activity with key: ${key}`); - return ['zadd', key, ttlScoreGenerator.getScore(), user.uid]; - }; - - const storeUserDetails = () => { - const userDetails = toUserString(user); + }, + extractUniqueUserIds: (result, uniqueUserIds) => { + if (result) { + result.forEach((item) => { + if (item && item[1]) { + const users = item[1]; + users.forEach((userId) => { + if (!uniqueUserIds.includes(userId)) { + uniqueUserIds.push(userId); + } + }); + } + }); + } + }, + get: { + caseActivities: (caseIds, activity, now) => { + return caseIds.map((id) => ['zrangebyscore', redisActivityKeys[activity](id), now, '+inf']); + }, + users: (userIds) => { + return userIds.map((id) => ['get', redisActivityKeys.user(id)]); + } + }, + store: { + userActivity: (activityKey, userId, score) => { + debug(`about to store activity "${activityKey}" for user "${userId}"`); + return ['zadd', activityKey, score, userId]; + }, + userDetails: (user, ttl) => { const key = redisActivityKeys.user(user.uid); - debug(`about to store user details with key ${key}: ${userDetails}`); - return ['set', key, userDetails, 'EX', userDetailsTtlSec]; - }; - - const storeSocketActivity = () => { - const activityKey = redisActivityKeys[activity](caseId); + const store = utils.toUserString(user); + debug(`about to store details "${key}" for user "${user.uid}": ${store}`); + return ['set', key, store, 'EX', ttl]; + }, + socketActivity: (socketId, activityKey, caseId, userId, ttl) => { const key = redisActivityKeys.socket(socketId); - const store = JSON.stringify({ - activityKey, - caseId, - userId: user.uid - }); - return ['set', key, store, 'EX', userDetailsTtlSec]; - }; + const store = JSON.stringify({ activityKey, caseId, userId }); + debug(`about to store activity "${key}" for socket "${socketId}": ${store}`); + return ['set', key, store, 'EX', ttl]; + } + }, + remove: { + userActivity: (activity) => { + debug(`about to remove activity "${activity.activityKey}" for user "${activity.userId}"`); + return ['zrem', activity.activityKey, activity.userId]; + }, + socketEntry: (socketId) => { + debug(`about to remove activity for socket "${socketId}"`); + return ['del', redisActivityKeys.socket(socketId)]; + } + } +} - return redis.pipeline([ - storeUserActivity(), - storeSocketActivity(), - storeUserDetails() - ]).exec().then(async () => { - redis.publish(redisActivityKeys.baseCase(caseId), Date.now().toString()); - }); - }; +module.exports = (config, redis, ttlScoreGenerator) => { + const userDetailsTtlSec = config.get('redis.userDetailsTtlSec'); + + const notifyChange = (caseId) => { + redis.publish(redisActivityKeys.baseCase(caseId), Date.now().toString()); + } const getSocketActivity = async (socketId) => { const key = redisActivityKeys.socket(socketId); return JSON.parse(await redis.get(key)); }; - const removeSocketActivity = async (socketId) => { + const getUserDetails = async (userIds) => { + if (userIds.length > 0) { + // Get hold of the details. + const details = await redis.pipeline(utils.get.users(userIds)).exec(); + + // Now turn them into a map. + return details.reduce((obj, item) => { + const user = JSON.parse(item[1]); + if (user) { + obj[user.id] = { forename: user.forename, surname: user.surname }; + } + return obj; + }, {}); + } + return []; + }; + + const addActivity = async (caseId, user, socketId, activity) => { + // First, clear out any existing activity on this socket. + await removeSocketActivity(socketId, caseId); + + // Now store this activity. + const activityKey = redisActivityKeys[activity](caseId); + return redis.pipeline([ + utils.store.userActivity(activityKey, user.uid, ttlScoreGenerator.getScore()), + utils.store.socketActivity(socketId, activityKey, caseId, user.uid, userDetailsTtlSec), + utils.store.userDetails(user, userDetailsTtlSec) + ]).exec().then(() => { + notifyChange(caseId); + }); + }; + + const removeSocketActivity = async (socketId, skipNotifyForCaseId) => { + // First make sure we actually have some activity to remove. const activity = await getSocketActivity(socketId); if (activity) { - const removeUserActivity = () => { - return ['zrem', activity.activityKey, activity.userId]; - }; - const removeSocketEntry = () => { - return ['del', redisActivityKeys.socket(socketId)]; - }; return redis.pipeline([ - removeUserActivity(), - removeSocketEntry() + utils.remove.userActivity(activity), + utils.remove.socketEntry(socketId) ]).exec().then(() => { - redis.publish(redisActivityKeys.baseCase(activity.caseId), Date.now().toString()); + if (activity.caseId !== skipNotifyForCaseId) { + notifyChange(activity.caseId); + } }); } return null; @@ -80,51 +132,32 @@ module.exports = (config, redis, ttlScoreGenerator) => { let caseViewers = []; let caseEditors = []; const now = Date.now(); - const getUserDetails = () => { - if (uniqueUserIds.length > 0) { - return redis.pipeline(uniqueUserIds.map((userId) => ['get', redisActivityKeys.user(userId)])).exec(); - } - return []; + const getPromise = (activity, cb, failureMessage) => { + return redis.pipeline( + utils.get.caseActivities(caseIds, activity, now) + ).exec().then((result) => { + redis.logPipelineFailures(result, failureMessage); + cb(result); + utils.extractUniqueUserIds(result, uniqueUserIds); + }); }; - const extractUniqueUserIds = (result) => { - if (result) { - result.forEach((item) => { - if (item && item[1]) { - item[1].forEach((userId) => { - if (!uniqueUserIds.includes(userId)) { - uniqueUserIds.push(userId); - } - }); - } - }); - } - }; - const caseViewersPromise = redis - .pipeline(caseIds.map((caseId) => ['zrangebyscore', redisActivityKeys.view(caseId), now, '+inf'])) - .exec() - .then((result) => { - redis.logPipelineFailures(result, 'caseViewersPromise'); - caseViewers = result; - extractUniqueUserIds(result); - }); - const caseEditorsPromise = redis - .pipeline(caseIds.map((caseId) => ['zrangebyscore', redisActivityKeys.edit(caseId), now, '+inf'])) - .exec() - .then((result) => { - redis.logPipelineFailures(result, 'caseEditorsPromise'); - caseEditors = result; - extractUniqueUserIds(result); - }); + + // Set up the promises fore view and edit. + const caseViewersPromise = getPromise('view', (result) => { + caseViewers = result; + }, 'caseViewersPromise'); + const caseEditorsPromise = getPromise('edit', (result) => { + caseEditors = result; + }, 'caseEditorsPromise'); + + // Now wait until both promises have been completed. await Promise.all([caseViewersPromise, caseEditorsPromise]); - const userDetails = await getUserDetails().reduce((obj, item) => { - const user = JSON.parse(item[1]); - obj[user.id] = { forename: user.forename, surname: user.surname }; - return obj; - }, {}); + // Get all the user details for both viewers and editors. + const userDetails = await getUserDetails(uniqueUserIds); + // Now produce a response for every case requested. return caseIds.map((caseId, index) => { - redis.logPipelineFailures(userDetails, 'userDetails'); const cv = caseViewers[index][1]; const ce = caseEditors[index][1]; const viewers = cv ? cv.map((v) => userDetails[v]) : []; diff --git a/app/socket/index.js b/app/socket/index.js index 126d7920..a0489602 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -81,17 +81,14 @@ module.exports = (server, redis) => { }); } async function handleViewOrEdit(socket, caseId, user, activity) { - // Leave all the case rooms. + // Leave all the existing case rooms. stopWatchingCases(socket); - // Remove the activity for this socket. - activityService.removeSocketActivity(socket.id); - - // Now watch this case again. + // Now watch this case specifically. watchCase(socket, caseId); - // Finally, add this new activity to redis. - activityService.addActivity(caseId, toUser(user), socket.id, activity); + // Finally, add this new activity to redis, which will also clear out the old activity. + await activityService.addActivity(caseId, toUser(user), socket.id, activity); } function handleEdit(socket, caseId, user) { handleViewOrEdit(socket, caseId, user, 'edit'); @@ -104,7 +101,7 @@ module.exports = (server, redis) => { stopWatchingCases(socket); // Remove the activity for this socket. - activityService.removeSocketActivity(socket.id); + await activityService.removeSocketActivity(socket.id); // Now watch the specified cases. watchCases(socket, caseIds); From 7cd2c17590c9b35e1e7dbc86344ebd59c954a8bf Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Tue, 25 May 2021 14:23:14 +0100 Subject: [PATCH 016/113] Update activity-service.js Linting. Oops. --- app/socket/activity-service.js | 36 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/app/socket/activity-service.js b/app/socket/activity-service.js index 2d49f51d..8c1638fe 100644 --- a/app/socket/activity-service.js +++ b/app/socket/activity-service.js @@ -65,14 +65,14 @@ const utils = { return ['del', redisActivityKeys.socket(socketId)]; } } -} +}; module.exports = (config, redis, ttlScoreGenerator) => { const userDetailsTtlSec = config.get('redis.userDetailsTtlSec'); const notifyChange = (caseId) => { redis.publish(redisActivityKeys.baseCase(caseId), Date.now().toString()); - } + }; const getSocketActivity = async (socketId) => { const key = redisActivityKeys.socket(socketId); @@ -96,21 +96,6 @@ module.exports = (config, redis, ttlScoreGenerator) => { return []; }; - const addActivity = async (caseId, user, socketId, activity) => { - // First, clear out any existing activity on this socket. - await removeSocketActivity(socketId, caseId); - - // Now store this activity. - const activityKey = redisActivityKeys[activity](caseId); - return redis.pipeline([ - utils.store.userActivity(activityKey, user.uid, ttlScoreGenerator.getScore()), - utils.store.socketActivity(socketId, activityKey, caseId, user.uid, userDetailsTtlSec), - utils.store.userDetails(user, userDetailsTtlSec) - ]).exec().then(() => { - notifyChange(caseId); - }); - }; - const removeSocketActivity = async (socketId, skipNotifyForCaseId) => { // First make sure we actually have some activity to remove. const activity = await getSocketActivity(socketId); @@ -127,6 +112,21 @@ module.exports = (config, redis, ttlScoreGenerator) => { return null; }; + const addActivity = async (caseId, user, socketId, activity) => { + // First, clear out any existing activity on this socket. + await removeSocketActivity(socketId, caseId); + + // Now store this activity. + const activityKey = redisActivityKeys[activity](caseId); + return redis.pipeline([ + utils.store.userActivity(activityKey, user.uid, ttlScoreGenerator.getScore()), + utils.store.socketActivity(socketId, activityKey, caseId, user.uid, userDetailsTtlSec), + utils.store.userDetails(user, userDetailsTtlSec) + ]).exec().then(() => { + notifyChange(caseId); + }); + }; + const getActivityForCases = async (caseIds) => { const uniqueUserIds = []; let caseViewers = []; @@ -139,7 +139,7 @@ module.exports = (config, redis, ttlScoreGenerator) => { redis.logPipelineFailures(result, failureMessage); cb(result); utils.extractUniqueUserIds(result, uniqueUserIds); - }); + }); }; // Set up the promises fore view and edit. From 2e4e5bc3b5cba39679a98f700509b804beaaa23d Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Tue, 25 May 2021 16:07:46 +0100 Subject: [PATCH 017/113] Tests Moved redis keys and utils into separate files. The former is fully tested, the latter still needs tests for store and remove. --- app/socket/activity-service.js | 79 +------ app/socket/redis-keys.js | 9 + app/socket/utils.js | 74 +++++++ test/spec/app/socket/redis-keys.spec.js | 31 +++ test/spec/app/socket/utils.spec.js | 263 ++++++++++++++++++++++++ 5 files changed, 385 insertions(+), 71 deletions(-) create mode 100644 app/socket/redis-keys.js create mode 100644 app/socket/utils.js create mode 100644 test/spec/app/socket/redis-keys.spec.js create mode 100644 test/spec/app/socket/utils.spec.js diff --git a/app/socket/activity-service.js b/app/socket/activity-service.js index 8c1638fe..bca905d4 100644 --- a/app/socket/activity-service.js +++ b/app/socket/activity-service.js @@ -1,71 +1,5 @@ -const debug = require('debug')('ccd-case-activity-api:activity-service'); - -const redisActivityKeys = { - view: (caseId) => `case:${caseId}:viewers`, - edit: (caseId) => `case:${caseId}:editors`, - baseCase: (caseId) => `case:${caseId}`, - user: (userId) => `user:${userId}`, - socket: (socketId) => `socket:${socketId}` -}; -const utils = { - toUserString: (user) => { - return JSON.stringify({ - id: user.uid, - forename: user.given_name, - surname: user.family_name - }); - }, - extractUniqueUserIds: (result, uniqueUserIds) => { - if (result) { - result.forEach((item) => { - if (item && item[1]) { - const users = item[1]; - users.forEach((userId) => { - if (!uniqueUserIds.includes(userId)) { - uniqueUserIds.push(userId); - } - }); - } - }); - } - }, - get: { - caseActivities: (caseIds, activity, now) => { - return caseIds.map((id) => ['zrangebyscore', redisActivityKeys[activity](id), now, '+inf']); - }, - users: (userIds) => { - return userIds.map((id) => ['get', redisActivityKeys.user(id)]); - } - }, - store: { - userActivity: (activityKey, userId, score) => { - debug(`about to store activity "${activityKey}" for user "${userId}"`); - return ['zadd', activityKey, score, userId]; - }, - userDetails: (user, ttl) => { - const key = redisActivityKeys.user(user.uid); - const store = utils.toUserString(user); - debug(`about to store details "${key}" for user "${user.uid}": ${store}`); - return ['set', key, store, 'EX', ttl]; - }, - socketActivity: (socketId, activityKey, caseId, userId, ttl) => { - const key = redisActivityKeys.socket(socketId); - const store = JSON.stringify({ activityKey, caseId, userId }); - debug(`about to store activity "${key}" for socket "${socketId}": ${store}`); - return ['set', key, store, 'EX', ttl]; - } - }, - remove: { - userActivity: (activity) => { - debug(`about to remove activity "${activity.activityKey}" for user "${activity.userId}"`); - return ['zrem', activity.activityKey, activity.userId]; - }, - socketEntry: (socketId) => { - debug(`about to remove activity for socket "${socketId}"`); - return ['del', redisActivityKeys.socket(socketId)]; - } - } -}; +const redisActivityKeys = require('./redis-keys'); +const utils = require('./utils'); module.exports = (config, redis, ttlScoreGenerator) => { const userDetailsTtlSec = config.get('redis.userDetailsTtlSec'); @@ -128,7 +62,7 @@ module.exports = (config, redis, ttlScoreGenerator) => { }; const getActivityForCases = async (caseIds) => { - const uniqueUserIds = []; + let uniqueUserIds = []; let caseViewers = []; let caseEditors = []; const now = Date.now(); @@ -138,7 +72,7 @@ module.exports = (config, redis, ttlScoreGenerator) => { ).exec().then((result) => { redis.logPipelineFailures(result, failureMessage); cb(result); - utils.extractUniqueUserIds(result, uniqueUserIds); + uniqueUserIds = utils.extractUniqueUserIds(result, uniqueUserIds); }); }; @@ -173,6 +107,9 @@ module.exports = (config, redis, ttlScoreGenerator) => { }; return { - addActivity, getActivityForCases, getSocketActivity, removeSocketActivity + addActivity, + getActivityForCases, + getSocketActivity, + removeSocketActivity }; }; diff --git a/app/socket/redis-keys.js b/app/socket/redis-keys.js new file mode 100644 index 00000000..7feafc1a --- /dev/null +++ b/app/socket/redis-keys.js @@ -0,0 +1,9 @@ +const redisActivityKeys = { + view: (caseId) => `case:${caseId}:viewers`, + edit: (caseId) => `case:${caseId}:editors`, + baseCase: (caseId) => `case:${caseId}`, + user: (userId) => `user:${userId}`, + socket: (socketId) => `socket:${socketId}` +}; + +module.exports = redisActivityKeys; diff --git a/app/socket/utils.js b/app/socket/utils.js new file mode 100644 index 00000000..9219f345 --- /dev/null +++ b/app/socket/utils.js @@ -0,0 +1,74 @@ +const debug = require('debug')('ccd-case-activity-api:socket-activity-service'); +const redisActivityKeys = require('./redis-keys'); + +const utils = { + toUserString: (user) => { + return user ? JSON.stringify({ + id: user.uid, + forename: user.given_name, + surname: user.family_name + }) : '{}'; + }, + extractUniqueUserIds: (result, uniqueUserIds) => { + const userIds = Array.isArray(uniqueUserIds) ? [...uniqueUserIds] : []; + if (Array.isArray(result)) { + result.forEach((item) => { + if (item && item[1]) { + const users = item[1]; + users.forEach((userId) => { + if (!userIds.includes(userId)) { + userIds.push(userId); + } + }); + } + }); + } + return userIds; + }, + get: { + caseActivities: (caseIds, activity, now) => { + if (Array.isArray(caseIds) && ['view', 'edit'].indexOf(activity) > -1) { + return caseIds.filter((id) => !!id).map((id) => { + return ['zrangebyscore', redisActivityKeys[activity](id), now, '+inf']; + }); + } + return []; + }, + users: (userIds) => { + if (Array.isArray(userIds)) { + return userIds.filter((id) => !!id).map((id) => ['get', redisActivityKeys.user(id)]); + } + return []; + } + }, + store: { + userActivity: (activityKey, userId, score) => { + debug(`about to store activity "${activityKey}" for user "${userId}"`); + return ['zadd', activityKey, score, userId]; + }, + userDetails: (user, ttl) => { + const key = redisActivityKeys.user(user.uid); + const store = utils.toUserString(user); + debug(`about to store details "${key}" for user "${user.uid}": ${store}`); + return ['set', key, store, 'EX', ttl]; + }, + socketActivity: (socketId, activityKey, caseId, userId, ttl) => { + const key = redisActivityKeys.socket(socketId); + const store = JSON.stringify({ activityKey, caseId, userId }); + debug(`about to store activity "${key}" for socket "${socketId}": ${store}`); + return ['set', key, store, 'EX', ttl]; + } + }, + remove: { + userActivity: (activity) => { + debug(`about to remove activity "${activity.activityKey}" for user "${activity.userId}"`); + return ['zrem', activity.activityKey, activity.userId]; + }, + socketEntry: (socketId) => { + debug(`about to remove activity for socket "${socketId}"`); + return ['del', redisActivityKeys.socket(socketId)]; + } + } +}; + +module.exports = utils; diff --git a/test/spec/app/socket/redis-keys.spec.js b/test/spec/app/socket/redis-keys.spec.js new file mode 100644 index 00000000..1441c506 --- /dev/null +++ b/test/spec/app/socket/redis-keys.spec.js @@ -0,0 +1,31 @@ +const expect = require('chai').expect; +const redisActivityKeys = require('../../../../app/socket/redis-keys'); + +describe('socket.redis-keys', () => { + + it('should get the correct key for viewing a case', () => { + const CASE_ID = '12345678'; + expect(redisActivityKeys.view(CASE_ID)).to.equal(`case:${CASE_ID}:viewers`); + }); + + it('should get the correct key for editing a case', () => { + const CASE_ID = '12345678'; + expect(redisActivityKeys.edit(CASE_ID)).to.equal(`case:${CASE_ID}:editors`); + }); + + it('should get the correct base key for a case', () => { + const CASE_ID = '12345678'; + expect(redisActivityKeys.baseCase(CASE_ID)).to.equal(`case:${CASE_ID}`); + }); + + it('should get the correct key for a user', () => { + const USER_ID = 'abcdef123456'; + expect(redisActivityKeys.user(USER_ID)).to.equal(`user:${USER_ID}`); + }); + + it('should get the correct key for a socket', () => { + const SOCKET_ID = 'zyxwvu987654'; + expect(redisActivityKeys.socket(SOCKET_ID)).to.equal(`socket:${SOCKET_ID}`); + }); + +}); diff --git a/test/spec/app/socket/utils.spec.js b/test/spec/app/socket/utils.spec.js new file mode 100644 index 00000000..94549274 --- /dev/null +++ b/test/spec/app/socket/utils.spec.js @@ -0,0 +1,263 @@ +const expect = require('chai').expect; +const utils = require('../../../../app/socket/utils'); +const redisActivityKeys = require('../../../../app/socket/redis-keys'); + +describe('socket.utils', () => { + + describe('toUserString', () => { + it('should handle a null user', () => { + expect(utils.toUserString(null)).to.equal('{}'); + }); + it('should handle an undefined user', () => { + expect(utils.toUserString(undefined)).to.equal('{}'); + }); + it('should handle an empty user', () => { + expect(utils.toUserString({})).to.equal('{}'); + }); + it('should handle a full user', () => { + const USER = { + uid: '1234567890', + given_name: 'Bob', + family_name: 'Smith' + }; + expect(utils.toUserString(USER)).to.equal('{"id":"1234567890","forename":"Bob","surname":"Smith"}'); + }); + it('should handle a user with a missing family name', () => { + const USER = { + uid: '1234567890', + given_name: 'Bob' + }; + expect(utils.toUserString(USER)).to.equal('{"id":"1234567890","forename":"Bob"}'); + }); + it('should handle a user with a missing given name', () => { + const USER = { + uid: '1234567890', + family_name: 'Smith' + }; + expect(utils.toUserString(USER)).to.equal('{"id":"1234567890","surname":"Smith"}'); + }); + it('should handle a user with a missing name', () => { + const USER = { + uid: '1234567890' + }; + expect(utils.toUserString(USER)).to.equal('{"id":"1234567890"}'); + }); + }); + + describe('extractUniqueUserIds', () => { + it('should handle a null result', () => { + const RESULT = null; + const UNIQUE = ['a']; + const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); + expect(IDS).to.be.an('array') + .that.has.lengthOf(1) + .and.that.includes('a'); + }); + it('should handle a result of the wrong type', () => { + const RESULT = 'bob'; + const UNIQUE = ['a']; + const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); + expect(IDS).to.be.an('array') + .that.has.lengthOf(1) + .and.that.includes('a'); + }); + it('should handle a result with the wrong structure', () => { + const RESULT = [ + ['bob'], + ['fred'] + ]; + const UNIQUE = ['a']; + const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); + expect(IDS).to.be.an('array') + .that.has.lengthOf(1) + .and.that.includes('a'); + }); + it('should handle a result containing nulls', () => { + const RESULT = [ + ['bob', ['b']], + ['fred', null] + ]; + const UNIQUE = ['a']; + const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); + expect(IDS).to.be.an('array') + .that.has.lengthOf(2) + .and.that.includes('a') + .and.that.includes('b'); + }); + it('should handle a result with the correct structure', () => { + const RESULT = [ + ['bob', ['b', 'g']], + ['fred', ['f']] + ]; + const UNIQUE = ['a']; + const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); + expect(IDS).to.be.an('array').that.has.lengthOf(4) + .and.that.includes('a') + .and.that.includes('b') + .and.that.includes('f') + .and.that.includes('g'); + }); + it('should handle a result with the correct structure but a null original array', () => { + const RESULT = [ + ['bob', ['b', 'g']], + ['fred', ['f']] + ]; + const UNIQUE = null; + const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); + expect(IDS).to.be.an('array').that.has.lengthOf(3) + .and.that.includes('b') + .and.that.includes('f') + .and.that.includes('g'); + }); + it('should handle a result with the correct structure but an original array of the wrong type', () => { + const RESULT = [ + ['bob', ['b', 'g']], + ['fred', ['f']] + ]; + const UNIQUE = 'a'; + const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); + expect(IDS).to.be.an('array').that.has.lengthOf(3) + .and.that.includes('b') + .and.that.includes('f') + .and.that.includes('g'); + }); + it('should strip out duplicates', () => { + const RESULT = [ + ['bob', ['a', 'b', 'g']], + ['fred', ['f', 'b']] + ]; + const UNIQUE = ['a']; + const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); + expect(IDS).to.be.an('array') + .and.that.includes('a') + .and.that.includes('b') + .and.that.includes('f') + .and.that.includes('g') + .but.that.has.lengthOf(4); // One of each, despite the RESULT containing an extra 'a', and 'b' twice. + }); + }); + + describe('get', () => { + + describe('caseActivities', () => { + it('should get the correct result for a single case being viewed', () => { + const CASE_IDS = ['1']; + const ACTIVITY = 'view'; + const NOW = 999; + const pipes = utils.get.caseActivities(CASE_IDS, ACTIVITY, NOW); + expect(pipes).to.be.an('array').and.have.lengthOf(1); + expect(pipes[0]).to.be.an('array').and.have.lengthOf(4); + expect(pipes[0][0]).to.equal('zrangebyscore'); + expect(pipes[0][1]).to.equal(redisActivityKeys.view(CASE_IDS[0])); + expect(pipes[0][2]).to.equal(NOW); + expect(pipes[0][3]).to.equal('+inf'); + }); + it('should get the correct result for a multiple cases being viewed', () => { + const CASE_IDS = ['1', '8', '2345678', 'x']; + const ACTIVITY = 'view'; + const NOW = 999; + const pipes = utils.get.caseActivities(CASE_IDS, ACTIVITY, NOW); + expect(pipes).to.be.an('array').and.have.lengthOf(CASE_IDS.length); + CASE_IDS.forEach((id, index) => { + expect(pipes[index]).to.be.an('array').and.have.lengthOf(4); + expect(pipes[index][0]).to.equal('zrangebyscore'); + expect(pipes[index][1]).to.equal(redisActivityKeys.view(id)); + expect(pipes[index][2]).to.equal(NOW); + expect(pipes[index][3]).to.equal('+inf'); + }); + }); + it('should handle a null case ID for cases being viewed', () => { + const CASE_IDS = ['1', '8', null, 'x']; + const ACTIVITY = 'view'; + const NOW = 999; + const pipes = utils.get.caseActivities(CASE_IDS, ACTIVITY, NOW); + expect(pipes).to.be.an('array').and.have.lengthOf(CASE_IDS.length - 1); + let pipeIndex = 0; + CASE_IDS.forEach((id) => { + if (id !== null) { + expect(pipes[pipeIndex]).to.be.an('array').and.have.lengthOf(4); + expect(pipes[pipeIndex][0]).to.equal('zrangebyscore'); + expect(pipes[pipeIndex][1]).to.equal(redisActivityKeys.view(id)); + expect(pipes[pipeIndex][2]).to.equal(NOW); + expect(pipes[pipeIndex][3]).to.equal('+inf'); + pipeIndex++; + } + }); + }); + it('should handle a null case ID for cases being edited', () => { + const CASE_IDS = ['1', '8', null, 'x']; + const ACTIVITY = 'edit'; + const NOW = 999; + const pipes = utils.get.caseActivities(CASE_IDS, ACTIVITY, NOW); + expect(pipes).to.be.an('array').and.have.lengthOf(CASE_IDS.length - 1); + let pipeIndex = 0; + CASE_IDS.forEach((id) => { + if (id !== null) { + expect(pipes[pipeIndex]).to.be.an('array').and.have.lengthOf(4); + expect(pipes[pipeIndex][0]).to.equal('zrangebyscore'); + expect(pipes[pipeIndex][1]).to.equal(redisActivityKeys.edit(id)); + expect(pipes[pipeIndex][2]).to.equal(NOW); + expect(pipes[pipeIndex][3]).to.equal('+inf'); + pipeIndex++; + } + }); + }); + it('should handle a null array of case IDs', () => { + const CASE_IDS = null; + const ACTIVITY = 'view'; + const NOW = 999; + const pipes = utils.get.caseActivities(CASE_IDS, ACTIVITY, NOW); + expect(pipes).to.be.an('array').and.have.lengthOf(0); + }); + it('should handle an invalid activity type', () => { + const CASE_IDS = ['1', '8', '2345678', 'x']; + const ACTIVITY = 'bob'; + const NOW = 999; + const pipes = utils.get.caseActivities(CASE_IDS, ACTIVITY, NOW); + expect(pipes).to.be.an('array').and.have.lengthOf(0); + }); + }); + + describe('users', () => { + it('should get the correct result for a single user ID', () => { + const USER_IDS = ['1']; + const pipes = utils.get.users(USER_IDS); + expect(pipes).to.be.an('array').and.have.lengthOf(1); + expect(pipes[0]).to.be.an('array').and.have.lengthOf(2); + expect(pipes[0][0]).to.equal('get'); + expect(pipes[0][1]).to.equal(redisActivityKeys.user(USER_IDS[0])); + }); + it('should get the correct result for multiple user IDs', () => { + const USER_IDS = ['1', '8', '2345678', 'x']; + const pipes = utils.get.users(USER_IDS); + expect(pipes).to.be.an('array').and.have.lengthOf(USER_IDS.length); + expect(pipes[0]).to.be.an('array').and.have.lengthOf(2); + USER_IDS.forEach((id, index) => { + expect(pipes[index][0]).to.equal('get'); + expect(pipes[index][1]).to.equal(redisActivityKeys.user(id)); + }); + }); + it('should handle a null user ID', () => { + const USER_IDS = ['1', '8', null, 'x']; + const pipes = utils.get.users(USER_IDS); + expect(pipes).to.be.an('array').and.have.lengthOf(USER_IDS.length - 1); + expect(pipes[0]).to.be.an('array').and.have.lengthOf(2); + let pipeIndex = 0; + USER_IDS.forEach((id) => { + if (id) { + expect(pipes[pipeIndex][0]).to.equal('get'); + expect(pipes[pipeIndex][1]).to.equal(redisActivityKeys.user(id)); + pipeIndex++; + } + }); + }); + it('should handle a null array of user IDs', () => { + const USER_IDS = null; + const pipes = utils.get.users(USER_IDS); + expect(pipes).to.be.an('array').and.have.lengthOf(0); + }); + }); + + }); + +}); From 8c30b43a4d3b15a74f92326b6842de1f55be9571 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Wed, 26 May 2021 12:01:44 +0100 Subject: [PATCH 018/113] Unit tests Added a bunch of unit tests for `socket/utils` and, as a consequence, refactored some of that code to make it more robust and more testable. --- app/socket/activity-service.js | 13 +- app/socket/index.js | 118 +++------- app/socket/utils.js | 74 ------ app/socket/utils/get.js | 20 ++ app/socket/utils/index.js | 9 + app/socket/utils/other.js | 70 ++++++ app/socket/utils/remove.js | 15 ++ app/socket/utils/store.js | 24 ++ app/socket/utils/watch.js | 27 +++ test/spec/app/socket/utils.spec.js | 263 ---------------------- test/spec/app/socket/utils/get.spec.js | 130 +++++++++++ test/spec/app/socket/utils/index.spec.js | 244 ++++++++++++++++++++ test/spec/app/socket/utils/remove.spec.js | 36 +++ test/spec/app/socket/utils/store.spec.js | 57 +++++ test/spec/app/socket/utils/watch.spec.js | 140 ++++++++++++ 15 files changed, 806 insertions(+), 434 deletions(-) delete mode 100644 app/socket/utils.js create mode 100644 app/socket/utils/get.js create mode 100644 app/socket/utils/index.js create mode 100644 app/socket/utils/other.js create mode 100644 app/socket/utils/remove.js create mode 100644 app/socket/utils/store.js create mode 100644 app/socket/utils/watch.js delete mode 100644 test/spec/app/socket/utils.spec.js create mode 100644 test/spec/app/socket/utils/get.spec.js create mode 100644 test/spec/app/socket/utils/index.spec.js create mode 100644 test/spec/app/socket/utils/remove.spec.js create mode 100644 test/spec/app/socket/utils/store.spec.js create mode 100644 test/spec/app/socket/utils/watch.spec.js diff --git a/app/socket/activity-service.js b/app/socket/activity-service.js index bca905d4..e29c6742 100644 --- a/app/socket/activity-service.js +++ b/app/socket/activity-service.js @@ -1,8 +1,11 @@ const redisActivityKeys = require('./redis-keys'); const utils = require('./utils'); -module.exports = (config, redis, ttlScoreGenerator) => { - const userDetailsTtlSec = config.get('redis.userDetailsTtlSec'); +module.exports = (config, redis) => { + const ttl = { + user: config.get('redis.userDetailsTtlSec'), + activity: config.get('redis.activityTtlSec') + }; const notifyChange = (caseId) => { redis.publish(redisActivityKeys.baseCase(caseId), Date.now().toString()); @@ -53,9 +56,9 @@ module.exports = (config, redis, ttlScoreGenerator) => { // Now store this activity. const activityKey = redisActivityKeys[activity](caseId); return redis.pipeline([ - utils.store.userActivity(activityKey, user.uid, ttlScoreGenerator.getScore()), - utils.store.socketActivity(socketId, activityKey, caseId, user.uid, userDetailsTtlSec), - utils.store.userDetails(user, userDetailsTtlSec) + utils.store.userActivity(activityKey, user.uid, utils.score(ttl.activity)), + utils.store.socketActivity(socketId, activityKey, caseId, user.uid, ttl.user), + utils.store.userDetails(user, ttl.user) ]).exec().then(() => { notifyChange(caseId); }); diff --git a/app/socket/index.js b/app/socket/index.js index a0489602..1866f273 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -1,10 +1,10 @@ -const debug = require('debug')('ccd-case-activity-api:socket'); const config = require('config'); const IORouter = require('socket.io-router-middleware'); const SocketIO = require('socket.io'); -const ttlScoreGenerator = require('../service/ttl-score-generator'); -const redisWatcher = require('./redis-watcher'); + const ActivityService = require('./activity-service'); +const redisWatcher = require('./redis-watcher'); +const utils = require('./utils'); const iorouter = new IORouter(); @@ -36,7 +36,7 @@ const iorouter = new IORouter(); * */ module.exports = (server, redis) => { - const activityService = ActivityService(config, redis, ttlScoreGenerator); + const activityService = ActivityService(config, redis); const io = SocketIO(server, { allowEIO3: true, cors: { @@ -44,134 +44,68 @@ module.exports = (server, redis) => { methods: ['GET', 'POST'] } }); - function toUser(obj) { - return { - sub: `${obj.name.replace(' ', '.')}@mailinator.com`, - uid: obj.id, - roles: [ - 'caseworker-employment', - 'caseworker-employment-leeds', - 'caseworker' - ], - name: obj.name, - given_name: obj.name.split(' ')[0], - family_name: obj.name.split(' ')[1] - }; - } - function watchCase(socket, caseId) { - socket.join(`case:${caseId}`); - } - function watchCases(socket, caseIds) { - caseIds.forEach((caseId) => { - watchCase(socket, caseId); - }); - } - function stopWatchingCases(socket) { - [...socket.rooms].filter((r) => r.indexOf('case:') === 0).forEach((r) => socket.leave(r)); - } + const socketUsers = {}; - async function whenActivityForCases(caseIds) { - return activityService.getActivityForCases(caseIds); - } - async function notifyWatchers(caseIds) { - const ids = Array.isArray(caseIds) ? caseIds : [caseIds]; - ids.sort().forEach(async (caseId) => { - const cs = await whenActivityForCases([caseId]); - io.to(`case:${caseId}`).emit('activity', cs); - }); + async function notifyWatchers(caseId) { + const cs = await activityService.getActivityForCases([caseId]); + io.to(`case:${caseId}`).emit('activity', cs); } - async function handleViewOrEdit(socket, caseId, user, activity) { - // Leave all the existing case rooms. - stopWatchingCases(socket); - - // Now watch this case specifically. - watchCase(socket, caseId); + async function handleActivity(socket, caseId, user, activity) { + // Update what's being watched. + utils.watch.update(socket, [caseId]); - // Finally, add this new activity to redis, which will also clear out the old activity. - await activityService.addActivity(caseId, toUser(user), socket.id, activity); - } - function handleEdit(socket, caseId, user) { - handleViewOrEdit(socket, caseId, user, 'edit'); - } - function handleView(socket, caseId, user) { - handleViewOrEdit(socket, caseId, user, 'view'); + // Then add this new activity to redis, which will also clear out the old activity. + await activityService.addActivity(caseId, utils.toUser(user), socket.id, activity); } async function handleWatch(socket, caseIds) { // Stop watching the current cases. - stopWatchingCases(socket); + utils.watch.stop(socket); // Remove the activity for this socket. await activityService.removeSocketActivity(socket.id); // Now watch the specified cases. - watchCases(socket, caseIds); + utils.watch.cases(socket, caseIds); // And immediately dispatch a message about the activity on those cases. - const cs = await whenActivityForCases(caseIds); + const cs = await activityService.getActivityForCases(caseIds); socket.emit('activity', cs); } - // TODO: Track this stuff in redis. - const socketUsers = {}; - - // Pretty way of logging. - function doLog(socket, payload, group) { - let text = `${new Date().toISOString()} | ${socket.id} | ${group}`; - if (typeof payload === 'string') { - if (payload) { - text = `${text} => ${payload}`; - } - debug(text); - } else { - debug(text); - debug(payload); - } - } - redisWatcher.psubscribe('case:*'); redisWatcher.on('pmessage', (_, room) => { const caseId = room.replace('case:', ''); - notifyWatchers([caseId]); + notifyWatchers(caseId); }); // Set up routes for each type of message. - iorouter.on('init', (socket, ctx, next) => { - // Do nothing in here. - doLog(socket, '', 'init'); - next(); - }); - iorouter.on('register', (socket, ctx, next) => { - doLog(socket, ctx.request.user, 'register'); + utils.log(socket, ctx.request.user, 'register'); socketUsers[socket.id] = ctx.request.user; next(); }); - iorouter.on('view', (socket, ctx, next) => { const user = socketUsers[socket.id]; - doLog(socket, `${ctx.request.caseId} (${user.name})`, 'view'); - handleView(socket, ctx.request.caseId, user); + utils.log(socket, `${ctx.request.caseId} (${user.name})`, 'view'); + handleActivity(socket, ctx.request.caseId, user, 'view'); next(); }); - iorouter.on('edit', (socket, ctx, next) => { const user = socketUsers[socket.id]; - doLog(socket, `${ctx.request.caseId} (${user.name})`, 'edit'); - handleEdit(socket, ctx.request.caseId, user); + utils.log(socket, `${ctx.request.caseId} (${user.name})`, 'edit'); + handleActivity(socket, ctx.request.caseId, user, 'edit'); next(); }); - iorouter.on('watch', (socket, ctx, next) => { const user = socketUsers[socket.id]; - doLog(socket, `${ctx.request.caseIds} (${user.name})`, 'watch'); + utils.log(socket, `${ctx.request.caseIds} (${user.name})`, 'watch'); handleWatch(socket, ctx.request.caseIds); next(); }); - // On client connection attach the router + // On client connection, attach the router io.on('connection', (socket) => { socket.use((packet, next) => { - // Call router.attach() with the client socket as the first parameter iorouter.attach(socket, packet, next); }); }); @@ -179,9 +113,9 @@ module.exports = (server, redis) => { const connections = []; io.sockets.on('connection', (socket) => { connections.push(socket); - doLog(socket, '', `connected (${connections.length} total)`); + utils.log(socket, '', `connected (${connections.length} total)`); socket.on('disconnect', () => { - doLog(socket, '', `disconnected (${connections.length - 1} total)`); + utils.log(socket, '', `disconnected (${connections.length - 1} total)`); activityService.removeSocketActivity(socket.id); delete socketUsers[socket.id]; connections.splice(connections.indexOf(socket), 1); diff --git a/app/socket/utils.js b/app/socket/utils.js deleted file mode 100644 index 9219f345..00000000 --- a/app/socket/utils.js +++ /dev/null @@ -1,74 +0,0 @@ -const debug = require('debug')('ccd-case-activity-api:socket-activity-service'); -const redisActivityKeys = require('./redis-keys'); - -const utils = { - toUserString: (user) => { - return user ? JSON.stringify({ - id: user.uid, - forename: user.given_name, - surname: user.family_name - }) : '{}'; - }, - extractUniqueUserIds: (result, uniqueUserIds) => { - const userIds = Array.isArray(uniqueUserIds) ? [...uniqueUserIds] : []; - if (Array.isArray(result)) { - result.forEach((item) => { - if (item && item[1]) { - const users = item[1]; - users.forEach((userId) => { - if (!userIds.includes(userId)) { - userIds.push(userId); - } - }); - } - }); - } - return userIds; - }, - get: { - caseActivities: (caseIds, activity, now) => { - if (Array.isArray(caseIds) && ['view', 'edit'].indexOf(activity) > -1) { - return caseIds.filter((id) => !!id).map((id) => { - return ['zrangebyscore', redisActivityKeys[activity](id), now, '+inf']; - }); - } - return []; - }, - users: (userIds) => { - if (Array.isArray(userIds)) { - return userIds.filter((id) => !!id).map((id) => ['get', redisActivityKeys.user(id)]); - } - return []; - } - }, - store: { - userActivity: (activityKey, userId, score) => { - debug(`about to store activity "${activityKey}" for user "${userId}"`); - return ['zadd', activityKey, score, userId]; - }, - userDetails: (user, ttl) => { - const key = redisActivityKeys.user(user.uid); - const store = utils.toUserString(user); - debug(`about to store details "${key}" for user "${user.uid}": ${store}`); - return ['set', key, store, 'EX', ttl]; - }, - socketActivity: (socketId, activityKey, caseId, userId, ttl) => { - const key = redisActivityKeys.socket(socketId); - const store = JSON.stringify({ activityKey, caseId, userId }); - debug(`about to store activity "${key}" for socket "${socketId}": ${store}`); - return ['set', key, store, 'EX', ttl]; - } - }, - remove: { - userActivity: (activity) => { - debug(`about to remove activity "${activity.activityKey}" for user "${activity.userId}"`); - return ['zrem', activity.activityKey, activity.userId]; - }, - socketEntry: (socketId) => { - debug(`about to remove activity for socket "${socketId}"`); - return ['del', redisActivityKeys.socket(socketId)]; - } - } -}; - -module.exports = utils; diff --git a/app/socket/utils/get.js b/app/socket/utils/get.js new file mode 100644 index 00000000..0b10618e --- /dev/null +++ b/app/socket/utils/get.js @@ -0,0 +1,20 @@ +const redisActivityKeys = require('../redis-keys'); + +const get = { + caseActivities: (caseIds, activity, now) => { + if (Array.isArray(caseIds) && ['view', 'edit'].indexOf(activity) > -1) { + return caseIds.filter((id) => !!id).map((id) => { + return ['zrangebyscore', redisActivityKeys[activity](id), now, '+inf']; + }); + } + return []; + }, + users: (userIds) => { + if (Array.isArray(userIds)) { + return userIds.filter((id) => !!id).map((id) => ['get', redisActivityKeys.user(id)]); + } + return []; + } +}; + +module.exports = get; diff --git a/app/socket/utils/index.js b/app/socket/utils/index.js new file mode 100644 index 00000000..c54179ce --- /dev/null +++ b/app/socket/utils/index.js @@ -0,0 +1,9 @@ +const other = require('./other'); + +module.exports = { + ...other, + get: require('./get'), + remove: require('./remove'), + store: require('./store'), + watch: require('./watch') +}; diff --git a/app/socket/utils/other.js b/app/socket/utils/other.js new file mode 100644 index 00000000..9524b777 --- /dev/null +++ b/app/socket/utils/other.js @@ -0,0 +1,70 @@ +const debug = require('debug')('ccd-case-activity-api:socket-utils'); + +const other = { + extractUniqueUserIds: (result, uniqueUserIds) => { + const userIds = Array.isArray(uniqueUserIds) ? [...uniqueUserIds] : []; + if (Array.isArray(result)) { + result.forEach((item) => { + if (item && item[1]) { + const users = item[1]; + users.forEach((userId) => { + if (!userIds.includes(userId)) { + userIds.push(userId); + } + }); + } + }); + } + return userIds; + }, + log: (socket, payload, group, logTo) => { + const outputTo = logTo || debug; + let text = `${new Date().toISOString()} | ${socket.id} | ${group}`; + if (typeof payload === 'string') { + if (payload) { + text = `${text} => ${payload}`; + } + outputTo(text); + } else { + outputTo(text); + outputTo(payload); + } + }, + score: (ttlStr) => { + const now = Date.now(); + const ttl = parseInt(ttlStr, 10) || 0; + const score = now + (ttl * 1000); + debug(`generated score out of current timestamp '${now}' plus ${ttl} sec`); + return score; + }, + toUser: (obj) => { + // TODO: REMOVE THIS + // This is here purely until we have proper auth coming from a client. + if (!obj) { + return {}; + } + const nameParts = obj.name.split(' '); + const givenName = nameParts.shift(); + return { + sub: `${givenName}.${nameParts.join('-')}@mailinator.com`, + uid: obj.id, + roles: [ + 'caseworker-employment', + 'caseworker-employment-leeds', + 'caseworker' + ], + name: obj.name, + given_name: givenName, + family_name: nameParts.join(' ') + }; + }, + toUserString: (user) => { + return user ? JSON.stringify({ + id: user.uid, + forename: user.given_name, + surname: user.family_name + }) : '{}'; + } +}; + +module.exports = other; diff --git a/app/socket/utils/remove.js b/app/socket/utils/remove.js new file mode 100644 index 00000000..ee491903 --- /dev/null +++ b/app/socket/utils/remove.js @@ -0,0 +1,15 @@ +const debug = require('debug')('ccd-case-activity-api:socket-utils-remove'); +const redisActivityKeys = require('../redis-keys'); + +const remove = { + userActivity: (activity) => { + debug(`about to remove activity "${activity.activityKey}" for user "${activity.userId}"`); + return ['zrem', activity.activityKey, activity.userId]; + }, + socketEntry: (socketId) => { + debug(`about to remove activity for socket "${socketId}"`); + return ['del', redisActivityKeys.socket(socketId)]; + } +}; + +module.exports = remove; diff --git a/app/socket/utils/store.js b/app/socket/utils/store.js new file mode 100644 index 00000000..165c90af --- /dev/null +++ b/app/socket/utils/store.js @@ -0,0 +1,24 @@ +const debug = require('debug')('ccd-case-activity-api:socket-utils-store'); +const redisActivityKeys = require('../redis-keys'); +const toUserString = require('./other').toUserString; + +const store = { + userActivity: (activityKey, userId, score) => { + debug(`about to store activity "${activityKey}" for user "${userId}"`); + return ['zadd', activityKey, score, userId]; + }, + userDetails: (user, ttl) => { + const key = redisActivityKeys.user(user.uid); + const store = toUserString(user); + debug(`about to store details "${key}" for user "${user.uid}": ${store}`); + return ['set', key, store, 'EX', ttl]; + }, + socketActivity: (socketId, activityKey, caseId, userId, ttl) => { + const key = redisActivityKeys.socket(socketId); + const store = JSON.stringify({ activityKey, caseId, userId }); + debug(`about to store activity "${key}" for socket "${socketId}": ${store}`); + return ['set', key, store, 'EX', ttl]; + } +}; + +module.exports = store; diff --git a/app/socket/utils/watch.js b/app/socket/utils/watch.js new file mode 100644 index 00000000..99339203 --- /dev/null +++ b/app/socket/utils/watch.js @@ -0,0 +1,27 @@ +const watch = { + case: (socket, caseId) => { + if (socket && caseId) { + socket.join(`case:${caseId}`); + } + }, + cases: (socket, caseIds) => { + if (socket && Array.isArray(caseIds)) { + caseIds.forEach((caseId) => { + watch.case(socket, caseId); + }); + } + }, + stop: (socket) => { + if (socket) { + [...socket.rooms] + .filter((r) => r.indexOf('case:') === 0) // Only case rooms. + .forEach((r) => socket.leave(r)); + } + }, + update: (socket, caseIds) => { + watch.stop(socket); + watch.cases(socket, caseIds); + } +}; + +module.exports = watch; diff --git a/test/spec/app/socket/utils.spec.js b/test/spec/app/socket/utils.spec.js deleted file mode 100644 index 94549274..00000000 --- a/test/spec/app/socket/utils.spec.js +++ /dev/null @@ -1,263 +0,0 @@ -const expect = require('chai').expect; -const utils = require('../../../../app/socket/utils'); -const redisActivityKeys = require('../../../../app/socket/redis-keys'); - -describe('socket.utils', () => { - - describe('toUserString', () => { - it('should handle a null user', () => { - expect(utils.toUserString(null)).to.equal('{}'); - }); - it('should handle an undefined user', () => { - expect(utils.toUserString(undefined)).to.equal('{}'); - }); - it('should handle an empty user', () => { - expect(utils.toUserString({})).to.equal('{}'); - }); - it('should handle a full user', () => { - const USER = { - uid: '1234567890', - given_name: 'Bob', - family_name: 'Smith' - }; - expect(utils.toUserString(USER)).to.equal('{"id":"1234567890","forename":"Bob","surname":"Smith"}'); - }); - it('should handle a user with a missing family name', () => { - const USER = { - uid: '1234567890', - given_name: 'Bob' - }; - expect(utils.toUserString(USER)).to.equal('{"id":"1234567890","forename":"Bob"}'); - }); - it('should handle a user with a missing given name', () => { - const USER = { - uid: '1234567890', - family_name: 'Smith' - }; - expect(utils.toUserString(USER)).to.equal('{"id":"1234567890","surname":"Smith"}'); - }); - it('should handle a user with a missing name', () => { - const USER = { - uid: '1234567890' - }; - expect(utils.toUserString(USER)).to.equal('{"id":"1234567890"}'); - }); - }); - - describe('extractUniqueUserIds', () => { - it('should handle a null result', () => { - const RESULT = null; - const UNIQUE = ['a']; - const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); - expect(IDS).to.be.an('array') - .that.has.lengthOf(1) - .and.that.includes('a'); - }); - it('should handle a result of the wrong type', () => { - const RESULT = 'bob'; - const UNIQUE = ['a']; - const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); - expect(IDS).to.be.an('array') - .that.has.lengthOf(1) - .and.that.includes('a'); - }); - it('should handle a result with the wrong structure', () => { - const RESULT = [ - ['bob'], - ['fred'] - ]; - const UNIQUE = ['a']; - const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); - expect(IDS).to.be.an('array') - .that.has.lengthOf(1) - .and.that.includes('a'); - }); - it('should handle a result containing nulls', () => { - const RESULT = [ - ['bob', ['b']], - ['fred', null] - ]; - const UNIQUE = ['a']; - const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); - expect(IDS).to.be.an('array') - .that.has.lengthOf(2) - .and.that.includes('a') - .and.that.includes('b'); - }); - it('should handle a result with the correct structure', () => { - const RESULT = [ - ['bob', ['b', 'g']], - ['fred', ['f']] - ]; - const UNIQUE = ['a']; - const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); - expect(IDS).to.be.an('array').that.has.lengthOf(4) - .and.that.includes('a') - .and.that.includes('b') - .and.that.includes('f') - .and.that.includes('g'); - }); - it('should handle a result with the correct structure but a null original array', () => { - const RESULT = [ - ['bob', ['b', 'g']], - ['fred', ['f']] - ]; - const UNIQUE = null; - const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); - expect(IDS).to.be.an('array').that.has.lengthOf(3) - .and.that.includes('b') - .and.that.includes('f') - .and.that.includes('g'); - }); - it('should handle a result with the correct structure but an original array of the wrong type', () => { - const RESULT = [ - ['bob', ['b', 'g']], - ['fred', ['f']] - ]; - const UNIQUE = 'a'; - const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); - expect(IDS).to.be.an('array').that.has.lengthOf(3) - .and.that.includes('b') - .and.that.includes('f') - .and.that.includes('g'); - }); - it('should strip out duplicates', () => { - const RESULT = [ - ['bob', ['a', 'b', 'g']], - ['fred', ['f', 'b']] - ]; - const UNIQUE = ['a']; - const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); - expect(IDS).to.be.an('array') - .and.that.includes('a') - .and.that.includes('b') - .and.that.includes('f') - .and.that.includes('g') - .but.that.has.lengthOf(4); // One of each, despite the RESULT containing an extra 'a', and 'b' twice. - }); - }); - - describe('get', () => { - - describe('caseActivities', () => { - it('should get the correct result for a single case being viewed', () => { - const CASE_IDS = ['1']; - const ACTIVITY = 'view'; - const NOW = 999; - const pipes = utils.get.caseActivities(CASE_IDS, ACTIVITY, NOW); - expect(pipes).to.be.an('array').and.have.lengthOf(1); - expect(pipes[0]).to.be.an('array').and.have.lengthOf(4); - expect(pipes[0][0]).to.equal('zrangebyscore'); - expect(pipes[0][1]).to.equal(redisActivityKeys.view(CASE_IDS[0])); - expect(pipes[0][2]).to.equal(NOW); - expect(pipes[0][3]).to.equal('+inf'); - }); - it('should get the correct result for a multiple cases being viewed', () => { - const CASE_IDS = ['1', '8', '2345678', 'x']; - const ACTIVITY = 'view'; - const NOW = 999; - const pipes = utils.get.caseActivities(CASE_IDS, ACTIVITY, NOW); - expect(pipes).to.be.an('array').and.have.lengthOf(CASE_IDS.length); - CASE_IDS.forEach((id, index) => { - expect(pipes[index]).to.be.an('array').and.have.lengthOf(4); - expect(pipes[index][0]).to.equal('zrangebyscore'); - expect(pipes[index][1]).to.equal(redisActivityKeys.view(id)); - expect(pipes[index][2]).to.equal(NOW); - expect(pipes[index][3]).to.equal('+inf'); - }); - }); - it('should handle a null case ID for cases being viewed', () => { - const CASE_IDS = ['1', '8', null, 'x']; - const ACTIVITY = 'view'; - const NOW = 999; - const pipes = utils.get.caseActivities(CASE_IDS, ACTIVITY, NOW); - expect(pipes).to.be.an('array').and.have.lengthOf(CASE_IDS.length - 1); - let pipeIndex = 0; - CASE_IDS.forEach((id) => { - if (id !== null) { - expect(pipes[pipeIndex]).to.be.an('array').and.have.lengthOf(4); - expect(pipes[pipeIndex][0]).to.equal('zrangebyscore'); - expect(pipes[pipeIndex][1]).to.equal(redisActivityKeys.view(id)); - expect(pipes[pipeIndex][2]).to.equal(NOW); - expect(pipes[pipeIndex][3]).to.equal('+inf'); - pipeIndex++; - } - }); - }); - it('should handle a null case ID for cases being edited', () => { - const CASE_IDS = ['1', '8', null, 'x']; - const ACTIVITY = 'edit'; - const NOW = 999; - const pipes = utils.get.caseActivities(CASE_IDS, ACTIVITY, NOW); - expect(pipes).to.be.an('array').and.have.lengthOf(CASE_IDS.length - 1); - let pipeIndex = 0; - CASE_IDS.forEach((id) => { - if (id !== null) { - expect(pipes[pipeIndex]).to.be.an('array').and.have.lengthOf(4); - expect(pipes[pipeIndex][0]).to.equal('zrangebyscore'); - expect(pipes[pipeIndex][1]).to.equal(redisActivityKeys.edit(id)); - expect(pipes[pipeIndex][2]).to.equal(NOW); - expect(pipes[pipeIndex][3]).to.equal('+inf'); - pipeIndex++; - } - }); - }); - it('should handle a null array of case IDs', () => { - const CASE_IDS = null; - const ACTIVITY = 'view'; - const NOW = 999; - const pipes = utils.get.caseActivities(CASE_IDS, ACTIVITY, NOW); - expect(pipes).to.be.an('array').and.have.lengthOf(0); - }); - it('should handle an invalid activity type', () => { - const CASE_IDS = ['1', '8', '2345678', 'x']; - const ACTIVITY = 'bob'; - const NOW = 999; - const pipes = utils.get.caseActivities(CASE_IDS, ACTIVITY, NOW); - expect(pipes).to.be.an('array').and.have.lengthOf(0); - }); - }); - - describe('users', () => { - it('should get the correct result for a single user ID', () => { - const USER_IDS = ['1']; - const pipes = utils.get.users(USER_IDS); - expect(pipes).to.be.an('array').and.have.lengthOf(1); - expect(pipes[0]).to.be.an('array').and.have.lengthOf(2); - expect(pipes[0][0]).to.equal('get'); - expect(pipes[0][1]).to.equal(redisActivityKeys.user(USER_IDS[0])); - }); - it('should get the correct result for multiple user IDs', () => { - const USER_IDS = ['1', '8', '2345678', 'x']; - const pipes = utils.get.users(USER_IDS); - expect(pipes).to.be.an('array').and.have.lengthOf(USER_IDS.length); - expect(pipes[0]).to.be.an('array').and.have.lengthOf(2); - USER_IDS.forEach((id, index) => { - expect(pipes[index][0]).to.equal('get'); - expect(pipes[index][1]).to.equal(redisActivityKeys.user(id)); - }); - }); - it('should handle a null user ID', () => { - const USER_IDS = ['1', '8', null, 'x']; - const pipes = utils.get.users(USER_IDS); - expect(pipes).to.be.an('array').and.have.lengthOf(USER_IDS.length - 1); - expect(pipes[0]).to.be.an('array').and.have.lengthOf(2); - let pipeIndex = 0; - USER_IDS.forEach((id) => { - if (id) { - expect(pipes[pipeIndex][0]).to.equal('get'); - expect(pipes[pipeIndex][1]).to.equal(redisActivityKeys.user(id)); - pipeIndex++; - } - }); - }); - it('should handle a null array of user IDs', () => { - const USER_IDS = null; - const pipes = utils.get.users(USER_IDS); - expect(pipes).to.be.an('array').and.have.lengthOf(0); - }); - }); - - }); - -}); diff --git a/test/spec/app/socket/utils/get.spec.js b/test/spec/app/socket/utils/get.spec.js new file mode 100644 index 00000000..2f9cdc4d --- /dev/null +++ b/test/spec/app/socket/utils/get.spec.js @@ -0,0 +1,130 @@ +const expect = require('chai').expect; +const get = require('../../../../../app/socket/utils/get'); +const redisActivityKeys = require('../../../../../app/socket/redis-keys'); + +describe('socket.utils', () => { + + describe('get', () => { + + describe('caseActivities', () => { + it('should get the correct result for a single case being viewed', () => { + const CASE_IDS = ['1']; + const ACTIVITY = 'view'; + const NOW = 999; + const pipes = get.caseActivities(CASE_IDS, ACTIVITY, NOW); + expect(pipes).to.be.an('array').and.have.lengthOf(1); + expect(pipes[0]).to.be.an('array').and.have.lengthOf(4); + expect(pipes[0][0]).to.equal('zrangebyscore'); + expect(pipes[0][1]).to.equal(redisActivityKeys.view(CASE_IDS[0])); + expect(pipes[0][2]).to.equal(NOW); + expect(pipes[0][3]).to.equal('+inf'); + }); + it('should get the correct result for a multiple cases being viewed', () => { + const CASE_IDS = ['1', '8', '2345678', 'x']; + const ACTIVITY = 'view'; + const NOW = 999; + const pipes = get.caseActivities(CASE_IDS, ACTIVITY, NOW); + expect(pipes).to.be.an('array').and.have.lengthOf(CASE_IDS.length); + CASE_IDS.forEach((id, index) => { + expect(pipes[index]).to.be.an('array').and.have.lengthOf(4); + expect(pipes[index][0]).to.equal('zrangebyscore'); + expect(pipes[index][1]).to.equal(redisActivityKeys.view(id)); + expect(pipes[index][2]).to.equal(NOW); + expect(pipes[index][3]).to.equal('+inf'); + }); + }); + it('should handle a null case ID for cases being viewed', () => { + const CASE_IDS = ['1', '8', null, 'x']; + const ACTIVITY = 'view'; + const NOW = 999; + const pipes = get.caseActivities(CASE_IDS, ACTIVITY, NOW); + expect(pipes).to.be.an('array').and.have.lengthOf(CASE_IDS.length - 1); + let pipeIndex = 0; + CASE_IDS.forEach((id) => { + if (id !== null) { + expect(pipes[pipeIndex]).to.be.an('array').and.have.lengthOf(4); + expect(pipes[pipeIndex][0]).to.equal('zrangebyscore'); + expect(pipes[pipeIndex][1]).to.equal(redisActivityKeys.view(id)); + expect(pipes[pipeIndex][2]).to.equal(NOW); + expect(pipes[pipeIndex][3]).to.equal('+inf'); + pipeIndex++; + } + }); + }); + it('should handle a null case ID for cases being edited', () => { + const CASE_IDS = ['1', '8', null, 'x']; + const ACTIVITY = 'edit'; + const NOW = 999; + const pipes = get.caseActivities(CASE_IDS, ACTIVITY, NOW); + expect(pipes).to.be.an('array').and.have.lengthOf(CASE_IDS.length - 1); + let pipeIndex = 0; + CASE_IDS.forEach((id) => { + if (id !== null) { + expect(pipes[pipeIndex]).to.be.an('array').and.have.lengthOf(4); + expect(pipes[pipeIndex][0]).to.equal('zrangebyscore'); + expect(pipes[pipeIndex][1]).to.equal(redisActivityKeys.edit(id)); + expect(pipes[pipeIndex][2]).to.equal(NOW); + expect(pipes[pipeIndex][3]).to.equal('+inf'); + pipeIndex++; + } + }); + }); + it('should handle a null array of case IDs', () => { + const CASE_IDS = null; + const ACTIVITY = 'view'; + const NOW = 999; + const pipes = get.caseActivities(CASE_IDS, ACTIVITY, NOW); + expect(pipes).to.be.an('array').and.have.lengthOf(0); + }); + it('should handle an invalid activity type', () => { + const CASE_IDS = ['1', '8', '2345678', 'x']; + const ACTIVITY = 'bob'; + const NOW = 999; + const pipes = get.caseActivities(CASE_IDS, ACTIVITY, NOW); + expect(pipes).to.be.an('array').and.have.lengthOf(0); + }); + }); + + describe('users', () => { + it('should get the correct result for a single user ID', () => { + const USER_IDS = ['1']; + const pipes = get.users(USER_IDS); + expect(pipes).to.be.an('array').and.have.lengthOf(1); + expect(pipes[0]).to.be.an('array').and.have.lengthOf(2); + expect(pipes[0][0]).to.equal('get'); + expect(pipes[0][1]).to.equal(redisActivityKeys.user(USER_IDS[0])); + }); + it('should get the correct result for multiple user IDs', () => { + const USER_IDS = ['1', '8', '2345678', 'x']; + const pipes = get.users(USER_IDS); + expect(pipes).to.be.an('array').and.have.lengthOf(USER_IDS.length); + expect(pipes[0]).to.be.an('array').and.have.lengthOf(2); + USER_IDS.forEach((id, index) => { + expect(pipes[index][0]).to.equal('get'); + expect(pipes[index][1]).to.equal(redisActivityKeys.user(id)); + }); + }); + it('should handle a null user ID', () => { + const USER_IDS = ['1', '8', null, 'x']; + const pipes = get.users(USER_IDS); + expect(pipes).to.be.an('array').and.have.lengthOf(USER_IDS.length - 1); + expect(pipes[0]).to.be.an('array').and.have.lengthOf(2); + let pipeIndex = 0; + USER_IDS.forEach((id) => { + if (id) { + expect(pipes[pipeIndex][0]).to.equal('get'); + expect(pipes[pipeIndex][1]).to.equal(redisActivityKeys.user(id)); + pipeIndex++; + } + }); + }); + it('should handle a null array of user IDs', () => { + const USER_IDS = null; + const pipes = get.users(USER_IDS); + expect(pipes).to.be.an('array').and.have.lengthOf(0); + }); + }); + + }); + +}); diff --git a/test/spec/app/socket/utils/index.spec.js b/test/spec/app/socket/utils/index.spec.js new file mode 100644 index 00000000..40be897a --- /dev/null +++ b/test/spec/app/socket/utils/index.spec.js @@ -0,0 +1,244 @@ +const expect = require('chai').expect; +const sandbox = require("sinon").createSandbox(); +const utils = require('../../../../../app/socket/utils'); + +describe('socket.utils', () => { + + describe('extractUniqueUserIds', () => { + it('should handle a null result', () => { + const RESULT = null; + const UNIQUE = ['a']; + const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); + expect(IDS).to.be.an('array') + .that.has.lengthOf(1) + .and.that.includes('a'); + }); + it('should handle a result of the wrong type', () => { + const RESULT = 'bob'; + const UNIQUE = ['a']; + const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); + expect(IDS).to.be.an('array') + .that.has.lengthOf(1) + .and.that.includes('a'); + }); + it('should handle a result with the wrong structure', () => { + const RESULT = [ + ['bob'], + ['fred'] + ]; + const UNIQUE = ['a']; + const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); + expect(IDS).to.be.an('array') + .that.has.lengthOf(1) + .and.that.includes('a'); + }); + it('should handle a result containing nulls', () => { + const RESULT = [ + ['bob', ['b']], + ['fred', null] + ]; + const UNIQUE = ['a']; + const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); + expect(IDS).to.be.an('array') + .that.has.lengthOf(2) + .and.that.includes('a') + .and.that.includes('b'); + }); + it('should handle a result with the correct structure', () => { + const RESULT = [ + ['bob', ['b', 'g']], + ['fred', ['f']] + ]; + const UNIQUE = ['a']; + const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); + expect(IDS).to.be.an('array').that.has.lengthOf(4) + .and.that.includes('a') + .and.that.includes('b') + .and.that.includes('f') + .and.that.includes('g'); + }); + it('should handle a result with the correct structure but a null original array', () => { + const RESULT = [ + ['bob', ['b', 'g']], + ['fred', ['f']] + ]; + const UNIQUE = null; + const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); + expect(IDS).to.be.an('array').that.has.lengthOf(3) + .and.that.includes('b') + .and.that.includes('f') + .and.that.includes('g'); + }); + it('should handle a result with the correct structure but an original array of the wrong type', () => { + const RESULT = [ + ['bob', ['b', 'g']], + ['fred', ['f']] + ]; + const UNIQUE = 'a'; + const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); + expect(IDS).to.be.an('array').that.has.lengthOf(3) + .and.that.includes('b') + .and.that.includes('f') + .and.that.includes('g'); + }); + it('should strip out duplicates', () => { + const RESULT = [ + ['bob', ['a', 'b', 'g']], + ['fred', ['f', 'b']] + ]; + const UNIQUE = ['a']; + const IDS = utils.extractUniqueUserIds(RESULT, UNIQUE); + expect(IDS).to.be.an('array') + .and.that.includes('a') + .and.that.includes('b') + .and.that.includes('f') + .and.that.includes('g') + .but.that.has.lengthOf(4); // One of each, despite the RESULT containing an extra 'a', and 'b' twice. + }); + }); + + describe('log', () => { + it('should output string payload', () => { + const logs = []; + const logTo = (str) => { + logs.push(str); + }; + const SOCKET = { id: 'Are' }; + const PAYLOAD = 'entertained?'; + const GROUP = 'you not'; + utils.log(SOCKET, PAYLOAD, GROUP, logTo); + expect(logs).to.have.lengthOf(1); + expect(logs[0]).to.include(`| Are | you not => entertained?`); + }); + it('should output object payload', () => { + const logs = []; + const logTo = (str) => { + logs.push(str); + }; + const SOCKET = { id: 'Are' }; + const PAYLOAD = { sufficiently: 'entertained?' }; + const GROUP = 'you not'; + utils.log(SOCKET, PAYLOAD, GROUP, logTo); + expect(logs).to.have.lengthOf(2); + expect(logs[0]).to.include(`| Are | you not`); + expect(logs[1]).to.equal(PAYLOAD); + }); + }); + + describe('score', () => { + it('should handle a string TTL', () => { + const TTL = '12'; + const NOW = 55; + sandbox.stub(Date, 'now').returns(NOW); + const score = utils.score(TTL); + expect(score).to.equal(12055); // (TTL * 1000) + NOW + }); + it('should handle a numeric TTL', () => { + const TTL = 13; + const NOW = 55; + sandbox.stub(Date, 'now').returns(NOW); + const score = utils.score(TTL); + expect(score).to.equal(13055); // (TTL * 1000) + NOW + }); + it('should handle a null TTL', () => { + const TTL = null; + const NOW = 55; + sandbox.stub(Date, 'now').returns(NOW); + const score = utils.score(TTL); + expect(score).to.equal(55); // null TTL => 0 + }); + + afterEach(() => { + // completely restore all fakes created through the sandbox + sandbox.restore(); + }); + }); + + describe('toUser', () => { + it('should handle a null object', () => { + const OBJ = null; + const user = utils.toUser(OBJ); + expect(user).to.deep.equal({}); + }); + it('should handle a valid object', () => { + const OBJ = { id: 'bob', name: 'Bob Smith' }; + const user = utils.toUser(OBJ); + expect(user.uid).to.equal(OBJ.id); + expect(user.name).to.equal(OBJ.name); + expect(user.given_name).to.equal('Bob'); + expect(user.family_name).to.equal('Smith'); + expect(user.sub).to.equal('Bob.Smith@mailinator.com'); + }); + it('should handle a valid object with a long name', () => { + const OBJ = { id: 'ddl', name: 'Daniel Day Lewis' }; + const user = utils.toUser(OBJ); + expect(user.uid).to.equal(OBJ.id); + expect(user.name).to.equal(OBJ.name); + expect(user.given_name).to.equal('Daniel'); + expect(user.family_name).to.equal('Day Lewis'); + expect(user.sub).to.equal('Daniel.Day-Lewis@mailinator.com'); + }); + }); + + describe('toUserString', () => { + it('should handle a null user', () => { + expect(utils.toUserString(null)).to.equal('{}'); + }); + it('should handle an undefined user', () => { + expect(utils.toUserString(undefined)).to.equal('{}'); + }); + it('should handle an empty user', () => { + expect(utils.toUserString({})).to.equal('{}'); + }); + it('should handle a full user', () => { + const USER = { + uid: '1234567890', + given_name: 'Bob', + family_name: 'Smith' + }; + expect(utils.toUserString(USER)).to.equal('{"id":"1234567890","forename":"Bob","surname":"Smith"}'); + }); + it('should handle a user with a missing family name', () => { + const USER = { + uid: '1234567890', + given_name: 'Bob' + }; + expect(utils.toUserString(USER)).to.equal('{"id":"1234567890","forename":"Bob"}'); + }); + it('should handle a user with a missing given name', () => { + const USER = { + uid: '1234567890', + family_name: 'Smith' + }; + expect(utils.toUserString(USER)).to.equal('{"id":"1234567890","surname":"Smith"}'); + }); + it('should handle a user with a missing name', () => { + const USER = { + uid: '1234567890' + }; + expect(utils.toUserString(USER)).to.equal('{"id":"1234567890"}'); + }); + }); + + describe('get', () => { + it('should be appropriately set up', () => { + expect(utils.get).to.equal(require('../../../../../app/socket/utils/get')); + }); + }); + describe('remove', () => { + it('should be appropriately set up', () => { + expect(utils.remove).to.equal(require('../../../../../app/socket/utils/remove')); + }); + }); + describe('store', () => { + it('should be appropriately set up', () => { + expect(utils.store).to.equal(require('../../../../../app/socket/utils/store')); + }); + }); + describe('watch', () => { + it('should be appropriately set up', () => { + expect(utils.watch).to.equal(require('../../../../../app/socket/utils/watch')); + }); + }); + +}); diff --git a/test/spec/app/socket/utils/remove.spec.js b/test/spec/app/socket/utils/remove.spec.js new file mode 100644 index 00000000..756189fc --- /dev/null +++ b/test/spec/app/socket/utils/remove.spec.js @@ -0,0 +1,36 @@ +const expect = require('chai').expect; +const remove = require('../../../../../app/socket/utils/remove'); +const redisActivityKeys = require('../../../../../app/socket/redis-keys'); + +describe('socket.utils', () => { + + describe('remove', () => { + + describe('userActivity', () => { + it('should produce an appopriate pipe', () => { + const CASE_ID = '1234567890'; + const ACTIVITY = { + activityKey: redisActivityKeys.view(CASE_ID), + userId: 'a' + }; + const pipe = remove.userActivity(ACTIVITY); + expect(pipe).to.be.an('array').and.have.lengthOf(3); + expect(pipe[0]).to.equal('zrem'); + expect(pipe[1]).to.equal(ACTIVITY.activityKey); + expect(pipe[2]).to.equal(ACTIVITY.userId); + }); + }); + + describe('socketEntry', () => { + it('should produce an appopriate pipe', () => { + const SOCKET_ID = 'abcdef123456'; + const pipe = remove.socketEntry(SOCKET_ID); + expect(pipe).to.be.an('array').and.have.lengthOf(2); + expect(pipe[0]).to.equal('del'); + expect(pipe[1]).to.equal(redisActivityKeys.socket(SOCKET_ID)); + }); + }); + + }); + +}); diff --git a/test/spec/app/socket/utils/store.spec.js b/test/spec/app/socket/utils/store.spec.js new file mode 100644 index 00000000..2cc486e9 --- /dev/null +++ b/test/spec/app/socket/utils/store.spec.js @@ -0,0 +1,57 @@ +const expect = require('chai').expect; +const store = require('../../../../../app/socket/utils/store'); +const redisActivityKeys = require('../../../../../app/socket/redis-keys'); + +describe('socket.utils', () => { + + describe('store', () => { + + describe('userActivity', () => { + it('should produce an appopriate pipe', () => { + const CASE_ID = '1234567890'; + const ACTIVITY_KEY = redisActivityKeys.view(CASE_ID); + const USER_ID = 'a'; + const SCORE = 500; + const pipe = store.userActivity(ACTIVITY_KEY, USER_ID, SCORE); + expect(pipe).to.be.an('array').and.have.lengthOf(4); + expect(pipe[0]).to.equal('zadd'); + expect(pipe[1]).to.equal(ACTIVITY_KEY); + expect(pipe[2]).to.equal(SCORE); + expect(pipe[3]).to.equal(USER_ID); + }); + }); + + describe('userDetails', () => { + it('should produce an appopriate pipe', () => { + const USER = { uid: 'a', given_name: 'Bob', family_name: 'Smith' }; + const TTL = 487; + const pipe = store.userDetails(USER, TTL); + expect(pipe).to.be.an('array').and.have.lengthOf(5); + expect(pipe[0]).to.equal('set'); + expect(pipe[1]).to.equal(redisActivityKeys.user(USER.uid)); + expect(pipe[2]).to.equal('{"id":"a","forename":"Bob","surname":"Smith"}'); + expect(pipe[3]).to.equal('EX'); // Expires in... + expect(pipe[4]).to.equal(TTL); // ...487 seconds. + }); + }); + + describe('socketActivity', () => { + it('should produce an appopriate pipe', () => { + const CASE_ID = '1234567890'; + const SOCKET_ID = 'abcdef123456'; + const ACTIVITY_KEY = redisActivityKeys.view(CASE_ID); + const USER_ID = 'a'; + const TTL = 487; + const pipe = store.socketActivity(SOCKET_ID, ACTIVITY_KEY, CASE_ID, USER_ID, TTL); + expect(pipe).to.be.an('array').and.have.lengthOf(5); + expect(pipe[0]).to.equal('set'); + expect(pipe[1]).to.equal(redisActivityKeys.socket(SOCKET_ID)); + expect(pipe[2]).to.equal(`{"activityKey":"${ACTIVITY_KEY}","caseId":"${CASE_ID}","userId":"${USER_ID}"}`); + expect(pipe[3]).to.equal('EX'); // Expires in... + expect(pipe[4]).to.equal(TTL); // ...487 seconds. + }); + }); + + }); + +}); diff --git a/test/spec/app/socket/utils/watch.spec.js b/test/spec/app/socket/utils/watch.spec.js new file mode 100644 index 00000000..77933bc0 --- /dev/null +++ b/test/spec/app/socket/utils/watch.spec.js @@ -0,0 +1,140 @@ +const expect = require('chai').expect; +const watch = require('../../../../../app/socket/utils/watch'); + +describe('socket.utils', () => { + + describe('watch', () => { + + const MOCK_SOCKET = { + id: 'socket-id', + rooms: ['socket-id'], + join: (room) => { + if (!MOCK_SOCKET.rooms.includes(room)) { + MOCK_SOCKET.rooms.push(room); + } + }, + leave: (room) => { + const roomIndex = MOCK_SOCKET.rooms.indexOf(room); + if (roomIndex > -1) { + MOCK_SOCKET.rooms.splice(roomIndex, 1); + } + } + }; + + afterEach(() => { + MOCK_SOCKET.rooms.length = 0; + MOCK_SOCKET.rooms.push(MOCK_SOCKET.id) + }); + + describe('case', () => { + it('should join the appropriate room on the socket', () => { + const CASE_ID = '1234567890'; + watch.case(MOCK_SOCKET, CASE_ID); + expect(MOCK_SOCKET.rooms).to.have.lengthOf(2) + .and.to.include(MOCK_SOCKET.id) + .and.to.include(`case:${CASE_ID}`); + }); + it('should handle a null room', () => { + const CASE_ID = null; + watch.case(MOCK_SOCKET, CASE_ID); + expect(MOCK_SOCKET.rooms).to.have.lengthOf(1) + .and.to.include(MOCK_SOCKET.id); + }); + it('should handle a null socket', () => { + const CASE_ID = null; + watch.case(null, CASE_ID); + expect(MOCK_SOCKET.rooms).to.have.lengthOf(1) + .and.to.include(MOCK_SOCKET.id); + }); + }); + + describe('cases', () => { + it('should join all appropriate rooms on the socket', () => { + const CASE_IDS = ['1234567890', '0987654321', 'bob']; + watch.cases(MOCK_SOCKET, CASE_IDS); + expect(MOCK_SOCKET.rooms).to.have.lengthOf(CASE_IDS.length + 1) + .and.to.include(MOCK_SOCKET.id); + CASE_IDS.forEach((id) => { + expect(MOCK_SOCKET.rooms).to.include(`case:${id}`); + }); + }); + it('should handle a null room', () => { + const CASE_IDS = ['1234567890', null, 'bob']; + watch.cases(MOCK_SOCKET, CASE_IDS); + expect(MOCK_SOCKET.rooms).to.have.lengthOf(CASE_IDS.length) + .and.to.include(MOCK_SOCKET.id); + CASE_IDS.forEach((id) => { + if (id) { + expect(MOCK_SOCKET.rooms).to.include(`case:${id}`); + } + }); + }); + it('should handle a null socket', () => { + const CASE_IDS = ['1234567890', '0987654321', 'bob']; + watch.cases(null, CASE_IDS); + expect(MOCK_SOCKET.rooms).to.have.lengthOf(1) + .and.to.include(MOCK_SOCKET.id); + }); + }); + + describe('stop', () => { + it('should leave all the case rooms', () => { + // First, join a bunch of rooms. + const CASE_IDS = ['1234567890', '0987654321', 'bob']; + watch.cases(MOCK_SOCKET, CASE_IDS); + expect(MOCK_SOCKET.rooms).to.have.lengthOf(CASE_IDS.length + 1) + .and.to.include(MOCK_SOCKET.id); + + // Now stop watching the rooms. + watch.stop(MOCK_SOCKET); + expect(MOCK_SOCKET.rooms).to.have.lengthOf(1) + .and.to.include(MOCK_SOCKET.id); + }); + it('should handle a null socket', () => { + // First, join a bunch of rooms. + const CASE_IDS = ['1234567890', '0987654321', 'bob']; + watch.cases(MOCK_SOCKET, CASE_IDS); + expect(MOCK_SOCKET.rooms).to.have.lengthOf(CASE_IDS.length + 1) + .and.to.include(MOCK_SOCKET.id); + + // Now pass a null socket to the stop method. + watch.stop(null); + + // The MOCK_SOCKET's rooms should be untouched. + expect(MOCK_SOCKET.rooms).to.have.lengthOf(CASE_IDS.length + 1) + .and.to.include(MOCK_SOCKET.id); + }); + it('should handle no case rooms to leave', () => { + expect(MOCK_SOCKET.rooms).to.have.lengthOf(1) + .and.to.include(MOCK_SOCKET.id); + + // Now stop watching the rooms, which should have no effect. + watch.stop(MOCK_SOCKET); + expect(MOCK_SOCKET.rooms).to.have.lengthOf(1) + .and.to.include(MOCK_SOCKET.id); + }); + }); + + describe('update', () => { + it('should appropriately replace one set of cases with another', () => { + // First, let's watch a bunch of cases. + const CASE_IDS = ['1234567890', '0987654321', 'bob']; + watch.cases(MOCK_SOCKET, CASE_IDS); + + // Now, let's use a whole different bunch. + const REPLACEMENT_CASE_IDS = ['a', 'b', 'c', 'd']; + watch.update(MOCK_SOCKET, REPLACEMENT_CASE_IDS); + expect(MOCK_SOCKET.rooms).to.have.lengthOf(REPLACEMENT_CASE_IDS.length + 1) + .and.to.include(MOCK_SOCKET.id); + REPLACEMENT_CASE_IDS.forEach((id) => { + expect(MOCK_SOCKET.rooms).to.include(`case:${id}`); + }); + CASE_IDS.forEach((id) => { + expect(MOCK_SOCKET.rooms).not.to.include(`case:${id}`); + }); + }); + }); + + }); + +}); From 46dde6a43513c3e9bf3ef00c51ef2f40147bbd66 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Wed, 26 May 2021 15:40:33 +0100 Subject: [PATCH 019/113] Unit tests Additional unit tests and further refactoring. --- app/socket/index.js | 112 ++---------------- app/socket/redis-watcher.js | 3 - app/socket/{redis-keys.js => redis/keys.js} | 0 app/socket/redis/pub-sub.js | 16 +++ app/socket/redis/watcher.js | 4 + app/socket/router/index.js | 59 +++++++++ app/socket/{ => service}/activity-service.js | 13 +- app/socket/service/handlers.js | 66 +++++++++++ app/socket/utils/get.js | 2 +- app/socket/utils/remove.js | 2 +- app/socket/utils/store.js | 2 +- test/spec/app/socket/index.spec.js | 18 +++ .../keys.spec.js} | 4 +- test/spec/app/socket/redis/pub-sub.spec.js | 64 ++++++++++ test/spec/app/socket/redis/watcher.spec.js | 12 ++ test/spec/app/socket/utils/get.spec.js | 2 +- test/spec/app/socket/utils/remove.spec.js | 2 +- test/spec/app/socket/utils/store.spec.js | 2 +- 18 files changed, 266 insertions(+), 117 deletions(-) delete mode 100644 app/socket/redis-watcher.js rename app/socket/{redis-keys.js => redis/keys.js} (100%) create mode 100644 app/socket/redis/pub-sub.js create mode 100644 app/socket/redis/watcher.js create mode 100644 app/socket/router/index.js rename app/socket/{ => service}/activity-service.js (92%) create mode 100644 app/socket/service/handlers.js create mode 100644 test/spec/app/socket/index.spec.js rename test/spec/app/socket/{redis-keys.spec.js => redis/keys.spec.js} (89%) create mode 100644 test/spec/app/socket/redis/pub-sub.spec.js create mode 100644 test/spec/app/socket/redis/watcher.spec.js diff --git a/app/socket/index.js b/app/socket/index.js index 1866f273..a0f9332d 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -2,11 +2,11 @@ const config = require('config'); const IORouter = require('socket.io-router-middleware'); const SocketIO = require('socket.io'); -const ActivityService = require('./activity-service'); -const redisWatcher = require('./redis-watcher'); -const utils = require('./utils'); - -const iorouter = new IORouter(); +const ActivityService = require('./service/activity-service'); +const Handlers = require('./service/handlers'); +const watcher = require('./redis/watcher'); +const pubSub = require('./redis/pub-sub')(); +const router = require('./router'); /** * Sets up a series of routes for a "socket" endpoint, that @@ -17,110 +17,20 @@ const iorouter = new IORouter(); * The behaviour is the same, though. * * TODO: - * 1. Use redis rather than holding the details in memory. - * 2. Some sort of auth / get the credentials when the user connects. - * - * Add view activity looks like this: - * addActivity 1588201414700270, { - sub: 'leeds_et@mailinator.com', - uid: '85269805-3a70-419d-acab-193faeb89ad3', - roles: [ - 'caseworker-employment', - 'caseworker-employment-leeds', - 'caseworker' - ], - name: 'Ethos Leeds', - given_name: 'Ethos', - family_name: 'Leeds' - }, '18hs67171jak', 'view' - * + * * Some sort of auth / get the credentials when the user connects. */ module.exports = (server, redis) => { const activityService = ActivityService(config, redis); - const io = SocketIO(server, { + const socketServer = SocketIO(server, { allowEIO3: true, cors: { origin: '*', methods: ['GET', 'POST'] } }); - const socketUsers = {}; - - async function notifyWatchers(caseId) { - const cs = await activityService.getActivityForCases([caseId]); - io.to(`case:${caseId}`).emit('activity', cs); - } - async function handleActivity(socket, caseId, user, activity) { - // Update what's being watched. - utils.watch.update(socket, [caseId]); - - // Then add this new activity to redis, which will also clear out the old activity. - await activityService.addActivity(caseId, utils.toUser(user), socket.id, activity); - } - async function handleWatch(socket, caseIds) { - // Stop watching the current cases. - utils.watch.stop(socket); - - // Remove the activity for this socket. - await activityService.removeSocketActivity(socket.id); - - // Now watch the specified cases. - utils.watch.cases(socket, caseIds); - - // And immediately dispatch a message about the activity on those cases. - const cs = await activityService.getActivityForCases(caseIds); - socket.emit('activity', cs); - } - - redisWatcher.psubscribe('case:*'); - redisWatcher.on('pmessage', (_, room) => { - const caseId = room.replace('case:', ''); - notifyWatchers(caseId); - }); - - // Set up routes for each type of message. - iorouter.on('register', (socket, ctx, next) => { - utils.log(socket, ctx.request.user, 'register'); - socketUsers[socket.id] = ctx.request.user; - next(); - }); - iorouter.on('view', (socket, ctx, next) => { - const user = socketUsers[socket.id]; - utils.log(socket, `${ctx.request.caseId} (${user.name})`, 'view'); - handleActivity(socket, ctx.request.caseId, user, 'view'); - next(); - }); - iorouter.on('edit', (socket, ctx, next) => { - const user = socketUsers[socket.id]; - utils.log(socket, `${ctx.request.caseId} (${user.name})`, 'edit'); - handleActivity(socket, ctx.request.caseId, user, 'edit'); - next(); - }); - iorouter.on('watch', (socket, ctx, next) => { - const user = socketUsers[socket.id]; - utils.log(socket, `${ctx.request.caseIds} (${user.name})`, 'watch'); - handleWatch(socket, ctx.request.caseIds); - next(); - }); - - // On client connection, attach the router - io.on('connection', (socket) => { - socket.use((packet, next) => { - iorouter.attach(socket, packet, next); - }); - }); - - const connections = []; - io.sockets.on('connection', (socket) => { - connections.push(socket); - utils.log(socket, '', `connected (${connections.length} total)`); - socket.on('disconnect', () => { - utils.log(socket, '', `disconnected (${connections.length - 1} total)`); - activityService.removeSocketActivity(socket.id); - delete socketUsers[socket.id]; - connections.splice(connections.indexOf(socket), 1); - }); - }); + const handlers = Handlers(activityService, socketServer); + pubSub.init(watcher, handlers.notify); + router.init(socketServer, new IORouter(), handlers); - return io; + return { socketServer, activityService, handlers }; }; diff --git a/app/socket/redis-watcher.js b/app/socket/redis-watcher.js deleted file mode 100644 index 50c1a4e3..00000000 --- a/app/socket/redis-watcher.js +++ /dev/null @@ -1,3 +0,0 @@ -const debug = require('debug')('ccd-case-activity-api:redis-watcher'); - -module.exports = require('../redis/instantiator')(debug); diff --git a/app/socket/redis-keys.js b/app/socket/redis/keys.js similarity index 100% rename from app/socket/redis-keys.js rename to app/socket/redis/keys.js diff --git a/app/socket/redis/pub-sub.js b/app/socket/redis/pub-sub.js new file mode 100644 index 00000000..5d4fe7d6 --- /dev/null +++ b/app/socket/redis/pub-sub.js @@ -0,0 +1,16 @@ +const ROOM_PREFIX = 'case:'; + +module.exports = () => { + return { + init: (sub, caseNotifier) => { + if (sub && typeof caseNotifier === 'function') { + sub.psubscribe(`${ROOM_PREFIX}*`); + sub.on('pmessage', (_, room) => { + const caseId = room.replace(ROOM_PREFIX, ''); + caseNotifier(caseId); + }); + } + }, + ROOM_PREFIX + }; +}; diff --git a/app/socket/redis/watcher.js b/app/socket/redis/watcher.js new file mode 100644 index 00000000..196bb24c --- /dev/null +++ b/app/socket/redis/watcher.js @@ -0,0 +1,4 @@ +const debug = require('debug')('ccd-case-activity-api:redis-watcher'); +const watcher = require('../../redis/instantiator')(debug); + +module.exports = watcher; diff --git a/app/socket/router/index.js b/app/socket/router/index.js new file mode 100644 index 00000000..714c710c --- /dev/null +++ b/app/socket/router/index.js @@ -0,0 +1,59 @@ +const utils = require('../utils'); + +const users = {}; +const connections = []; +const router = { + addUser: (socketId, user) => { + users[socketId] = user; + }, + removeUser: (socketId) => { + delete users[socketId]; + }, + getUser: (socketId) => { + return users[socketId]; + }, + init: (io, iorouter, handlers) => { + // Set up routes for each type of message. + iorouter.on('register', (socket, ctx, next) => { + utils.log(socket, ctx.request.user, 'register'); + router.addUser(socket.id, ctx.request.user); + next(); + }); + iorouter.on('view', (socket, ctx, next) => { + const user = router.getUser(socket.id); + utils.log(socket, `${ctx.request.caseId} (${user.name})`, 'view'); + handlers.addActivity(socket, ctx.request.caseId, user, 'view'); + next(); + }); + iorouter.on('edit', (socket, ctx, next) => { + const user = router.getUser(socket.id); + utils.log(socket, `${ctx.request.caseId} (${user.name})`, 'edit'); + handlers.addActivity(socket, ctx.request.caseId, user, 'edit'); + next(); + }); + iorouter.on('watch', (socket, ctx, next) => { + const user = router.getUser(socket.id); + utils.log(socket, `${ctx.request.caseIds} (${user.name})`, 'watch'); + handlers.watch(socket, ctx.request.caseIds); + next(); + }); + + // On client connection, attach the router and track the socket. + io.on('connection', (socket) => { + connections.push(socket); + utils.log(socket, '', `connected (${connections.length} total)`); + socket.use((packet, next) => { + iorouter.attach(socket, packet, next); + }); + // When the socket disconnects, do an appropriate teardown. + socket.on('disconnect', () => { + utils.log(socket, '', `disconnected (${connections.length - 1} total)`); + handlers.removeSocketActivity(socket.id); + router.removeUser(socket.id); + connections.splice(connections.indexOf(socket), 1); + }); + }); + } +}; + +module.exports = router; diff --git a/app/socket/activity-service.js b/app/socket/service/activity-service.js similarity index 92% rename from app/socket/activity-service.js rename to app/socket/service/activity-service.js index e29c6742..7c77b9b0 100644 --- a/app/socket/activity-service.js +++ b/app/socket/service/activity-service.js @@ -1,5 +1,5 @@ -const redisActivityKeys = require('./redis-keys'); -const utils = require('./utils'); +const keys = require('../redis/keys'); +const utils = require('../utils'); module.exports = (config, redis) => { const ttl = { @@ -8,11 +8,11 @@ module.exports = (config, redis) => { }; const notifyChange = (caseId) => { - redis.publish(redisActivityKeys.baseCase(caseId), Date.now().toString()); + redis.publish(keys.baseCase(caseId), Date.now().toString()); }; const getSocketActivity = async (socketId) => { - const key = redisActivityKeys.socket(socketId); + const key = keys.socket(socketId); return JSON.parse(await redis.get(key)); }; @@ -54,7 +54,7 @@ module.exports = (config, redis) => { await removeSocketActivity(socketId, caseId); // Now store this activity. - const activityKey = redisActivityKeys[activity](caseId); + const activityKey = keys[activity](caseId); return redis.pipeline([ utils.store.userActivity(activityKey, user.uid, utils.score(ttl.activity)), utils.store.socketActivity(socketId, activityKey, caseId, user.uid, ttl.user), @@ -113,6 +113,9 @@ module.exports = (config, redis) => { addActivity, getActivityForCases, getSocketActivity, + getUserDetails, + notifyChange, + redis, removeSocketActivity }; }; diff --git a/app/socket/service/handlers.js b/app/socket/service/handlers.js new file mode 100644 index 00000000..68dba882 --- /dev/null +++ b/app/socket/service/handlers.js @@ -0,0 +1,66 @@ +const utils = require('../utils'); + +module.exports = (activityService, socketServer) => { + /** + * Handle a user viewing or editing a case on a specific socket. + * @param {*} socket The socket they're connected on. + * @param {*} caseId The id of the case they're viewing or editing. + * @param {*} user The user object. + * @param {*} activity Whether they're viewing or editing. + */ + async function addActivity(socket, caseId, user, activity) { + // Update what's being watched. + utils.watch.update(socket, [caseId]); + + // Then add this new activity to redis, which will also clear out the old activity. + await activityService.addActivity(caseId, utils.toUser(user), socket.id, activity); + } + + /** + * Notify all users in a case room about any change to activity on a case. + * @param {*} caseId + */ + async function notify(caseId) { + const cs = await activityService.getActivityForCases([caseId]); + socketServer.to(`case:${caseId}`).emit('activity', cs); + } + + /** + * Remove any activity associated with a socket. This can be called when the + * socket disconnects. + * @param {*} socketId + * @returns + */ + async function removeSocketActivity(socketId) { + return activityService.removeSocketActivity(socketId); + } + + /** + * Handle a user watching a bunch of cases on a specific socket. + * @param {*} socket The socket they're connected on. + * @param {*} caseIds The ids of the cases they're interested in. + */ + async function watch(socket, caseIds) { + // Stop watching the current cases. + utils.watch.stop(socket); + + // Remove the activity for this socket. + await activityService.removeSocketActivity(socket.id); + + // Now watch the specified cases. + utils.watch.cases(socket, caseIds); + + // And immediately dispatch a message about the activity on those cases. + const cs = await activityService.getActivityForCases(caseIds); + socket.emit('activity', cs); + } + + return { + activityService, + addActivity, + notify, + removeSocketActivity, + socketServer, + watch + }; +}; diff --git a/app/socket/utils/get.js b/app/socket/utils/get.js index 0b10618e..954b2310 100644 --- a/app/socket/utils/get.js +++ b/app/socket/utils/get.js @@ -1,4 +1,4 @@ -const redisActivityKeys = require('../redis-keys'); +const redisActivityKeys = require('../redis/keys'); const get = { caseActivities: (caseIds, activity, now) => { diff --git a/app/socket/utils/remove.js b/app/socket/utils/remove.js index ee491903..dad26c6f 100644 --- a/app/socket/utils/remove.js +++ b/app/socket/utils/remove.js @@ -1,5 +1,5 @@ const debug = require('debug')('ccd-case-activity-api:socket-utils-remove'); -const redisActivityKeys = require('../redis-keys'); +const redisActivityKeys = require('../redis/keys'); const remove = { userActivity: (activity) => { diff --git a/app/socket/utils/store.js b/app/socket/utils/store.js index 165c90af..f139bdfe 100644 --- a/app/socket/utils/store.js +++ b/app/socket/utils/store.js @@ -1,5 +1,5 @@ const debug = require('debug')('ccd-case-activity-api:socket-utils-store'); -const redisActivityKeys = require('../redis-keys'); +const redisActivityKeys = require('../redis/keys'); const toUserString = require('./other').toUserString; const store = { diff --git a/test/spec/app/socket/index.spec.js b/test/spec/app/socket/index.spec.js new file mode 100644 index 00000000..54e514eb --- /dev/null +++ b/test/spec/app/socket/index.spec.js @@ -0,0 +1,18 @@ +const SocketIO = require('socket.io'); +const expect = require('chai').expect; +const Socket = require('../../../../app/socket'); + +describe('socket', () => { + const MOCK_SERVER = {}; + const MOCK_REDIS = {}; + it('should be appropriately initialised', () => { + const socket = Socket(MOCK_SERVER, MOCK_REDIS); + expect(socket).not.to.be.undefined; + expect(socket.socketServer).to.be.instanceOf(SocketIO.Server); + expect(socket.activityService).to.be.an('object'); + expect(socket.activityService.redis).to.equal(MOCK_REDIS); + expect(socket.handlers).to.be.an('object'); + expect(socket.handlers.activityService).to.equal(socket.activityService); + expect(socket.handlers.socketServer).to.equal(socket.socketServer); + }) +}); \ No newline at end of file diff --git a/test/spec/app/socket/redis-keys.spec.js b/test/spec/app/socket/redis/keys.spec.js similarity index 89% rename from test/spec/app/socket/redis-keys.spec.js rename to test/spec/app/socket/redis/keys.spec.js index 1441c506..910df7e4 100644 --- a/test/spec/app/socket/redis-keys.spec.js +++ b/test/spec/app/socket/redis/keys.spec.js @@ -1,7 +1,7 @@ const expect = require('chai').expect; -const redisActivityKeys = require('../../../../app/socket/redis-keys'); +const redisActivityKeys = require('../../../../../app/socket/redis/keys'); -describe('socket.redis-keys', () => { +describe('socket.redis.keys', () => { it('should get the correct key for viewing a case', () => { const CASE_ID = '12345678'; diff --git a/test/spec/app/socket/redis/pub-sub.spec.js b/test/spec/app/socket/redis/pub-sub.spec.js new file mode 100644 index 00000000..75804b07 --- /dev/null +++ b/test/spec/app/socket/redis/pub-sub.spec.js @@ -0,0 +1,64 @@ +const expect = require('chai').expect; +const pubSub = require('../../../../../app/socket/redis/pub-sub')(); + +describe('socket.redis.pub-sub', () => { + const MOCK_SUBSCRIBER = { + patterns: [], + events: {}, + psubscribe: (pattern) => { + if (!MOCK_SUBSCRIBER.patterns.includes(pattern)) { + MOCK_SUBSCRIBER.patterns.push(pattern); + } + }, + on: (event, eventHandler) => { + MOCK_SUBSCRIBER.events[event] = eventHandler; + }, + dispatch: (event, channel, message) => { + const handler = MOCK_SUBSCRIBER.events[event]; + if (handler) { + handler(MOCK_SUBSCRIBER.patterns[0], channel, message); + } + } + }; + const MOCK_NOTIFIER = { + messages: [], + notify: (message) => { + MOCK_NOTIFIER.messages.push(message); + } + }; + + afterEach(() => { + MOCK_SUBSCRIBER.patterns.length = 0; + MOCK_SUBSCRIBER.events = {}; + MOCK_NOTIFIER.messages.length = 0; + }); + + describe('init', () => { + it('should handle a null subscription client', () => { + pubSub.init(null, MOCK_NOTIFIER.notify); + expect(MOCK_SUBSCRIBER.patterns).to.have.lengthOf(0); + expect(MOCK_SUBSCRIBER.events).to.deep.equal({}) + }); + it('should handle a null caseNotifier', () => { + pubSub.init(MOCK_SUBSCRIBER, null); + expect(MOCK_SUBSCRIBER.patterns).to.have.lengthOf(0); + expect(MOCK_SUBSCRIBER.events).to.deep.equal({}) + }); + it('should handle appropriate parameters', () => { + pubSub.init(MOCK_SUBSCRIBER, MOCK_NOTIFIER.notify); + expect(MOCK_SUBSCRIBER.patterns).to.have.lengthOf(1) + .and.to.include(`${pubSub.ROOM_PREFIX}*`); + expect(MOCK_SUBSCRIBER.events.pmessage).to.be.a('function'); + expect(MOCK_NOTIFIER.messages).to.have.lengthOf(0); + }); + it('should call the caseNotifier when the correct event is received', () => { + pubSub.init(MOCK_SUBSCRIBER, MOCK_NOTIFIER.notify); + const CASE_ID = '1234567890'; + expect(MOCK_NOTIFIER.messages).to.have.lengthOf(0); + MOCK_SUBSCRIBER.dispatch('pmessage', `${pubSub.ROOM_PREFIX}${CASE_ID}`, new Date().toISOString()); + expect(MOCK_NOTIFIER.messages).to.have.lengthOf(1); + expect(MOCK_NOTIFIER.messages[0]).to.equal(CASE_ID); + }); + }); + +}); diff --git a/test/spec/app/socket/redis/watcher.spec.js b/test/spec/app/socket/redis/watcher.spec.js new file mode 100644 index 00000000..f5151d44 --- /dev/null +++ b/test/spec/app/socket/redis/watcher.spec.js @@ -0,0 +1,12 @@ +const config = require('config'); +const expect = require('chai').expect; +const Redis = require('ioredis'); + +describe('socket.redis.watcher', () => { + it('should instantiate a Redis client', () => { + const watcher = require('../../../../../app/socket/redis/watcher'); + expect(watcher).to.be.instanceOf(Redis); + expect(watcher.options.port).to.equal(config.get('redis.port')); + expect(watcher.options.host).to.equal(config.get('redis.host')); + }); +}); \ No newline at end of file diff --git a/test/spec/app/socket/utils/get.spec.js b/test/spec/app/socket/utils/get.spec.js index 2f9cdc4d..1de07a42 100644 --- a/test/spec/app/socket/utils/get.spec.js +++ b/test/spec/app/socket/utils/get.spec.js @@ -1,6 +1,6 @@ const expect = require('chai').expect; const get = require('../../../../../app/socket/utils/get'); -const redisActivityKeys = require('../../../../../app/socket/redis-keys'); +const redisActivityKeys = require('../../../../../app/socket/redis/keys'); describe('socket.utils', () => { diff --git a/test/spec/app/socket/utils/remove.spec.js b/test/spec/app/socket/utils/remove.spec.js index 756189fc..3912f42a 100644 --- a/test/spec/app/socket/utils/remove.spec.js +++ b/test/spec/app/socket/utils/remove.spec.js @@ -1,6 +1,6 @@ const expect = require('chai').expect; const remove = require('../../../../../app/socket/utils/remove'); -const redisActivityKeys = require('../../../../../app/socket/redis-keys'); +const redisActivityKeys = require('../../../../../app/socket/redis/keys'); describe('socket.utils', () => { diff --git a/test/spec/app/socket/utils/store.spec.js b/test/spec/app/socket/utils/store.spec.js index 2cc486e9..2fce2e45 100644 --- a/test/spec/app/socket/utils/store.spec.js +++ b/test/spec/app/socket/utils/store.spec.js @@ -1,6 +1,6 @@ const expect = require('chai').expect; const store = require('../../../../../app/socket/utils/store'); -const redisActivityKeys = require('../../../../../app/socket/redis-keys'); +const redisActivityKeys = require('../../../../../app/socket/redis/keys'); describe('socket.utils', () => { From cbc6c22c9ca221f8b4a77b513687a7d88d89c813 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Wed, 26 May 2021 18:00:34 +0100 Subject: [PATCH 020/113] Key space and tests Changed the key space for cases (to 'c'), users (to 'u'), and sockets (to 's') to keep them distinct from the existing mechanism. Also started adding unit tests for `socket/service/handlers.js`. --- app/socket/redis/keys.js | 26 +++++-- app/socket/service/handlers.js | 10 +-- app/socket/utils/watch.js | 6 +- test/spec/app/socket/redis/keys.spec.js | 12 ++-- test/spec/app/socket/service/handlers.spec.js | 71 +++++++++++++++++++ test/spec/app/socket/utils/watch.spec.js | 13 ++-- 6 files changed, 112 insertions(+), 26 deletions(-) create mode 100644 test/spec/app/socket/service/handlers.spec.js diff --git a/app/socket/redis/keys.js b/app/socket/redis/keys.js index 7feafc1a..91799b6b 100644 --- a/app/socket/redis/keys.js +++ b/app/socket/redis/keys.js @@ -1,9 +1,21 @@ -const redisActivityKeys = { - view: (caseId) => `case:${caseId}:viewers`, - edit: (caseId) => `case:${caseId}:editors`, - baseCase: (caseId) => `case:${caseId}`, - user: (userId) => `user:${userId}`, - socket: (socketId) => `socket:${socketId}` +const keys = { + prefixes: { + case: 'c', + user: 'u', + socket: 's' + }, + view: (caseId) => keys.compile('case', caseId, 'viewers'), + edit: (caseId) => keys.compile('case', caseId, 'editors'), + baseCase: (caseId) => keys.compile('case', caseId), + user: (userId) => keys.compile('user', userId), + socket: (socketId) => keys.compile('socket', socketId), + compile: (prefix, value, suffix) => { + const key = `${keys.prefixes[prefix]}:${value}`; + if (suffix) { + return `${key}:${suffix}`; + } + return key; + } }; -module.exports = redisActivityKeys; +module.exports = keys; diff --git a/app/socket/service/handlers.js b/app/socket/service/handlers.js index 68dba882..6388c53c 100644 --- a/app/socket/service/handlers.js +++ b/app/socket/service/handlers.js @@ -1,3 +1,4 @@ +const keys = require('../redis/keys'); const utils = require('../utils'); module.exports = (activityService, socketServer) => { @@ -18,21 +19,20 @@ module.exports = (activityService, socketServer) => { /** * Notify all users in a case room about any change to activity on a case. - * @param {*} caseId + * @param {*} caseId The id of the case that has activity and that people should be notified about. */ async function notify(caseId) { const cs = await activityService.getActivityForCases([caseId]); - socketServer.to(`case:${caseId}`).emit('activity', cs); + socketServer.to(keys.baseCase(caseId)).emit('activity', cs); } /** * Remove any activity associated with a socket. This can be called when the * socket disconnects. - * @param {*} socketId - * @returns + * @param {*} socketId The id of the socket to remove activity for. */ async function removeSocketActivity(socketId) { - return activityService.removeSocketActivity(socketId); + await activityService.removeSocketActivity(socketId); } /** diff --git a/app/socket/utils/watch.js b/app/socket/utils/watch.js index 99339203..df5525cd 100644 --- a/app/socket/utils/watch.js +++ b/app/socket/utils/watch.js @@ -1,7 +1,9 @@ +const keys = require('../redis/keys'); + const watch = { case: (socket, caseId) => { if (socket && caseId) { - socket.join(`case:${caseId}`); + socket.join(keys.baseCase(caseId)); } }, cases: (socket, caseIds) => { @@ -14,7 +16,7 @@ const watch = { stop: (socket) => { if (socket) { [...socket.rooms] - .filter((r) => r.indexOf('case:') === 0) // Only case rooms. + .filter((r) => r.indexOf(`${keys.prefixes.case}:`) === 0) // Only case rooms. .forEach((r) => socket.leave(r)); } }, diff --git a/test/spec/app/socket/redis/keys.spec.js b/test/spec/app/socket/redis/keys.spec.js index 910df7e4..c1f94459 100644 --- a/test/spec/app/socket/redis/keys.spec.js +++ b/test/spec/app/socket/redis/keys.spec.js @@ -1,31 +1,31 @@ +const keys = require('../../../../../app/socket/redis/keys'); const expect = require('chai').expect; -const redisActivityKeys = require('../../../../../app/socket/redis/keys'); describe('socket.redis.keys', () => { it('should get the correct key for viewing a case', () => { const CASE_ID = '12345678'; - expect(redisActivityKeys.view(CASE_ID)).to.equal(`case:${CASE_ID}:viewers`); + expect(keys.view(CASE_ID)).to.equal(`${keys.prefixes.case}:${CASE_ID}:viewers`); }); it('should get the correct key for editing a case', () => { const CASE_ID = '12345678'; - expect(redisActivityKeys.edit(CASE_ID)).to.equal(`case:${CASE_ID}:editors`); + expect(keys.edit(CASE_ID)).to.equal(`${keys.prefixes.case}:${CASE_ID}:editors`); }); it('should get the correct base key for a case', () => { const CASE_ID = '12345678'; - expect(redisActivityKeys.baseCase(CASE_ID)).to.equal(`case:${CASE_ID}`); + expect(keys.baseCase(CASE_ID)).to.equal(`${keys.prefixes.case}:${CASE_ID}`); }); it('should get the correct key for a user', () => { const USER_ID = 'abcdef123456'; - expect(redisActivityKeys.user(USER_ID)).to.equal(`user:${USER_ID}`); + expect(keys.user(USER_ID)).to.equal(`${keys.prefixes.user}:${USER_ID}`); }); it('should get the correct key for a socket', () => { const SOCKET_ID = 'zyxwvu987654'; - expect(redisActivityKeys.socket(SOCKET_ID)).to.equal(`socket:${SOCKET_ID}`); + expect(keys.socket(SOCKET_ID)).to.equal(`${keys.prefixes.socket}:${SOCKET_ID}`); }); }); diff --git a/test/spec/app/socket/service/handlers.spec.js b/test/spec/app/socket/service/handlers.spec.js new file mode 100644 index 00000000..f6faff5d --- /dev/null +++ b/test/spec/app/socket/service/handlers.spec.js @@ -0,0 +1,71 @@ +const keys = require('../../../../../app/socket/redis/keys'); +const Handlers = require('../../../../../app/socket/service/handlers'); +const expect = require('chai').expect; + + +describe('socket.service.handlers', () => { + const MOCK_ACTIVITY_SERVICE = { + calls: [], + getActivityForCases: async(caseIds) => { + MOCK_ACTIVITY_SERVICE.calls.push({ method: 'getActivityForCases', params: { caseIds } }); + return caseIds.map((caseId) => { + return { + caseId, + viewers: [], + unknownViewers: 0, + editors: [], + unknownEditors: 0 + }; + }); + }, + removeSocketActivity: (socketId) => { + + } + }; + const MOCK_SOCKET_SERVER = { + messagesTo: [], + to: (room) => { + const messageTo = { room } + MOCK_SOCKET_SERVER.messagesTo.push(messageTo); + return { + emit: (event, message) => { + messageTo.event = event; + messageTo.message = message; + } + }; + } + }; + + afterEach(async () => { + MOCK_ACTIVITY_SERVICE.calls.length = 0; + MOCK_SOCKET_SERVER.messagesTo.length = 0; + }); + + describe('addActivity', () => {}); + + describe('notify', () => { + let handlers; + beforeEach(async () => { + handlers = Handlers(MOCK_ACTIVITY_SERVICE, MOCK_SOCKET_SERVER); + }); + + it('should get activity for specified case and notify watchers', async () => { + const CASE_ID = '1234567890'; + await handlers.notify(CASE_ID); + + // The activity service should have been called. + expect(MOCK_ACTIVITY_SERVICE.calls).to.have.lengthOf(1); + expect(MOCK_ACTIVITY_SERVICE.calls[0].method).to.equal('getActivityForCases'); + expect(MOCK_ACTIVITY_SERVICE.calls[0].params.caseIds).to.deep.equal([CASE_ID]); + + // The socket server should also have been called. + expect(MOCK_SOCKET_SERVER.messagesTo).to.have.lengthOf(1); + expect(MOCK_SOCKET_SERVER.messagesTo[0].room).to.equal(keys.baseCase(CASE_ID)); + expect(MOCK_SOCKET_SERVER.messagesTo[0].event).to.equal('activity'); + expect(MOCK_SOCKET_SERVER.messagesTo[0].message).to.be.an('array').and.to.have.lengthOf(1); + expect(MOCK_SOCKET_SERVER.messagesTo[0].message[0].caseId).to.equal(CASE_ID); + }); + }); + + +}); diff --git a/test/spec/app/socket/utils/watch.spec.js b/test/spec/app/socket/utils/watch.spec.js index 77933bc0..f1b8de11 100644 --- a/test/spec/app/socket/utils/watch.spec.js +++ b/test/spec/app/socket/utils/watch.spec.js @@ -1,5 +1,6 @@ -const expect = require('chai').expect; +const keys = require('../../../../../app/socket/redis/keys'); const watch = require('../../../../../app/socket/utils/watch'); +const expect = require('chai').expect; describe('socket.utils', () => { @@ -32,7 +33,7 @@ describe('socket.utils', () => { watch.case(MOCK_SOCKET, CASE_ID); expect(MOCK_SOCKET.rooms).to.have.lengthOf(2) .and.to.include(MOCK_SOCKET.id) - .and.to.include(`case:${CASE_ID}`); + .and.to.include(keys.baseCase(CASE_ID)); }); it('should handle a null room', () => { const CASE_ID = null; @@ -55,7 +56,7 @@ describe('socket.utils', () => { expect(MOCK_SOCKET.rooms).to.have.lengthOf(CASE_IDS.length + 1) .and.to.include(MOCK_SOCKET.id); CASE_IDS.forEach((id) => { - expect(MOCK_SOCKET.rooms).to.include(`case:${id}`); + expect(MOCK_SOCKET.rooms).to.include(keys.baseCase(id)); }); }); it('should handle a null room', () => { @@ -65,7 +66,7 @@ describe('socket.utils', () => { .and.to.include(MOCK_SOCKET.id); CASE_IDS.forEach((id) => { if (id) { - expect(MOCK_SOCKET.rooms).to.include(`case:${id}`); + expect(MOCK_SOCKET.rooms).to.include(keys.baseCase(id)); } }); }); @@ -127,10 +128,10 @@ describe('socket.utils', () => { expect(MOCK_SOCKET.rooms).to.have.lengthOf(REPLACEMENT_CASE_IDS.length + 1) .and.to.include(MOCK_SOCKET.id); REPLACEMENT_CASE_IDS.forEach((id) => { - expect(MOCK_SOCKET.rooms).to.include(`case:${id}`); + expect(MOCK_SOCKET.rooms).to.include(keys.baseCase(id)); }); CASE_IDS.forEach((id) => { - expect(MOCK_SOCKET.rooms).not.to.include(`case:${id}`); + expect(MOCK_SOCKET.rooms).not.to.include(keys.baseCase(id)); }); }); }); From b2bf897258ec1fe041cc22ca25e033c7ae855271 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Thu, 27 May 2021 09:15:30 +0100 Subject: [PATCH 021/113] Update keys.js --- app/socket/redis/keys.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/socket/redis/keys.js b/app/socket/redis/keys.js index 91799b6b..79b0076f 100644 --- a/app/socket/redis/keys.js +++ b/app/socket/redis/keys.js @@ -1,8 +1,8 @@ const keys = { prefixes: { case: 'c', - user: 'u', - socket: 's' + socket: 's', + user: 'u' }, view: (caseId) => keys.compile('case', caseId, 'viewers'), edit: (caseId) => keys.compile('case', caseId, 'editors'), From 9a0a6f2092e06888c086ac48a5790da831b768e9 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Thu, 27 May 2021 11:01:00 +0100 Subject: [PATCH 022/113] Unit tests Unit tests for `socket/service/handlers` and a little bit of a refactor around the redis keys. --- app/socket/redis/keys.js | 8 +- app/socket/service/activity-service.js | 4 +- app/socket/service/handlers.js | 2 +- app/socket/utils/get.js | 6 +- app/socket/utils/watch.js | 2 +- test/spec/app/socket/redis/keys.spec.js | 6 +- test/spec/app/socket/service/handlers.spec.js | 135 ++++++++++++++++-- test/spec/app/socket/utils/get.spec.js | 16 +-- test/spec/app/socket/utils/remove.spec.js | 6 +- test/spec/app/socket/utils/store.spec.js | 10 +- test/spec/app/socket/utils/watch.spec.js | 10 +- 11 files changed, 161 insertions(+), 44 deletions(-) diff --git a/app/socket/redis/keys.js b/app/socket/redis/keys.js index 79b0076f..a73fb18e 100644 --- a/app/socket/redis/keys.js +++ b/app/socket/redis/keys.js @@ -4,9 +4,11 @@ const keys = { socket: 's', user: 'u' }, - view: (caseId) => keys.compile('case', caseId, 'viewers'), - edit: (caseId) => keys.compile('case', caseId, 'editors'), - baseCase: (caseId) => keys.compile('case', caseId), + case: { + view: (caseId) => keys.compile('case', caseId, 'viewers'), + edit: (caseId) => keys.compile('case', caseId, 'editors'), + base: (caseId) => keys.compile('case', caseId), + }, user: (userId) => keys.compile('user', userId), socket: (socketId) => keys.compile('socket', socketId), compile: (prefix, value, suffix) => { diff --git a/app/socket/service/activity-service.js b/app/socket/service/activity-service.js index 7c77b9b0..95f22e02 100644 --- a/app/socket/service/activity-service.js +++ b/app/socket/service/activity-service.js @@ -8,7 +8,7 @@ module.exports = (config, redis) => { }; const notifyChange = (caseId) => { - redis.publish(keys.baseCase(caseId), Date.now().toString()); + redis.publish(keys.case.base(caseId), Date.now().toString()); }; const getSocketActivity = async (socketId) => { @@ -54,7 +54,7 @@ module.exports = (config, redis) => { await removeSocketActivity(socketId, caseId); // Now store this activity. - const activityKey = keys[activity](caseId); + const activityKey = keys.case[activity](caseId); return redis.pipeline([ utils.store.userActivity(activityKey, user.uid, utils.score(ttl.activity)), utils.store.socketActivity(socketId, activityKey, caseId, user.uid, ttl.user), diff --git a/app/socket/service/handlers.js b/app/socket/service/handlers.js index 6388c53c..09c5348f 100644 --- a/app/socket/service/handlers.js +++ b/app/socket/service/handlers.js @@ -23,7 +23,7 @@ module.exports = (activityService, socketServer) => { */ async function notify(caseId) { const cs = await activityService.getActivityForCases([caseId]); - socketServer.to(keys.baseCase(caseId)).emit('activity', cs); + socketServer.to(keys.case.base(caseId)).emit('activity', cs); } /** diff --git a/app/socket/utils/get.js b/app/socket/utils/get.js index 954b2310..91f9d728 100644 --- a/app/socket/utils/get.js +++ b/app/socket/utils/get.js @@ -1,17 +1,17 @@ -const redisActivityKeys = require('../redis/keys'); +const keys = require('../redis/keys'); const get = { caseActivities: (caseIds, activity, now) => { if (Array.isArray(caseIds) && ['view', 'edit'].indexOf(activity) > -1) { return caseIds.filter((id) => !!id).map((id) => { - return ['zrangebyscore', redisActivityKeys[activity](id), now, '+inf']; + return ['zrangebyscore', keys.case[activity](id), now, '+inf']; }); } return []; }, users: (userIds) => { if (Array.isArray(userIds)) { - return userIds.filter((id) => !!id).map((id) => ['get', redisActivityKeys.user(id)]); + return userIds.filter((id) => !!id).map((id) => ['get', keys.user(id)]); } return []; } diff --git a/app/socket/utils/watch.js b/app/socket/utils/watch.js index df5525cd..8820298d 100644 --- a/app/socket/utils/watch.js +++ b/app/socket/utils/watch.js @@ -3,7 +3,7 @@ const keys = require('../redis/keys'); const watch = { case: (socket, caseId) => { if (socket && caseId) { - socket.join(keys.baseCase(caseId)); + socket.join(keys.case.base(caseId)); } }, cases: (socket, caseIds) => { diff --git a/test/spec/app/socket/redis/keys.spec.js b/test/spec/app/socket/redis/keys.spec.js index c1f94459..5711711e 100644 --- a/test/spec/app/socket/redis/keys.spec.js +++ b/test/spec/app/socket/redis/keys.spec.js @@ -5,17 +5,17 @@ describe('socket.redis.keys', () => { it('should get the correct key for viewing a case', () => { const CASE_ID = '12345678'; - expect(keys.view(CASE_ID)).to.equal(`${keys.prefixes.case}:${CASE_ID}:viewers`); + expect(keys.case.view(CASE_ID)).to.equal(`${keys.prefixes.case}:${CASE_ID}:viewers`); }); it('should get the correct key for editing a case', () => { const CASE_ID = '12345678'; - expect(keys.edit(CASE_ID)).to.equal(`${keys.prefixes.case}:${CASE_ID}:editors`); + expect(keys.case.edit(CASE_ID)).to.equal(`${keys.prefixes.case}:${CASE_ID}:editors`); }); it('should get the correct base key for a case', () => { const CASE_ID = '12345678'; - expect(keys.baseCase(CASE_ID)).to.equal(`${keys.prefixes.case}:${CASE_ID}`); + expect(keys.case.base(CASE_ID)).to.equal(`${keys.prefixes.case}:${CASE_ID}`); }); it('should get the correct key for a user', () => { diff --git a/test/spec/app/socket/service/handlers.spec.js b/test/spec/app/socket/service/handlers.spec.js index f6faff5d..ef9644e6 100644 --- a/test/spec/app/socket/service/handlers.spec.js +++ b/test/spec/app/socket/service/handlers.spec.js @@ -4,10 +4,19 @@ const expect = require('chai').expect; describe('socket.service.handlers', () => { + // An instance that can be tested. + let handlers; + const MOCK_ACTIVITY_SERVICE = { calls: [], - getActivityForCases: async(caseIds) => { - MOCK_ACTIVITY_SERVICE.calls.push({ method: 'getActivityForCases', params: { caseIds } }); + addActivity: async (caseId, user, socketId, activity) => { + const params = { caseId, user, socketId, activity }; + MOCK_ACTIVITY_SERVICE.calls.push({ method: 'addActivity', params }); + return null; + }, + getActivityForCases: async (caseIds) => { + const params = { caseIds }; + MOCK_ACTIVITY_SERVICE.calls.push({ method: 'getActivityForCases', params }); return caseIds.map((caseId) => { return { caseId, @@ -18,8 +27,10 @@ describe('socket.service.handlers', () => { }; }); }, - removeSocketActivity: (socketId) => { - + removeSocketActivity: async (socketId) => { + const params = { socketId }; + MOCK_ACTIVITY_SERVICE.calls.push({ method: 'removeSocketActivity', params }); + return; } }; const MOCK_SOCKET_SERVER = { @@ -35,20 +46,74 @@ describe('socket.service.handlers', () => { }; } }; + const MOCK_SOCKET = { + id: 'socket-id', + rooms: ['socket-id'], + messages: [], + join: (room) => { + if (!MOCK_SOCKET.rooms.includes(room)) { + MOCK_SOCKET.rooms.push(room); + } + }, + leave: (room) => { + const roomIndex = MOCK_SOCKET.rooms.indexOf(room); + if (roomIndex > -1) { + MOCK_SOCKET.rooms.splice(roomIndex, 1); + } + }, + emit: (event, message) => { + MOCK_SOCKET.messages.push({ event, message }); + } + }; + + beforeEach(async () => { + handlers = Handlers(MOCK_ACTIVITY_SERVICE, MOCK_SOCKET_SERVER); + }); afterEach(async () => { MOCK_ACTIVITY_SERVICE.calls.length = 0; MOCK_SOCKET_SERVER.messagesTo.length = 0; + MOCK_SOCKET.rooms.length = 0; + MOCK_SOCKET.rooms.push(MOCK_SOCKET.id); + MOCK_SOCKET.messages.length = 0; }); - describe('addActivity', () => {}); + describe('addActivity', () => { + it('should update what the socket is watching and add activity for the specified case', async () => { + const CASE_ID = '0987654321'; + const USER = { id: 'a', name: 'John Smith' }; + const ACTIVITY = 'view'; - describe('notify', () => { - let handlers; - beforeEach(async () => { - handlers = Handlers(MOCK_ACTIVITY_SERVICE, MOCK_SOCKET_SERVER); + // Pretend the socket is watching a bunch of additional rooms. + MOCK_SOCKET.join(keys.case.base('bob')); + MOCK_SOCKET.join(keys.case.base('fred')); + MOCK_SOCKET.join(keys.case.base('xyz')); + expect(MOCK_SOCKET.rooms).to.have.lengthOf(4); + + // Now make the call. + await handlers.addActivity(MOCK_SOCKET, CASE_ID, USER, ACTIVITY); + + // The socket should be watching that case and that case alone... + // ... plus its own room, which is not related to a case, hence lengthOf(2). + expect(MOCK_SOCKET.rooms).to.have.lengthOf(2) + .and.to.include(MOCK_SOCKET.id) + .and.to.include(keys.case.base(CASE_ID)); + + // The activity service should have been called with appropriate parameters + expect(MOCK_ACTIVITY_SERVICE.calls).to.have.lengthOf(1); + expect(MOCK_ACTIVITY_SERVICE.calls[0].method).to.equal('addActivity'); + expect(MOCK_ACTIVITY_SERVICE.calls[0].params.caseId).to.equal(CASE_ID); + expect(MOCK_ACTIVITY_SERVICE.calls[0].params.socketId).to.equal(MOCK_SOCKET.id); + expect(MOCK_ACTIVITY_SERVICE.calls[0].params.activity).to.equal(ACTIVITY); + // The user parameter should have been transformed appropriatel. + expect(MOCK_ACTIVITY_SERVICE.calls[0].params.user.uid).to.equal(USER.id); + expect(MOCK_ACTIVITY_SERVICE.calls[0].params.user.name).to.equal(USER.name); + expect(MOCK_ACTIVITY_SERVICE.calls[0].params.user.given_name).to.equal('John'); + expect(MOCK_ACTIVITY_SERVICE.calls[0].params.user.family_name).to.equal('Smith'); }); + }); + describe('notify', () => { it('should get activity for specified case and notify watchers', async () => { const CASE_ID = '1234567890'; await handlers.notify(CASE_ID); @@ -60,12 +125,62 @@ describe('socket.service.handlers', () => { // The socket server should also have been called. expect(MOCK_SOCKET_SERVER.messagesTo).to.have.lengthOf(1); - expect(MOCK_SOCKET_SERVER.messagesTo[0].room).to.equal(keys.baseCase(CASE_ID)); + expect(MOCK_SOCKET_SERVER.messagesTo[0].room).to.equal(keys.case.base(CASE_ID)); expect(MOCK_SOCKET_SERVER.messagesTo[0].event).to.equal('activity'); expect(MOCK_SOCKET_SERVER.messagesTo[0].message).to.be.an('array').and.to.have.lengthOf(1); expect(MOCK_SOCKET_SERVER.messagesTo[0].message[0].caseId).to.equal(CASE_ID); }); }); + describe('removeSocketActivity', () => { + it('should remove activity for specified socket', async () => { + const SOCKET_ID = 'abcdef123456'; + await handlers.removeSocketActivity(SOCKET_ID); + + // The activity service should have been called. + expect(MOCK_ACTIVITY_SERVICE.calls).to.have.lengthOf(1); + expect(MOCK_ACTIVITY_SERVICE.calls[0].method).to.equal('removeSocketActivity'); + expect(MOCK_ACTIVITY_SERVICE.calls[0].params.socketId).to.equal(SOCKET_ID); + }); + }); + + describe('watch', () => { + it('should update what the socket is watching, remove its activity, and let the user know what state the cases are in', async () => { + const CASE_IDS = ['0987654321', '9876543210', '8765432109']; + + // Pretend the socket is watching a bunch of additional rooms. + MOCK_SOCKET.join(keys.case.base('bob')); + MOCK_SOCKET.join(keys.case.base('fred')); + MOCK_SOCKET.join(keys.case.base('xyz')); + expect(MOCK_SOCKET.rooms).to.have.lengthOf(4); + + // Now make the call. + await handlers.watch(MOCK_SOCKET, CASE_IDS); + + // The socket should be watching just the cases specified... + // ... plus its own room, which is not related to a case, hence lengthOf(2). + expect(MOCK_SOCKET.rooms).to.have.lengthOf(CASE_IDS.length + 1) + .and.to.include(MOCK_SOCKET.id); + CASE_IDS.forEach((caseId) => { + expect(MOCK_SOCKET.rooms).to.include(keys.case.base(caseId)); + }); + + // The activity service should have been called twice. + expect(MOCK_ACTIVITY_SERVICE.calls).to.have.lengthOf(2); + expect(MOCK_ACTIVITY_SERVICE.calls[0].method).to.equal('removeSocketActivity'); + expect(MOCK_ACTIVITY_SERVICE.calls[0].params.socketId).to.equal(MOCK_SOCKET.id); + expect(MOCK_ACTIVITY_SERVICE.calls[1].method).to.equal('getActivityForCases'); + expect(MOCK_ACTIVITY_SERVICE.calls[1].params.caseIds).to.deep.equal(CASE_IDS); + + // And the socket should have been told about the case statuses. + expect(MOCK_SOCKET.messages).to.have.lengthOf(1); + expect(MOCK_SOCKET.messages[0].event).to.equal('activity'); + expect(MOCK_SOCKET.messages[0].message).to.be.an('array').and.have.lengthOf(CASE_IDS.length); + CASE_IDS.forEach((caseId, index) => { + expect(MOCK_SOCKET.messages[0].message[index].caseId).to.equal(caseId); + }) + }) + }); + }); diff --git a/test/spec/app/socket/utils/get.spec.js b/test/spec/app/socket/utils/get.spec.js index 1de07a42..36bd84a5 100644 --- a/test/spec/app/socket/utils/get.spec.js +++ b/test/spec/app/socket/utils/get.spec.js @@ -1,6 +1,6 @@ const expect = require('chai').expect; const get = require('../../../../../app/socket/utils/get'); -const redisActivityKeys = require('../../../../../app/socket/redis/keys'); +const keys = require('../../../../../app/socket/redis/keys'); describe('socket.utils', () => { @@ -15,7 +15,7 @@ describe('socket.utils', () => { expect(pipes).to.be.an('array').and.have.lengthOf(1); expect(pipes[0]).to.be.an('array').and.have.lengthOf(4); expect(pipes[0][0]).to.equal('zrangebyscore'); - expect(pipes[0][1]).to.equal(redisActivityKeys.view(CASE_IDS[0])); + expect(pipes[0][1]).to.equal(keys.case.view(CASE_IDS[0])); expect(pipes[0][2]).to.equal(NOW); expect(pipes[0][3]).to.equal('+inf'); }); @@ -28,7 +28,7 @@ describe('socket.utils', () => { CASE_IDS.forEach((id, index) => { expect(pipes[index]).to.be.an('array').and.have.lengthOf(4); expect(pipes[index][0]).to.equal('zrangebyscore'); - expect(pipes[index][1]).to.equal(redisActivityKeys.view(id)); + expect(pipes[index][1]).to.equal(keys.case.view(id)); expect(pipes[index][2]).to.equal(NOW); expect(pipes[index][3]).to.equal('+inf'); }); @@ -44,7 +44,7 @@ describe('socket.utils', () => { if (id !== null) { expect(pipes[pipeIndex]).to.be.an('array').and.have.lengthOf(4); expect(pipes[pipeIndex][0]).to.equal('zrangebyscore'); - expect(pipes[pipeIndex][1]).to.equal(redisActivityKeys.view(id)); + expect(pipes[pipeIndex][1]).to.equal(keys.case.view(id)); expect(pipes[pipeIndex][2]).to.equal(NOW); expect(pipes[pipeIndex][3]).to.equal('+inf'); pipeIndex++; @@ -62,7 +62,7 @@ describe('socket.utils', () => { if (id !== null) { expect(pipes[pipeIndex]).to.be.an('array').and.have.lengthOf(4); expect(pipes[pipeIndex][0]).to.equal('zrangebyscore'); - expect(pipes[pipeIndex][1]).to.equal(redisActivityKeys.edit(id)); + expect(pipes[pipeIndex][1]).to.equal(keys.case.edit(id)); expect(pipes[pipeIndex][2]).to.equal(NOW); expect(pipes[pipeIndex][3]).to.equal('+inf'); pipeIndex++; @@ -92,7 +92,7 @@ describe('socket.utils', () => { expect(pipes).to.be.an('array').and.have.lengthOf(1); expect(pipes[0]).to.be.an('array').and.have.lengthOf(2); expect(pipes[0][0]).to.equal('get'); - expect(pipes[0][1]).to.equal(redisActivityKeys.user(USER_IDS[0])); + expect(pipes[0][1]).to.equal(keys.user(USER_IDS[0])); }); it('should get the correct result for multiple user IDs', () => { const USER_IDS = ['1', '8', '2345678', 'x']; @@ -101,7 +101,7 @@ describe('socket.utils', () => { expect(pipes[0]).to.be.an('array').and.have.lengthOf(2); USER_IDS.forEach((id, index) => { expect(pipes[index][0]).to.equal('get'); - expect(pipes[index][1]).to.equal(redisActivityKeys.user(id)); + expect(pipes[index][1]).to.equal(keys.user(id)); }); }); it('should handle a null user ID', () => { @@ -113,7 +113,7 @@ describe('socket.utils', () => { USER_IDS.forEach((id) => { if (id) { expect(pipes[pipeIndex][0]).to.equal('get'); - expect(pipes[pipeIndex][1]).to.equal(redisActivityKeys.user(id)); + expect(pipes[pipeIndex][1]).to.equal(keys.user(id)); pipeIndex++; } }); diff --git a/test/spec/app/socket/utils/remove.spec.js b/test/spec/app/socket/utils/remove.spec.js index 3912f42a..773ca9aa 100644 --- a/test/spec/app/socket/utils/remove.spec.js +++ b/test/spec/app/socket/utils/remove.spec.js @@ -1,6 +1,6 @@ const expect = require('chai').expect; const remove = require('../../../../../app/socket/utils/remove'); -const redisActivityKeys = require('../../../../../app/socket/redis/keys'); +const keys = require('../../../../../app/socket/redis/keys'); describe('socket.utils', () => { @@ -10,7 +10,7 @@ describe('socket.utils', () => { it('should produce an appopriate pipe', () => { const CASE_ID = '1234567890'; const ACTIVITY = { - activityKey: redisActivityKeys.view(CASE_ID), + activityKey: keys.case.view(CASE_ID), userId: 'a' }; const pipe = remove.userActivity(ACTIVITY); @@ -27,7 +27,7 @@ describe('socket.utils', () => { const pipe = remove.socketEntry(SOCKET_ID); expect(pipe).to.be.an('array').and.have.lengthOf(2); expect(pipe[0]).to.equal('del'); - expect(pipe[1]).to.equal(redisActivityKeys.socket(SOCKET_ID)); + expect(pipe[1]).to.equal(keys.socket(SOCKET_ID)); }); }); diff --git a/test/spec/app/socket/utils/store.spec.js b/test/spec/app/socket/utils/store.spec.js index 2fce2e45..7134a0b5 100644 --- a/test/spec/app/socket/utils/store.spec.js +++ b/test/spec/app/socket/utils/store.spec.js @@ -1,6 +1,6 @@ const expect = require('chai').expect; const store = require('../../../../../app/socket/utils/store'); -const redisActivityKeys = require('../../../../../app/socket/redis/keys'); +const keys = require('../../../../../app/socket/redis/keys'); describe('socket.utils', () => { @@ -9,7 +9,7 @@ describe('socket.utils', () => { describe('userActivity', () => { it('should produce an appopriate pipe', () => { const CASE_ID = '1234567890'; - const ACTIVITY_KEY = redisActivityKeys.view(CASE_ID); + const ACTIVITY_KEY = keys.case.view(CASE_ID); const USER_ID = 'a'; const SCORE = 500; const pipe = store.userActivity(ACTIVITY_KEY, USER_ID, SCORE); @@ -28,7 +28,7 @@ describe('socket.utils', () => { const pipe = store.userDetails(USER, TTL); expect(pipe).to.be.an('array').and.have.lengthOf(5); expect(pipe[0]).to.equal('set'); - expect(pipe[1]).to.equal(redisActivityKeys.user(USER.uid)); + expect(pipe[1]).to.equal(keys.user(USER.uid)); expect(pipe[2]).to.equal('{"id":"a","forename":"Bob","surname":"Smith"}'); expect(pipe[3]).to.equal('EX'); // Expires in... expect(pipe[4]).to.equal(TTL); // ...487 seconds. @@ -39,13 +39,13 @@ describe('socket.utils', () => { it('should produce an appopriate pipe', () => { const CASE_ID = '1234567890'; const SOCKET_ID = 'abcdef123456'; - const ACTIVITY_KEY = redisActivityKeys.view(CASE_ID); + const ACTIVITY_KEY = keys.case.view(CASE_ID); const USER_ID = 'a'; const TTL = 487; const pipe = store.socketActivity(SOCKET_ID, ACTIVITY_KEY, CASE_ID, USER_ID, TTL); expect(pipe).to.be.an('array').and.have.lengthOf(5); expect(pipe[0]).to.equal('set'); - expect(pipe[1]).to.equal(redisActivityKeys.socket(SOCKET_ID)); + expect(pipe[1]).to.equal(keys.socket(SOCKET_ID)); expect(pipe[2]).to.equal(`{"activityKey":"${ACTIVITY_KEY}","caseId":"${CASE_ID}","userId":"${USER_ID}"}`); expect(pipe[3]).to.equal('EX'); // Expires in... expect(pipe[4]).to.equal(TTL); // ...487 seconds. diff --git a/test/spec/app/socket/utils/watch.spec.js b/test/spec/app/socket/utils/watch.spec.js index f1b8de11..a2d1650a 100644 --- a/test/spec/app/socket/utils/watch.spec.js +++ b/test/spec/app/socket/utils/watch.spec.js @@ -33,7 +33,7 @@ describe('socket.utils', () => { watch.case(MOCK_SOCKET, CASE_ID); expect(MOCK_SOCKET.rooms).to.have.lengthOf(2) .and.to.include(MOCK_SOCKET.id) - .and.to.include(keys.baseCase(CASE_ID)); + .and.to.include(keys.case.base(CASE_ID)); }); it('should handle a null room', () => { const CASE_ID = null; @@ -56,7 +56,7 @@ describe('socket.utils', () => { expect(MOCK_SOCKET.rooms).to.have.lengthOf(CASE_IDS.length + 1) .and.to.include(MOCK_SOCKET.id); CASE_IDS.forEach((id) => { - expect(MOCK_SOCKET.rooms).to.include(keys.baseCase(id)); + expect(MOCK_SOCKET.rooms).to.include(keys.case.base(id)); }); }); it('should handle a null room', () => { @@ -66,7 +66,7 @@ describe('socket.utils', () => { .and.to.include(MOCK_SOCKET.id); CASE_IDS.forEach((id) => { if (id) { - expect(MOCK_SOCKET.rooms).to.include(keys.baseCase(id)); + expect(MOCK_SOCKET.rooms).to.include(keys.case.base(id)); } }); }); @@ -128,10 +128,10 @@ describe('socket.utils', () => { expect(MOCK_SOCKET.rooms).to.have.lengthOf(REPLACEMENT_CASE_IDS.length + 1) .and.to.include(MOCK_SOCKET.id); REPLACEMENT_CASE_IDS.forEach((id) => { - expect(MOCK_SOCKET.rooms).to.include(keys.baseCase(id)); + expect(MOCK_SOCKET.rooms).to.include(keys.case.base(id)); }); CASE_IDS.forEach((id) => { - expect(MOCK_SOCKET.rooms).not.to.include(keys.baseCase(id)); + expect(MOCK_SOCKET.rooms).not.to.include(keys.case.base(id)); }); }); }); From b30efab8e92549a41aec13976025792d0be51a0e Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Thu, 27 May 2021 14:42:47 +0100 Subject: [PATCH 023/113] Fixed the subscriptions The subscriptions were still looking for a 'case:' prefix but it's now 'c:'. --- app/socket/redis/pub-sub.js | 9 ++++----- test/spec/app/socket/redis/pub-sub.spec.js | 5 +++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/socket/redis/pub-sub.js b/app/socket/redis/pub-sub.js index 5d4fe7d6..5a311f31 100644 --- a/app/socket/redis/pub-sub.js +++ b/app/socket/redis/pub-sub.js @@ -1,16 +1,15 @@ -const ROOM_PREFIX = 'case:'; +const keys = require('./keys'); module.exports = () => { return { init: (sub, caseNotifier) => { if (sub && typeof caseNotifier === 'function') { - sub.psubscribe(`${ROOM_PREFIX}*`); + sub.psubscribe(`${keys.prefixes.case}:*`); sub.on('pmessage', (_, room) => { - const caseId = room.replace(ROOM_PREFIX, ''); + const caseId = room.replace(`${keys.prefixes.case}:`, ''); caseNotifier(caseId); }); } - }, - ROOM_PREFIX + } }; }; diff --git a/test/spec/app/socket/redis/pub-sub.spec.js b/test/spec/app/socket/redis/pub-sub.spec.js index 75804b07..92cd064f 100644 --- a/test/spec/app/socket/redis/pub-sub.spec.js +++ b/test/spec/app/socket/redis/pub-sub.spec.js @@ -1,4 +1,5 @@ const expect = require('chai').expect; +const keys = require('../../../../../app/socket/redis/keys'); const pubSub = require('../../../../../app/socket/redis/pub-sub')(); describe('socket.redis.pub-sub', () => { @@ -47,7 +48,7 @@ describe('socket.redis.pub-sub', () => { it('should handle appropriate parameters', () => { pubSub.init(MOCK_SUBSCRIBER, MOCK_NOTIFIER.notify); expect(MOCK_SUBSCRIBER.patterns).to.have.lengthOf(1) - .and.to.include(`${pubSub.ROOM_PREFIX}*`); + .and.to.include(`${keys.prefixes.case}:*`); expect(MOCK_SUBSCRIBER.events.pmessage).to.be.a('function'); expect(MOCK_NOTIFIER.messages).to.have.lengthOf(0); }); @@ -55,7 +56,7 @@ describe('socket.redis.pub-sub', () => { pubSub.init(MOCK_SUBSCRIBER, MOCK_NOTIFIER.notify); const CASE_ID = '1234567890'; expect(MOCK_NOTIFIER.messages).to.have.lengthOf(0); - MOCK_SUBSCRIBER.dispatch('pmessage', `${pubSub.ROOM_PREFIX}${CASE_ID}`, new Date().toISOString()); + MOCK_SUBSCRIBER.dispatch('pmessage', `${keys.prefixes.case}:${CASE_ID}`, new Date().toISOString()); expect(MOCK_NOTIFIER.messages).to.have.lengthOf(1); expect(MOCK_NOTIFIER.messages[0]).to.equal(CASE_ID); }); From 985bdc33295b9debab64a68907df88e592c1828d Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Thu, 27 May 2021 14:43:09 +0100 Subject: [PATCH 024/113] Unit tests Unit tests for `socket/router`. --- app/socket/router/index.js | 20 +- test/spec/app/socket/router/index.spec.js | 219 ++++++++++++++++++++++ 2 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 test/spec/app/socket/router/index.spec.js diff --git a/app/socket/router/index.js b/app/socket/router/index.js index 714c710c..3a97e346 100644 --- a/app/socket/router/index.js +++ b/app/socket/router/index.js @@ -12,6 +12,18 @@ const router = { getUser: (socketId) => { return users[socketId]; }, + addConnection: (socket) => { + connections.push(socket); + }, + removeConnection: (socket) => { + const socketIndex = connections.indexOf(socket); + if (socketIndex > -1) { + connections.splice(socketIndex, 1); + } + }, + getConnections: () => { + return [...connections]; + }, init: (io, iorouter, handlers) => { // Set up routes for each type of message. iorouter.on('register', (socket, ctx, next) => { @@ -40,17 +52,17 @@ const router = { // On client connection, attach the router and track the socket. io.on('connection', (socket) => { - connections.push(socket); - utils.log(socket, '', `connected (${connections.length} total)`); + router.addConnection(socket); + utils.log(socket, '', `connected (${router.getConnections().length} total)`); socket.use((packet, next) => { iorouter.attach(socket, packet, next); }); // When the socket disconnects, do an appropriate teardown. socket.on('disconnect', () => { - utils.log(socket, '', `disconnected (${connections.length - 1} total)`); + utils.log(socket, '', `disconnected (${router.getConnections().length - 1} total)`); handlers.removeSocketActivity(socket.id); router.removeUser(socket.id); - connections.splice(connections.indexOf(socket), 1); + router.removeConnection(socket); }); }); } diff --git a/test/spec/app/socket/router/index.spec.js b/test/spec/app/socket/router/index.spec.js new file mode 100644 index 00000000..6938edf4 --- /dev/null +++ b/test/spec/app/socket/router/index.spec.js @@ -0,0 +1,219 @@ +const expect = require('chai').expect; +const router = require('../../../../../app/socket/router'); + +describe('socket.router', () => { + const MOCK_SOCKET_SERVER = { + events: {}, + on: (event, eventHandler) => { + MOCK_SOCKET_SERVER.events[event] = eventHandler; + }, + dispatch: (event, socket) => { + const handler = MOCK_SOCKET_SERVER.events[event]; + if (handler) { + handler(socket); + } + } + }; + const MOCK_IO_ROUTER = { + events: {}, + attachments: [], + on: (event, eventHandler) => { + MOCK_IO_ROUTER.events[event] = eventHandler; + }, + attach: (socket, packet, next) => { + MOCK_IO_ROUTER.attachments.push({ socket, packet, next }); + }, + dispatch: (event, socket, ctx, next) => { + const handler = MOCK_IO_ROUTER.events[event]; + if (handler) { + handler(socket, ctx, next); + } + } + }; + const MOCK_HANDLERS = { + calls: [], + addActivity: (socket, caseId, user, activity) => { + const params = { socket, caseId, user, activity }; + MOCK_HANDLERS.calls.push({ method: 'addActivity', params }); + }, + watch: (socket, caseIds) => { + const params = { socket, caseIds }; + MOCK_HANDLERS.calls.push({ method: 'watch', params }); + }, + removeSocketActivity: async (socketId) => { + const params = { socketId }; + MOCK_HANDLERS.calls.push({ method: 'removeSocketActivity', params }); + } + }; + const MOCK_SOCKET = { + id: 'socket-id', + rooms: ['socket-id'], + events: {}, + messages: [], + using: [], + join: (room) => { + if (!MOCK_SOCKET.rooms.includes(room)) { + MOCK_SOCKET.rooms.push(room); + } + }, + leave: (room) => { + const roomIndex = MOCK_SOCKET.rooms.indexOf(room); + if (roomIndex > -1) { + MOCK_SOCKET.rooms.splice(roomIndex, 1); + } + }, + emit: (event, message) => { + MOCK_SOCKET.messages.push({ event, message }); + }, + use: (fn) => { + MOCK_SOCKET.using.push(fn); + }, + on: (event, eventHandler) => { + MOCK_SOCKET.events[event] = eventHandler; + }, + dispatch: (event) => { + const handler = MOCK_SOCKET.events[event]; + if (handler) { + handler(MOCK_SOCKET); + } + } + }; + + beforeEach(() => { + router.init(MOCK_SOCKET_SERVER, MOCK_IO_ROUTER, MOCK_HANDLERS); + }); + + afterEach(() => { + MOCK_SOCKET_SERVER.events = {}; + MOCK_IO_ROUTER.events = {}; + MOCK_IO_ROUTER.attachments.length = 0; + MOCK_HANDLERS.calls.length = 0; + MOCK_SOCKET.using.length = 0; + router.removeUser(MOCK_SOCKET.id); + router.removeConnection(MOCK_SOCKET); + }); + + describe('init', () => { + it('should have set up the appropriate events on the socket server', () => { + const EXPECTED_EVENTS = ['connection']; + EXPECTED_EVENTS.forEach((event) => { + expect(MOCK_SOCKET_SERVER.events[event]).to.be.a('function'); + }); + }); + it('should have set up the appropriate events on the io router', () => { + const EXPECTED_EVENTS = ['register', 'view', 'edit', 'watch']; + EXPECTED_EVENTS.forEach((event) => { + expect(MOCK_IO_ROUTER.events[event]).to.be.a('function'); + }); + }); + }); + + describe('iorouter', () => { + const MOCK_CONTEXT_REGISTER = { + request: { + user: { id: 'a', name: 'Bob Smith' } + } + }; + const MOCK_CONTEXT = { + request: { + caseId: '1234567890', + caseIds: ['2345678901', '3456789012', '4567890123'] + } + }; + beforeEach(() => { + // We need to register before each call as it sets up the user. + MOCK_IO_ROUTER.dispatch('register', MOCK_SOCKET, MOCK_CONTEXT_REGISTER, () => {}); + }); + it('should appropriately handle registering a user', () => { + expect(router.getUser(MOCK_SOCKET.id)).to.deep.equal(MOCK_CONTEXT_REGISTER.request.user); + }); + it('should appropriately handle viewing a case', () => { + const ACTIVITY = 'view'; + let nextCalled = false; + MOCK_IO_ROUTER.dispatch(ACTIVITY, MOCK_SOCKET, MOCK_CONTEXT, () => { + // next() should be called last so everything else should have been done already. + nextCalled = true; + expect(MOCK_HANDLERS.calls).to.have.lengthOf(1); + expect(MOCK_HANDLERS.calls[0].method).to.equal('addActivity'); + expect(MOCK_HANDLERS.calls[0].params.socket).to.equal(MOCK_SOCKET); + expect(MOCK_HANDLERS.calls[0].params.caseId).to.equal(MOCK_CONTEXT.request.caseId); + // Note that the MOCK_CONTEXT doesn't include the user, which means we had to get it from elsewhere. + expect(MOCK_HANDLERS.calls[0].params.user).to.deep.equal(MOCK_CONTEXT_REGISTER.request.user); + expect(MOCK_HANDLERS.calls[0].params.activity).to.equal(ACTIVITY); + }); + expect(nextCalled).to.be.true; + }); + it('should appropriately handle editing a case', () => { + const ACTIVITY = 'edit'; + let nextCalled = false; + MOCK_IO_ROUTER.dispatch(ACTIVITY, MOCK_SOCKET, MOCK_CONTEXT, () => { + // next() should be called last so everything else should have been done already. + nextCalled = true; + expect(MOCK_HANDLERS.calls).to.have.lengthOf(1); + expect(MOCK_HANDLERS.calls[0].method).to.equal('addActivity'); + expect(MOCK_HANDLERS.calls[0].params.socket).to.equal(MOCK_SOCKET); + expect(MOCK_HANDLERS.calls[0].params.caseId).to.equal(MOCK_CONTEXT.request.caseId); + // Note that the MOCK_CONTEXT doesn't include the user, which means we had to get it from elsewhere. + expect(MOCK_HANDLERS.calls[0].params.user).to.deep.equal(MOCK_CONTEXT_REGISTER.request.user); + expect(MOCK_HANDLERS.calls[0].params.activity).to.equal(ACTIVITY); + }); + expect(nextCalled).to.be.true; + }); + it('should appropriately handle watching cases', () => { + const ACTIVITY = 'watch'; + let nextCalled = false; + MOCK_IO_ROUTER.dispatch(ACTIVITY, MOCK_SOCKET, MOCK_CONTEXT, () => { + // next() should be called last so everything else should have been done already. + nextCalled = true; + expect(MOCK_HANDLERS.calls).to.have.lengthOf(1); + expect(MOCK_HANDLERS.calls[0].method).to.equal('watch'); + expect(MOCK_HANDLERS.calls[0].params.socket).to.equal(MOCK_SOCKET); + expect(MOCK_HANDLERS.calls[0].params.caseIds).to.deep.equal(MOCK_CONTEXT.request.caseIds); + }); + expect(nextCalled).to.be.true; + }); + }); + + describe('io', () => { + const MOCK_CONTEXT_REGISTER = { + request: { + user: { id: 'a', name: 'Bob Smith' } + } + }; + beforeEach(() => { + // We need to register before each call as it sets up the user. + MOCK_IO_ROUTER.dispatch('register', MOCK_SOCKET, MOCK_CONTEXT_REGISTER, () => {}); + + // Dispatch the connection each time. + MOCK_SOCKET_SERVER.dispatch('connection', MOCK_SOCKET); + }); + it('should appropriately handle a new connection', () => { + expect(router.getConnections()).to.have.lengthOf(1) + .and.to.contain(MOCK_SOCKET); + expect(MOCK_SOCKET.using).to.have.lengthOf(1); + expect(MOCK_SOCKET.using[0]).to.be.a('function'); + expect(MOCK_SOCKET.events.disconnect).to.be.a('function'); + }); + it('should handle a socket use', () => { + const useFn = MOCK_SOCKET.using[0]; + const PACKET = 'packet'; + const NEXT_FN = () => {}; + + expect(MOCK_IO_ROUTER.attachments).to.have.lengthOf(0); + useFn(PACKET, NEXT_FN); + expect(MOCK_IO_ROUTER.attachments).to.have.lengthOf(1); + expect(MOCK_IO_ROUTER.attachments[0].socket).to.equal(MOCK_SOCKET); + expect(MOCK_IO_ROUTER.attachments[0].packet).to.equal(PACKET); + expect(MOCK_IO_ROUTER.attachments[0].next).to.equal(NEXT_FN); + }); + it('should handle a socket disconnecting', () => { + MOCK_SOCKET.dispatch('disconnect'); + expect(MOCK_HANDLERS.calls).to.have.lengthOf(1); + expect(MOCK_HANDLERS.calls[0].method).to.equal('removeSocketActivity'); + expect(MOCK_HANDLERS.calls[0].params.socketId).to.equal(MOCK_SOCKET.id); + expect(router.getUser(MOCK_SOCKET.id)).to.be.undefined; + expect(router.getConnections()).to.have.lengthOf(0); + }); + }); + +}); From caee0ae02b2a0214b9b6b2e0c2548bef30724bd6 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Fri, 28 May 2021 09:25:20 +0100 Subject: [PATCH 025/113] Redis subscriber Did away with the need for a separate redis client by simply calling `redis.duplicate()` on the existing one - much cleaner. --- app/socket/index.js | 2 +- app/socket/redis/pub-sub.js | 8 ++++---- app/socket/redis/watcher.js | 4 ---- test/spec/app/socket/index.spec.js | 16 +++++++++++++++- test/spec/app/socket/redis/watcher.spec.js | 12 ------------ 5 files changed, 20 insertions(+), 22 deletions(-) delete mode 100644 app/socket/redis/watcher.js delete mode 100644 test/spec/app/socket/redis/watcher.spec.js diff --git a/app/socket/index.js b/app/socket/index.js index a0f9332d..34300519 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -4,7 +4,6 @@ const SocketIO = require('socket.io'); const ActivityService = require('./service/activity-service'); const Handlers = require('./service/handlers'); -const watcher = require('./redis/watcher'); const pubSub = require('./redis/pub-sub')(); const router = require('./router'); @@ -29,6 +28,7 @@ module.exports = (server, redis) => { } }); const handlers = Handlers(activityService, socketServer); + const watcher = redis.duplicate(); pubSub.init(watcher, handlers.notify); router.init(socketServer, new IORouter(), handlers); diff --git a/app/socket/redis/pub-sub.js b/app/socket/redis/pub-sub.js index 5a311f31..7cb287ad 100644 --- a/app/socket/redis/pub-sub.js +++ b/app/socket/redis/pub-sub.js @@ -2,10 +2,10 @@ const keys = require('./keys'); module.exports = () => { return { - init: (sub, caseNotifier) => { - if (sub && typeof caseNotifier === 'function') { - sub.psubscribe(`${keys.prefixes.case}:*`); - sub.on('pmessage', (_, room) => { + init: (watcher, caseNotifier) => { + if (watcher && typeof caseNotifier === 'function') { + watcher.psubscribe(`${keys.prefixes.case}:*`); + watcher.on('pmessage', (_, room) => { const caseId = room.replace(`${keys.prefixes.case}:`, ''); caseNotifier(caseId); }); diff --git a/app/socket/redis/watcher.js b/app/socket/redis/watcher.js deleted file mode 100644 index 196bb24c..00000000 --- a/app/socket/redis/watcher.js +++ /dev/null @@ -1,4 +0,0 @@ -const debug = require('debug')('ccd-case-activity-api:redis-watcher'); -const watcher = require('../../redis/instantiator')(debug); - -module.exports = watcher; diff --git a/test/spec/app/socket/index.spec.js b/test/spec/app/socket/index.spec.js index 54e514eb..34f8b893 100644 --- a/test/spec/app/socket/index.spec.js +++ b/test/spec/app/socket/index.spec.js @@ -4,7 +4,20 @@ const Socket = require('../../../../app/socket'); describe('socket', () => { const MOCK_SERVER = {}; - const MOCK_REDIS = {}; + const MOCK_REDIS = { + duplicated: false, + duplicate: () => { + MOCK_REDIS.duplicated = true; + return MOCK_REDIS; + }, + psubscribe: () => {}, + on: () => {} + }; + + afterEach(() => { + MOCK_REDIS.duplicated = false; + }); + it('should be appropriately initialised', () => { const socket = Socket(MOCK_SERVER, MOCK_REDIS); expect(socket).not.to.be.undefined; @@ -14,5 +27,6 @@ describe('socket', () => { expect(socket.handlers).to.be.an('object'); expect(socket.handlers.activityService).to.equal(socket.activityService); expect(socket.handlers.socketServer).to.equal(socket.socketServer); + expect(MOCK_REDIS.duplicated).to.be.true; }) }); \ No newline at end of file diff --git a/test/spec/app/socket/redis/watcher.spec.js b/test/spec/app/socket/redis/watcher.spec.js deleted file mode 100644 index f5151d44..00000000 --- a/test/spec/app/socket/redis/watcher.spec.js +++ /dev/null @@ -1,12 +0,0 @@ -const config = require('config'); -const expect = require('chai').expect; -const Redis = require('ioredis'); - -describe('socket.redis.watcher', () => { - it('should instantiate a Redis client', () => { - const watcher = require('../../../../../app/socket/redis/watcher'); - expect(watcher).to.be.instanceOf(Redis); - expect(watcher.options.port).to.equal(config.get('redis.port')); - expect(watcher.options.host).to.equal(config.get('redis.host')); - }); -}); \ No newline at end of file From 7f8a6786ed7dcb12558b2e2204df6beda2ba967a Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Fri, 28 May 2021 09:26:07 +0100 Subject: [PATCH 026/113] Unit tests Unit tests for the activity service. --- app/socket/service/activity-service.js | 84 ++-- .../socket/service/activity-service.spec.js | 391 ++++++++++++++++++ test/spec/app/socket/utils/watch.spec.js | 1 - 3 files changed, 444 insertions(+), 32 deletions(-) create mode 100644 test/spec/app/socket/service/activity-service.spec.js diff --git a/app/socket/service/activity-service.js b/app/socket/service/activity-service.js index 95f22e02..fe64578f 100644 --- a/app/socket/service/activity-service.js +++ b/app/socket/service/activity-service.js @@ -8,84 +8,105 @@ module.exports = (config, redis) => { }; const notifyChange = (caseId) => { - redis.publish(keys.case.base(caseId), Date.now().toString()); + if (caseId) { + redis.publish(keys.case.base(caseId), Date.now().toString()); + } }; const getSocketActivity = async (socketId) => { - const key = keys.socket(socketId); - return JSON.parse(await redis.get(key)); + if (socketId) { + const key = keys.socket(socketId); + return JSON.parse(await redis.get(key)); + } + return null; }; const getUserDetails = async (userIds) => { - if (userIds.length > 0) { + if (Array.isArray(userIds) && userIds.length > 0) { // Get hold of the details. const details = await redis.pipeline(utils.get.users(userIds)).exec(); // Now turn them into a map. return details.reduce((obj, item) => { - const user = JSON.parse(item[1]); - if (user) { + if (item[1]) { + const user = JSON.parse(item[1]); obj[user.id] = { forename: user.forename, surname: user.surname }; } return obj; }, {}); } - return []; + return {}; }; - const removeSocketActivity = async (socketId, skipNotifyForCaseId) => { + const doRemoveSocketActivity = async (socketId) => { // First make sure we actually have some activity to remove. const activity = await getSocketActivity(socketId); if (activity) { - return redis.pipeline([ + await redis.pipeline([ utils.remove.userActivity(activity), utils.remove.socketEntry(socketId) - ]).exec().then(() => { - if (activity.caseId !== skipNotifyForCaseId) { - notifyChange(activity.caseId); - } - }); + ]).exec(); + return activity.caseId; } return null; }; - const addActivity = async (caseId, user, socketId, activity) => { - // First, clear out any existing activity on this socket. - await removeSocketActivity(socketId, caseId); + const removeSocketActivity = async (socketId) => { + const removedCaseId = await doRemoveSocketActivity(socketId); + if (removedCaseId) { + notifyChange(removedCaseId); + } + }; + const doAddActivity = async (caseId, user, socketId, activity) => { // Now store this activity. const activityKey = keys.case[activity](caseId); return redis.pipeline([ utils.store.userActivity(activityKey, user.uid, utils.score(ttl.activity)), utils.store.socketActivity(socketId, activityKey, caseId, user.uid, ttl.user), utils.store.userDetails(user, ttl.user) - ]).exec().then(() => { + ]).exec(); + }; + + const addActivity = async (caseId, user, socketId, activity) => { + if (caseId && user && socketId && activity) { + // First, clear out any existing activity on this socket. + const removedCaseId = await doRemoveSocketActivity(socketId); + + // Now store this activity. + await doAddActivity(caseId, user, socketId, activity); + if (removedCaseId !== caseId) { + notifyChange(removedCaseId); + } notifyChange(caseId); - }); + } + return null; }; const getActivityForCases = async (caseIds) => { + if (!Array.isArray(caseIds) || caseIds.length === 0) { + return []; + } let uniqueUserIds = []; let caseViewers = []; let caseEditors = []; const now = Date.now(); - const getPromise = (activity, cb, failureMessage) => { - return redis.pipeline( + const getPromise = async (activity, failureMessage, cb) => { + const result = await redis.pipeline( utils.get.caseActivities(caseIds, activity, now) - ).exec().then((result) => { - redis.logPipelineFailures(result, failureMessage); - cb(result); - uniqueUserIds = utils.extractUniqueUserIds(result, uniqueUserIds); - }); + ).exec(); + redis.logPipelineFailures(result, failureMessage); + cb(result); + uniqueUserIds = utils.extractUniqueUserIds(result, uniqueUserIds); }; // Set up the promises fore view and edit. - const caseViewersPromise = getPromise('view', (result) => { + const caseViewersPromise = getPromise('view', 'caseViewersPromise', (result) => { caseViewers = result; - }, 'caseViewersPromise'); - const caseEditorsPromise = getPromise('edit', (result) => { + }); + const caseEditorsPromise = getPromise('edit', 'caseEditorsPromise', (result) => { caseEditors = result; - }, 'caseEditorsPromise'); + }); // Now wait until both promises have been completed. await Promise.all([caseViewersPromise, caseEditorsPromise]); @@ -116,6 +137,7 @@ module.exports = (config, redis) => { getUserDetails, notifyChange, redis, - removeSocketActivity + removeSocketActivity, + ttl }; }; diff --git a/test/spec/app/socket/service/activity-service.spec.js b/test/spec/app/socket/service/activity-service.spec.js new file mode 100644 index 00000000..46939bbf --- /dev/null +++ b/test/spec/app/socket/service/activity-service.spec.js @@ -0,0 +1,391 @@ +const keys = require('../../../../../app/socket/redis/keys'); +const ActivityService = require('../../../../../app/socket/service/activity-service'); +const expect = require('chai').expect; +const sandbox = require("sinon").createSandbox(); + +describe('socket.service.activity-service', () => { + // An instance that can be tested. + let activityService; + + const USER_ID = 'a'; + const CASE_ID = '1234567890'; + const TTL_USER = 20; + const TTL_ACTIVITY = 99; + const MOCK_CONFIG = { + getCalls: [], + keys: { + 'redis.activityTtlSec': TTL_ACTIVITY, + 'redis.userDetailsTtlSec': TTL_USER + }, + get: (key) => { + MOCK_CONFIG.getCalls.push(key); + return MOCK_CONFIG.keys[key]; + } + }; + const MOCK_REDIS = { + messages: [], + gets: [], + pipelines: [], + pipelineFailureLogs: [], + pipelineMode: undefined, + publish: (channel, message) => { + MOCK_REDIS.messages.push({ channel, message }); + }, + get: (key) => { + MOCK_REDIS.gets.push(key); + return JSON.stringify({ + activityKey: keys.case.view(CASE_ID), + caseId: CASE_ID, + userId: USER_ID + }); + }, + pipeline: (pipes) => { + MOCK_REDIS.pipelines.push(pipes); + let result = null; + let execResult = null; + switch (MOCK_REDIS.pipelineMode) { + case 'get': + if (MOCK_REDIS.isUserGet(pipes)) { + execResult = MOCK_REDIS.userPipeline(pipes); + } else { + execResult = MOCK_REDIS.casePipeline(pipes); + } + break; + case 'socket': + execResult = CASE_ID; + break; + case 'user': + execResult = MOCK_REDIS.userPipeline(pipes); + break; + } + return { + exec: () => { + return execResult; + } + }; + }, + casePipeline: (pipes) => { + return pipes.map((pipe) => { + // ['zrangebyscore', keys.case[activity](id), now, '+inf']; + const id = pipe[1].replace(`${keys.prefixes.case}:`, ''); + return [null, [USER_ID, 'MISSING']]; + }); + }, + userPipeline: (pipes) => { + return pipes.map((pipe) => { + const id = pipe[1].replace(`${keys.prefixes.user}:`, ''); + if (id === 'MISSING') { + return [null, null]; + } + return [null, JSON.stringify({ id, forename: `Bob ${id.toUpperCase()}`, surname: 'Smith' })]; + }); + }, + logPipelineFailures: (result, message) => { + MOCK_REDIS.pipelineFailureLogs.push({ result, message }); + }, + isUserGet: (pipes) => { + if (pipes.length > 0) { + return pipes[0][0] === 'get'; + } + return false; + } + }; + + beforeEach(() => { + activityService = ActivityService(MOCK_CONFIG, MOCK_REDIS); + }); + + afterEach(async () => { + MOCK_CONFIG.getCalls.length = 0; + MOCK_REDIS.messages.length = 0; + MOCK_REDIS.gets.length = 0; + MOCK_REDIS.pipelines.length = 0; + MOCK_REDIS.pipelineMode = undefined; + MOCK_REDIS.pipelineFailureLogs.length = 0; + }); + + it('should have appropriately initialised from the config', () => { + expect(MOCK_CONFIG.getCalls).to.include('redis.activityTtlSec'); + expect(activityService.ttl.activity).to.equal(TTL_ACTIVITY); + expect(MOCK_CONFIG.getCalls).to.include('redis.userDetailsTtlSec'); + expect(activityService.ttl.user).to.equal(TTL_USER); + }); + + describe('notifyChange', () => { + it('should broadcast via redis that there is a change to a case', () => { + const NOW = Date.now(); + activityService.notifyChange(CASE_ID); + expect(MOCK_REDIS.messages).to.have.lengthOf(1); + expect(MOCK_REDIS.messages[0].channel).to.equal(keys.case.base(CASE_ID)); + const messageTS = parseInt(MOCK_REDIS.messages[0].message, 10); + expect(messageTS).to.be.approximately(NOW, 5); // Within 5ms. + }); + it('should handle a null caseId', () => { + activityService.notifyChange(null); + expect(MOCK_REDIS.messages).to.have.lengthOf(0); // Should have been no broadcast. + }); + }); + + describe('getSocketActivity', () => { + it('should appropriately get socket activity', async () => { + const SOCKET_ID = 'abcdef123456'; + const activity = await activityService.getSocketActivity(SOCKET_ID); + expect(MOCK_REDIS.gets).to.have.lengthOf(1); + expect(MOCK_REDIS.gets[0]).to.equal(keys.socket(SOCKET_ID)); + expect(activity).to.be.an('object'); + expect(activity.activityKey).to.equal(keys.case.view(CASE_ID)); // Just our mock response. + }); + it('should handle a null caseId', async () => { + const SOCKET_ID = null; + const activity = await activityService.getSocketActivity(SOCKET_ID); + expect(MOCK_REDIS.messages).to.have.lengthOf(0); // Should have been no broadcast. + expect(activity).to.be.null; + }); + }); + + describe('getUserDetails', () => { + beforeEach(() => { + MOCK_REDIS.pipelineMode = 'user'; + }); + + it('should appropriately get user details', async () => { + const USER_IDS = ['a', 'b']; + const userDetails = await activityService.getUserDetails(USER_IDS); + expect(MOCK_REDIS.pipelines).to.have.lengthOf(1); + const pipes = MOCK_REDIS.pipelines[0]; + expect(pipes).to.be.an('array').and.have.lengthOf(USER_IDS.length); + USER_IDS.forEach((id, index) => { + const user = userDetails[id]; + expect(user).to.be.an('object'); + expect(user.forename).to.be.a('string'); + expect(user.surname).to.be.a('string'); + + expect(pipes[index]).to.be.an('array') + .and.to.have.lengthOf(2) + .and.to.contain('get') + .and.to.contain(keys.user(id)); + }); + }); + it('should handle null userIds', async () => { + const USER_IDS = null; + const userDetails = await activityService.getUserDetails(USER_IDS); + expect(MOCK_REDIS.pipelines).to.have.lengthOf(0); // Should have been no calls to redis. + expect(userDetails).to.deep.equal({}); + }); + it('should handle empty userIds', async () => { + const USER_IDS = []; + const userDetails = await activityService.getUserDetails(USER_IDS); + expect(MOCK_REDIS.pipelines).to.have.lengthOf(0); // Should have been no calls to redis. + expect(userDetails).to.deep.equal({}); + }); + it('should handle a missing user', async () => { + const USER_IDS = ['a', 'b', 'MISSING']; + const userDetails = await activityService.getUserDetails(USER_IDS); + expect(MOCK_REDIS.pipelines).to.have.lengthOf(1); + const pipes = MOCK_REDIS.pipelines[0]; + expect(pipes).to.be.an('array').and.have.lengthOf(USER_IDS.length); + USER_IDS.forEach((id, index) => { + if (id === 'MISSING') { + expect(userDetails[id]).to.be.undefined; + } else { + const user = userDetails[id]; + expect(user).to.be.an('object'); + expect(user.forename).to.be.a('string'); + expect(user.surname).to.be.a('string'); + } + expect(pipes[index]).to.be.an('array') + .and.to.have.lengthOf(2) + .and.to.contain('get') + .and.to.contain(keys.user(id)); + }); + }); + it('should handle a null userId', async () => { + const USER_IDS = ['a', 'b', null]; + const userDetails = await activityService.getUserDetails(USER_IDS); + expect(MOCK_REDIS.pipelines).to.have.lengthOf(1); + const pipes = MOCK_REDIS.pipelines[0]; + // Should not have tried to retrieve the null user at all. + expect(pipes).to.be.an('array').and.have.lengthOf(USER_IDS.length - 1); + let userIndex = 0; + USER_IDS.forEach((id) => { + if (id) { + const user = userDetails[id]; + expect(user).to.be.an('object'); + expect(user.forename).to.be.a('string'); + expect(user.surname).to.be.a('string'); + + expect(pipes[userIndex]).to.be.an('array') + .and.to.have.lengthOf(2) + .and.to.contain('get') + .and.to.contain(keys.user(id)); + userIndex++; + } + }); + }); + }); + + describe('removeSocketActivity', () => { + beforeEach(() => { + MOCK_REDIS.pipelineMode = 'socket'; + }); + + it('should appropriately remove socket activity', async () => { + const NOW = Date.now(); + const SOCKET_ID = 'abcdef123456'; + await activityService.removeSocketActivity(SOCKET_ID); + expect(MOCK_REDIS.pipelines).to.have.lengthOf(1); + const pipes = MOCK_REDIS.pipelines[0]; + expect(pipes).to.be.an('array').with.a.lengthOf(2); + // First one should be to remove the user activity. + expect(pipes[0]).to.be.an('array').with.a.lengthOf(3) + .and.to.contain('zrem') + .and.to.contain(keys.case.view(CASE_ID)) + .and.to.contain(USER_ID); + // Second one should be to remove the socket entry. + expect(pipes[1]).to.be.an('array').with.a.lengthOf(2) + .and.to.contain('del') + .and.to.contain(keys.socket(SOCKET_ID)); + + // Should have also notified about the change. + expect(MOCK_REDIS.messages).to.have.lengthOf(1); + expect(MOCK_REDIS.messages[0].channel).to.equal(keys.case.base(CASE_ID)); + const messageTS = parseInt(MOCK_REDIS.messages[0].message, 10); + expect(messageTS).to.be.approximately(NOW, 5); // Within 5ms. + }); + it('should handle a null socketId', async () => { + await activityService.removeSocketActivity(null); + expect(MOCK_REDIS.pipelines).to.have.lengthOf(0); // Should have been no calls to redis. + }); + }); + + describe('addActivity', () => { + const DATE_NOW = 55; + + beforeEach(() => { + MOCK_REDIS.pipelineMode = 'add'; + sandbox.stub(Date, 'now').returns(DATE_NOW); + }); + + afterEach(() => { + // completely restore all fakes created through the sandbox + sandbox.restore(); + }); + + it('should appropriately add view activity', async () => { + const NOW = Date.now(); + const USER = { uid: USER_ID, given_name: 'Joe', family_name: 'Bloggs' }; + const SOCKET_ID = 'abcdef123456'; + await activityService.addActivity(CASE_ID, USER, SOCKET_ID, 'view'); + expect(MOCK_REDIS.pipelines).to.have.lengthOf(2); + const removePipes = MOCK_REDIS.pipelines[0]; + expect(removePipes).to.be.an('array').with.a.lengthOf(2); // Remove + + const pipes = MOCK_REDIS.pipelines[1]; + // First one should be to add the user activity. + expect(pipes[0]).to.be.an('array').with.a.lengthOf(4) + .and.to.contain('zadd') + .and.to.contain(keys.case.view(CASE_ID)) + .and.to.contain(DATE_NOW + TTL_ACTIVITY * 1000) // TTL + NOW + .and.to.contain(USER_ID); + // Second one should be to add the socket entry. + expect(pipes[1]).to.be.an('array').with.a.lengthOf(5) + .and.to.contain('set') + .and.to.contain(keys.socket(SOCKET_ID)) + .and.to.contain(`{"activityKey":"${keys.case.view(CASE_ID)}","caseId":"${CASE_ID}","userId":"${USER_ID}"}`) + .and.to.contain('EX') + .and.to.contain(TTL_USER); + // Third one should be to set the user details. + expect(pipes[2]).to.be.an('array').with.a.lengthOf(5) + .and.to.contain('set') + .and.to.contain(keys.user(USER_ID)) + .and.to.contain(`{"id":"${USER_ID}","forename":"Joe","surname":"Bloggs"}`) + .and.to.contain('EX') + .and.to.contain(TTL_USER); + + // Should have also notified about the change. + expect(MOCK_REDIS.messages).to.have.lengthOf(1); + expect(MOCK_REDIS.messages[0].channel).to.equal(keys.case.base(CASE_ID)); + const messageTS = parseInt(MOCK_REDIS.messages[0].message, 10); + expect(messageTS).to.be.approximately(NOW, 5); // Within 5ms. + }); + it('should notifications about both removed and added cases', async () => { + const NOW = Date.now(); + const USER = { uid: USER_ID, given_name: 'Joe', family_name: 'Bloggs' }; + const SOCKET_ID = 'abcdef123456'; + const NEW_CASE_ID = '0987654321'; + await activityService.addActivity(NEW_CASE_ID, USER, SOCKET_ID, 'view'); + + // Should have been two notifictions... + expect(MOCK_REDIS.messages).to.have.lengthOf(2); + // ... firstly about the original case. + expect(MOCK_REDIS.messages[0].channel).to.equal(keys.case.base(CASE_ID)); + // ... and then about the new case. + expect(MOCK_REDIS.messages[1].channel).to.equal(keys.case.base(NEW_CASE_ID)); + }); + it('should handle a null caseId', async () => { + const USER = { uid: USER_ID }; + const SOCKET_ID = 'abcdef123456'; + await activityService.addActivity(null, USER, SOCKET_ID, 'view'); + expect(MOCK_REDIS.pipelines).to.have.lengthOf(0); // Should have been no calls to redis. + }); + it('should handle a null user', async () => { + const SOCKET_ID = 'abcdef123456'; + await activityService.addActivity(CASE_ID, null, SOCKET_ID, 'view'); + expect(MOCK_REDIS.pipelines).to.have.lengthOf(0); // Should have been no calls to redis. + }); + it('should handle a null socketId', async () => { + const USER = { uid: USER_ID }; + await activityService.addActivity(CASE_ID, USER, null, 'view'); + expect(MOCK_REDIS.pipelines).to.have.lengthOf(0); // Should have been no calls to redis. + }); + it('should handle a null activity', async () => { + const USER = { uid: USER_ID }; + const SOCKET_ID = 'abcdef123456'; + await activityService.addActivity(CASE_ID, USER, SOCKET_ID, null); + expect(MOCK_REDIS.pipelines).to.have.lengthOf(0); // Should have been no calls to redis. + }); + }); + + describe('getActivityForCases', () => { + const DATE_NOW = 55; + + beforeEach(() => { + MOCK_REDIS.pipelineMode = 'get'; + sandbox.stub(Date, 'now').returns(DATE_NOW); + }); + + afterEach(() => { + // completely restore all fakes created through the sandbox + sandbox.restore(); + }); + + it('should appropriately get case activity', async () => { + const CASE_IDS = ['1234567890','0987654321']; + const result = await activityService.getActivityForCases(CASE_IDS); + expect(result).to.be.an('array').with.a.lengthOf(CASE_IDS.length); + CASE_IDS.forEach((id, index) => { + expect(result[index]).to.be.an('object'); + expect(result[index].caseId).to.equal(id); + expect(result[index].viewers).to.be.an('array').with.a.lengthOf(1); + expect(result[index].viewers[0]).to.be.an('object'); + expect(result[index].viewers[0].forename).to.equal(`Bob ${USER_ID.toUpperCase()}`); + expect(result[index].unknownViewers).to.equal(1); // 'MISSING' id. + expect(result[index].editors).to.be.an('array').with.a.lengthOf(1); + expect(result[index].editors[0]).to.be.an('object'); + expect(result[index].unknownEditors).to.equal(1); // 'MISSING' id. + expect(result[index].editors[0].forename).to.equal(`Bob ${USER_ID.toUpperCase()}`); + }); + }); + it('should handle null caseIds', async () => { + const result = await activityService.getActivityForCases(null); + expect(result).to.be.an('array').with.a.lengthOf(0); + expect(MOCK_REDIS.pipelines).to.have.lengthOf(0); // Should have been no calls to redis. + }); + it('should handle empty caseIds', async () => { + const result = await activityService.getActivityForCases([]); + expect(result).to.be.an('array').with.a.lengthOf(0); + expect(MOCK_REDIS.pipelines).to.have.lengthOf(0); // Should have been no calls to redis. + }); + }); + +}); diff --git a/test/spec/app/socket/utils/watch.spec.js b/test/spec/app/socket/utils/watch.spec.js index a2d1650a..94651344 100644 --- a/test/spec/app/socket/utils/watch.spec.js +++ b/test/spec/app/socket/utils/watch.spec.js @@ -5,7 +5,6 @@ const expect = require('chai').expect; describe('socket.utils', () => { describe('watch', () => { - const MOCK_SOCKET = { id: 'socket-id', rooms: ['socket-id'], From 2762b6732e2aabf6382a3df679f33453a336fc81 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Fri, 28 May 2021 09:36:39 +0100 Subject: [PATCH 027/113] moment Moment isn't needed so ditching it. --- package.json | 1 - test/spec/app/service/activity-service.spec.js | 1 - 2 files changed, 2 deletions(-) diff --git a/package.json b/package.json index 97ba0c01..18a449cd 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "ioredis": "^3.1.4", "joi": "^17.2.1", "jwt-decode": "^2.2.0", - "moment": "^2.19.3", "morgan": "^1.9.1", "nocache": "^2.1.0", "node-cache": "^5.1.0", diff --git a/test/spec/app/service/activity-service.spec.js b/test/spec/app/service/activity-service.spec.js index ef73e385..d431fd57 100644 --- a/test/spec/app/service/activity-service.spec.js +++ b/test/spec/app/service/activity-service.spec.js @@ -54,7 +54,6 @@ describe("activity service", () => { it("getActivities should create a redis pipeline with the correct redis commands for getViewers", (done) => { sandbox.stub(Date, 'now').returns(TIMESTAMP); - // sandbox.stub(moment, 'now').returns(TIMESTAMP); sandbox.stub(config, 'get').returns(USER_DETAILS_TTL); sandbox.stub(redis, "pipeline").callsFake(function (arguments) { argStr = JSON.stringify(arguments); From 3288a680d298e4c75fb3ee55ec8a023a7c8eee66 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Fri, 28 May 2021 09:41:12 +0100 Subject: [PATCH 028/113] Fixed underscore CVE issue Resolving underscore to `^1.12.1` to address a security vulnerability. --- package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 18a449cd..1877586b 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,6 @@ "minimist": "^1.2.3", "y18n": "^4.0.1", "hosted-git-info": "^3.0.8", - "underscore": "^1.13.1" + "underscore": "^1.12.1" } } diff --git a/yarn.lock b/yarn.lock index a0bbe630..2026dc8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3457,7 +3457,7 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -underscore@^1.13.1, underscore@~1.9.1: +underscore@^1.12.1, underscore@~1.9.1: version "1.13.1" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1" integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g== From a4000c96f417b549a539134e4a513c723641349e Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Fri, 28 May 2021 12:18:48 +0100 Subject: [PATCH 029/113] Refactor for testing Moved a bunch of functions out of `server.js` and into `app/util/utils.js` and added unit tests for them all. --- app.js | 2 +- app/util/utils.js | 51 ++++++ server.js | 89 ++--------- test/spec/app/health/health-check.spec.js | 6 +- test/spec/app/util/utils.spec.js | 186 ++++++++++++++++++++++ 5 files changed, 253 insertions(+), 81 deletions(-) create mode 100644 test/spec/app/util/utils.spec.js diff --git a/app.js b/app.js index 12be98ba..26a0d565 100644 --- a/app.js +++ b/app.js @@ -72,4 +72,4 @@ app.use((err, req, res, next) => { }); }); -module.exports = { app, redis }; +module.exports = app; diff --git a/app/util/utils.js b/app/util/utils.js index 28ba42fe..3ef5572e 100644 --- a/app/util/utils.js +++ b/app/util/utils.js @@ -7,3 +7,54 @@ exports.ifNotTimedOut = (request, f) => { debug('request timed out'); } }; + +exports.normalizePort = (val) => { + const port = parseInt(val, 10); + if (Number.isNaN(port)) { + // named pipe + return val; + } + if (port >= 0) { + // port number + return port; + } + return false; +}; + +/** + * Event listener for HTTP server "error" event. + */ +exports.onServerError = (port, logTo, exitRoute) => { + return (error) => { + if (error.syscall !== 'listen') { + throw error; + } + + const bind = typeof port === 'string' ? `Pipe ${port}` : `Port ${port}`; + + // Handle specific listen errors with friendly messages. + switch (error.code) { + case 'EACCES': + logTo(`${bind} requires elevated privileges`); + exitRoute(1); + break; + case 'EADDRINUSE': + logTo(`${bind} is already in use`); + exitRoute(1); + break; + default: + throw error; + } + }; +}; + +/** + * Event listener for HTTP server "listening" event. + */ +exports.onListening = (server, logTo) => { + return () => { + const addr = server.address(); + const bind = typeof addr === 'string' ? `pipe ${addr}` : `port ${addr.port}`; + logTo(`Listening on ${bind}`); + }; +}; diff --git a/server.js b/server.js index 88df8c5c..b83cf80b 100755 --- a/server.js +++ b/server.js @@ -3,98 +3,33 @@ /** * Module dependencies. */ - require('@hmcts/properties-volume').addTo(require('config')); -var app = require('./app'); - -var debug = require('debug')('ccd-case-activity-api:server'); -var http = require('http'); +const { normalizePort, onListening, onServerError } = require('./app/util/utils'); +const debug = require('debug')('ccd-case-activity-api:server'); +const http = require('http'); +const app = require('./app'); /** * Get port from environment and store in Express. */ -var port = normalizePort(process.env.PORT || '3460'); -console.log('Starting on port ' + port); -app.app.set('port', port); +const port = normalizePort(process.env.PORT || '3460'); +console.log(`Starting on port ${port}`); +app.set('port', port); /** * Create HTTP server. */ - -var server = http.createServer(app.app); +const server = http.createServer(app); /** * Create the socket server. */ -require('./app/socket')(server, app.redis); +const redis = require('./app/redis/redis-client'); +require('./app/socket')(server, redis); /** * Listen on provided port, on all network interfaces. */ - server.listen(port); -server.on('error', onError); -server.on('listening', onListening); - -/** - * Normalize a port into a number, string, or false. - */ - -function normalizePort(val) { - var port = parseInt(val, 10); - - if (isNaN(port)) { - // named pipe - return val; - } - - if (port >= 0) { - // port number - return port; - } - - return false; -} - -/** - * Event listener for HTTP server "error" event. - */ - -function onError(error) { - if (error.syscall !== 'listen') { - throw error; - } - - var bind = typeof port === 'string' - ? 'Pipe ' + port - : 'Port ' + port; - - // handle specific listen errors with friendly messages - switch (error.code) { - case 'EACCES': - console.error(bind + ' requires elevated privileges'); - process.exit(1); - break; - case 'EADDRINUSE': - console.error(bind + ' is already in use'); - process.exit(1); - break; - default: - throw error; - } -} - -/** - * Event listener for HTTP server "listening" event. - */ - -function onListening() { - - var addr = server.address(); - - var bind = typeof addr === 'string' - ? 'pipe ' + addr - : 'port ' + addr.port; - - debug('Listening on ' + bind); -} +server.on('error', onServerError(port, console.error, process.exit)); +server.on('listening', onListening(server, debug)); diff --git a/test/spec/app/health/health-check.spec.js b/test/spec/app/health/health-check.spec.js index e85f7054..9efb6c9c 100644 --- a/test/spec/app/health/health-check.spec.js +++ b/test/spec/app/health/health-check.spec.js @@ -5,7 +5,7 @@ const app = require('../../../../app'); describe('health check', () => { it('should return 200 OK for health check', async () => { - await request(app.app) + await request(app) .get('/health') .expect(res => { expect(res.status).equal(200); @@ -14,7 +14,7 @@ describe('health check', () => { }); it('should return 200 OK for liveness health check', async () => { - await request(app.app) + await request(app) .get('/health/liveness') .expect(res => { expect(res.status).equal(200); @@ -23,7 +23,7 @@ describe('health check', () => { }); it('should return 200 OK for readiness health check', async () => { - await request(app.app) + await request(app) .get('/health/readiness') .expect(res => { expect(res.status).equal(200); diff --git a/test/spec/app/util/utils.spec.js b/test/spec/app/util/utils.spec.js new file mode 100644 index 00000000..29bd184f --- /dev/null +++ b/test/spec/app/util/utils.spec.js @@ -0,0 +1,186 @@ +const expect = require('chai').expect; +const utils = require('../../../../app/util/utils'); + +describe('util.utils', () => { + + describe('ifNotTimedOut', () => { + it('should call the function if it is not timed out', () => { + const REQUEST = { timedout: false }; + let functionCalled = false; + utils.ifNotTimedOut(REQUEST, () => { + functionCalled = true; + }); + expect(functionCalled).to.be.true; + }); + it('should not the function if it is timed out', () => { + const REQUEST = { timedout: true }; + let functionCalled = false; + utils.ifNotTimedOut(REQUEST, () => { + functionCalled = true; + }); + expect(functionCalled).to.be.false; + }); + }); + + describe('normalizePort', () => { + it('should parse and use a numeric string', () => { + const PORT = '1234'; + const response = utils.normalizePort(PORT); + expect(response).to.be.a('number').and.to.equal(1234); + }); + it('should parse and use a zero string', () => { + const PORT = '0'; + const response = utils.normalizePort(PORT); + expect(response).to.be.a('number').and.to.equal(0); + }); + it('should bounce a null', () => { + const PORT = null; + const response = utils.normalizePort(PORT); + expect(response).to.equal(PORT); + }); + it('should bounce an object', () => { + const PORT = { bob: 'Bob' }; + const response = utils.normalizePort(PORT); + expect(response).to.equal(PORT); + }); + it('should bounce a string that cannot be parsed as a number', () => { + const PORT = 'Bob'; + const response = utils.normalizePort(PORT); + expect(response).to.equal(PORT); + }); + it('should reject an invalid numeric string', () => { + const PORT = '-1234'; + const response = utils.normalizePort(PORT); + expect(response).to.be.false; + }); + }); + + describe('onServerError', () => { + const getSystemError = (code, syscall, message) => { + return { + address: 'http://test.address.net', + code: code, + errno: 1, + message: message || 'An error occurred', + syscall: syscall + }; + }; + let logTo; + let exitRoute; + beforeEach(() => { + logTo = { + logs: [], + output: (str) => { + logTo.logs.push(str); + } + }; + exitRoute = { + calls: [], + exit: (code) => { + exitRoute.calls.push(code); + } + } + }); + + it('should handle an access error on a numeric port', () => { + const PORT = 1234; + const ERROR = getSystemError('EACCES', 'listen'); + utils.onServerError(PORT, logTo.output, exitRoute.exit)(ERROR); + expect(logTo.logs).to.have.a.lengthOf(1) + .and.to.contain('Port 1234 requires elevated privileges'); + expect(exitRoute.calls).to.have.a.lengthOf(1) + .and.to.contain(1); + }); + it('should handle an access error on a string port', () => { + const PORT = 'BOBBINS'; + const ERROR = getSystemError('EACCES', 'listen'); + utils.onServerError(PORT, logTo.output, exitRoute.exit)(ERROR); + expect(logTo.logs).to.have.a.lengthOf(1) + .and.to.contain('Pipe BOBBINS requires elevated privileges'); + expect(exitRoute.calls).to.have.a.lengthOf(1) + .and.to.contain(1); + }); + it('should handle an address in use error on a numeric port', () => { + const PORT = 1234; + const ERROR = getSystemError('EADDRINUSE', 'listen'); + utils.onServerError(PORT, logTo.output, exitRoute.exit)(ERROR); + expect(logTo.logs).to.have.a.lengthOf(1) + .and.to.contain('Port 1234 is already in use'); + expect(exitRoute.calls).to.have.a.lengthOf(1) + .and.to.contain(1); + }); + it('should handle an address in use error on a string port', () => { + const PORT = 'BOBBINS'; + const ERROR = getSystemError('EADDRINUSE', 'listen'); + utils.onServerError(PORT, logTo.output, exitRoute.exit)(ERROR); + expect(logTo.logs).to.have.a.lengthOf(1) + .and.to.contain('Pipe BOBBINS is already in use'); + expect(exitRoute.calls).to.have.a.lengthOf(1) + .and.to.contain(1); + }); + it('should throw an error when not a listen syscall', () => { + const PORT = 1234; + const ERROR = getSystemError('EADDRINUSE', 'not listening', `Sorry, what was that? I wasn't listening.`); + const onServerError = utils.onServerError(PORT, logTo.output, exitRoute.exit); + let errorThrown = null; + try { + onServerError(ERROR); + } catch (err) { + errorThrown = err; + } + expect(errorThrown).to.equal(ERROR); + expect(logTo.logs).to.have.a.lengthOf(0); + expect(exitRoute.calls).to.have.a.lengthOf(0); + }); + it('should rethrow an unhandled error', () => { + const PORT = 1234; + const ERROR = getSystemError('PANIC_STATIONS', 'listen'); + const onServerError = utils.onServerError(PORT, logTo.output, exitRoute.exit); + let errorThrown = null; + try { + onServerError(ERROR); + } catch (err) { + errorThrown = err; + } + expect(errorThrown).to.equal(ERROR); + expect(logTo.logs).to.have.a.lengthOf(0); + expect(exitRoute.calls).to.have.a.lengthOf(0); + }); + + }); + + describe('onListening', () => { + let logTo; + beforeEach(() => { + logTo = { + logs: [], + output: (str) => { + logTo.logs.push(str); + } + }; + }); + it('should handle a string address', () => { + const ADDRESS = 'http://test.address'; + const SERVER = { + address: () => { + return ADDRESS; + } + }; + utils.onListening(SERVER, logTo.output)(); + expect(logTo.logs).to.have.a.lengthOf(1) + .and.to.contain(`Listening on pipe ${ADDRESS}`); + }); + it('should handle an address with a port', () => { + const PORT = 6251; + const SERVER = { + address: () => { + return { port: PORT }; + } + }; + utils.onListening(SERVER, logTo.output)(); + expect(logTo.logs).to.have.a.lengthOf(1) + .and.to.contain(`Listening on port ${PORT}`); + }); + }); + +}); \ No newline at end of file From 23abf9dde93cc51697e8ac1270e6f8db929031b5 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Fri, 28 May 2021 12:32:28 +0100 Subject: [PATCH 030/113] Create ttl-score-generator.spec.js Added some unit tests for `service/ttl-score-generator`. --- .../app/service/ttl-score-generator.spec.js | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 test/spec/app/service/ttl-score-generator.spec.js diff --git a/test/spec/app/service/ttl-score-generator.spec.js b/test/spec/app/service/ttl-score-generator.spec.js new file mode 100644 index 00000000..5fbff506 --- /dev/null +++ b/test/spec/app/service/ttl-score-generator.spec.js @@ -0,0 +1,40 @@ + +const expect = require('chai').expect; +const config = require('config'); +const sandbox = require("sinon").createSandbox(); +const ttlScoreGenerator = require('../../../../app/service/ttl-score-generator'); + +describe('service.ttl-score-generator', () => { + + afterEach(() => { + sandbox.restore(); + }); + + describe('getScore', () => { + it('should handle an activity TTL', () => { + const TTL = '12'; + const NOW = 55; + sandbox.stub(Date, 'now').returns(NOW); + sandbox.stub(config, 'get').returns(TTL); + const score = ttlScoreGenerator.getScore(); + expect(score).to.equal(12055); // (TTL * 1000) + NOW + }); + it('should handle a numeric TTL', () => { + const TTL = 13; + const NOW = 55; + sandbox.stub(Date, 'now').returns(NOW); + sandbox.stub(config, 'get').returns(TTL); + const score = ttlScoreGenerator.getScore(); + expect(score).to.equal(13055); // (TTL * 1000) + NOW + }); + it('should handle a null TTL', () => { + const TTL = null; + const NOW = 55; + sandbox.stub(Date, 'now').returns(NOW); + sandbox.stub(config, 'get').returns(TTL); + const score = ttlScoreGenerator.getScore(); + expect(score).to.equal(55); // null TTL => 0 + }); + }); + +}); From 9f5a85f00d8d049b7c90b62b4fbdd71900568d0a Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Fri, 28 May 2021 12:56:11 +0100 Subject: [PATCH 031/113] Update store-cleanup-job.js Adjusted the store cleanup so it now also clears out cases that have arrived via the socket interface, which has a different prefix - `c:`. --- app/job/store-cleanup-job.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/job/store-cleanup-job.js b/app/job/store-cleanup-job.js index 0da3179e..88198325 100644 --- a/app/job/store-cleanup-job.js +++ b/app/job/store-cleanup-job.js @@ -6,10 +6,10 @@ const redis = require('../redis/redis-client'); const { logPipelineFailures } = redis; const REDIS_ACTIVITY_KEY_PREFIX = config.get('redis.keyPrefix'); -const scanExistingCasesKeys = (f) => { +const scanExistingCasesKeys = (f, prefix) => { const stream = redis.scanStream({ // only returns keys following the pattern - match: `${REDIS_ACTIVITY_KEY_PREFIX}case:*`, + match: `${REDIS_ACTIVITY_KEY_PREFIX}${prefix}:*`, // returns approximately 100 elements per call count: 100, }); @@ -26,7 +26,7 @@ const scanExistingCasesKeys = (f) => { }); }; -const getCasesWithActivities = (f) => scanExistingCasesKeys(f); +const getCasesWithActivities = (f, prefix) => scanExistingCasesKeys(f, prefix); const cleanupActivitiesCommand = (key) => ['zremrangebyscore', key, '-inf', Date.now()]; @@ -38,6 +38,11 @@ const pipeline = (cases) => { const storeCleanup = () => { debug('store cleanup starting...'); + cleanCasesWithPrefix('case'); // Cases via RESTful interface. + cleanCasesWithPrefix('c'); // Cases via socket interface. +}; + +const cleanCasesWithPrefix = (prefix) => { getCasesWithActivities((cases) => { // scan returns the prefixed keys. Remove them since the redis client will add it back const casesWithoutPrefix = cases.map((k) => k.replace(REDIS_ACTIVITY_KEY_PREFIX, '')); @@ -48,7 +53,7 @@ const storeCleanup = () => { .catch((err) => { debug('Error in getCasesWithActivities', err.message); }); - }); + }, prefix); }; exports.start = (crontab) => { From 753d42fd2b72e2585aa1613ea44aec6691682a7d Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Fri, 28 May 2021 13:11:48 +0100 Subject: [PATCH 032/113] Update store-cleanup-job.js Lint error. --- app/job/store-cleanup-job.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/job/store-cleanup-job.js b/app/job/store-cleanup-job.js index 88198325..279a2bb8 100644 --- a/app/job/store-cleanup-job.js +++ b/app/job/store-cleanup-job.js @@ -36,12 +36,6 @@ const pipeline = (cases) => { return redis.pipeline(commands); }; -const storeCleanup = () => { - debug('store cleanup starting...'); - cleanCasesWithPrefix('case'); // Cases via RESTful interface. - cleanCasesWithPrefix('c'); // Cases via socket interface. -}; - const cleanCasesWithPrefix = (prefix) => { getCasesWithActivities((cases) => { // scan returns the prefixed keys. Remove them since the redis client will add it back @@ -56,6 +50,12 @@ const cleanCasesWithPrefix = (prefix) => { }, prefix); }; +const storeCleanup = () => { + debug('store cleanup starting...'); + cleanCasesWithPrefix('case'); // Cases via RESTful interface. + cleanCasesWithPrefix('c'); // Cases via socket interface. +}; + exports.start = (crontab) => { const isValid = cron.validate(crontab); if (!isValid) throw new Error(`invalid crontab: ${crontab}`); From 52d039566bcca4848b05777349522dc67e66af56 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Fri, 28 May 2021 16:00:28 +0100 Subject: [PATCH 033/113] Update server.js Added a description for how the socket server behaves in parallel. --- server.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/server.js b/server.js index b83cf80b..9e9e676e 100755 --- a/server.js +++ b/server.js @@ -23,6 +23,16 @@ const server = http.createServer(app); /** * Create the socket server. + * + * This runs on the same server, in parallel to the RESTful interface. At the present + * time, interoperability is turned off to keep them isolated but, with a couple of + * tweaks, it can easily be enabled: + * + * * Adjust the prefixes in socket/redis/keys.js to be the same as the RESTful ones. + * * This will immediately allow the RESTful interface to see what people on sockets + * are viewing/editing. + * * Add redis.publish(...) calls in service/activity-service.js. + * * To notify those on sockets when someone is viewing or editing a case. */ const redis = require('./app/redis/redis-client'); require('./app/socket')(server, redis); From af4a7d8f6dcd12936b7c7698f9350dc2c6e76a54 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Wed, 9 Jun 2021 13:06:03 +0100 Subject: [PATCH 034/113] CVE issue CVE fix for `ws`, which is part of `engine.io`. --- package.json | 3 ++- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1877586b..ee064e61 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "minimist": "^1.2.3", "y18n": "^4.0.1", "hosted-git-info": "^3.0.8", - "underscore": "^1.12.1" + "underscore": "^1.12.1", + "ws": "^7.4.6" } } diff --git a/yarn.lock b/yarn.lock index 2026dc8f..d242cd8c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3602,10 +3602,10 @@ write@1.0.3: dependencies: mkdirp "^0.5.1" -ws@~7.4.2: - version "7.4.5" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.5.tgz#a484dd851e9beb6fdb420027e3885e8ce48986c1" - integrity sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g== +ws@^7.4.6, ws@~7.4.2: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== y18n@^4.0.0, y18n@^4.0.1: version "4.0.3" From a8a177efa7ae8102eee4e81f4b7962244bb51f9c Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Thu, 10 Jun 2021 09:50:21 +0100 Subject: [PATCH 035/113] Socket TTLs Adjusted the TTLs on the socket as they should last a lot longer than the ones made via RESTful calls. --- app/socket/service/activity-service.js | 4 ++-- config/default.yaml | 3 +++ test/spec/app/socket/service/activity-service.spec.js | 8 ++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/socket/service/activity-service.js b/app/socket/service/activity-service.js index fe64578f..d19a6dfc 100644 --- a/app/socket/service/activity-service.js +++ b/app/socket/service/activity-service.js @@ -3,8 +3,8 @@ const utils = require('../utils'); module.exports = (config, redis) => { const ttl = { - user: config.get('redis.userDetailsTtlSec'), - activity: config.get('redis.activityTtlSec') + user: config.get('redis.socket.userDetailsTtlSec'), + activity: config.get('redis.socket.activityTtlSec') }; const notifyChange = (caseId) => { diff --git a/config/default.yaml b/config/default.yaml index cf3b21f2..30f811b1 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -12,6 +12,9 @@ redis: keyPrefix: "activity:" activityTtlSec: 5 userDetailsTtlSec: 2 + socket: + activityTtlSec: 600 + userDetailsTtlSec: 3600 cache: user_info_enabled: true user_info_ttl: 600 diff --git a/test/spec/app/socket/service/activity-service.spec.js b/test/spec/app/socket/service/activity-service.spec.js index 46939bbf..8e8ef18e 100644 --- a/test/spec/app/socket/service/activity-service.spec.js +++ b/test/spec/app/socket/service/activity-service.spec.js @@ -14,8 +14,8 @@ describe('socket.service.activity-service', () => { const MOCK_CONFIG = { getCalls: [], keys: { - 'redis.activityTtlSec': TTL_ACTIVITY, - 'redis.userDetailsTtlSec': TTL_USER + 'redis.socket.activityTtlSec': TTL_ACTIVITY, + 'redis.socket.userDetailsTtlSec': TTL_USER }, get: (key) => { MOCK_CONFIG.getCalls.push(key); @@ -105,9 +105,9 @@ describe('socket.service.activity-service', () => { }); it('should have appropriately initialised from the config', () => { - expect(MOCK_CONFIG.getCalls).to.include('redis.activityTtlSec'); + expect(MOCK_CONFIG.getCalls).to.include('redis.socket.activityTtlSec'); expect(activityService.ttl.activity).to.equal(TTL_ACTIVITY); - expect(MOCK_CONFIG.getCalls).to.include('redis.userDetailsTtlSec'); + expect(MOCK_CONFIG.getCalls).to.include('redis.socket.userDetailsTtlSec'); expect(activityService.ttl.user).to.equal(TTL_USER); }); From 5558216272f8d9d6e8eea1060835b1a9135f8582 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Thu, 10 Jun 2021 11:41:20 +0100 Subject: [PATCH 036/113] console.logs Temporarily log new and closed connections to the console as nothing is coming through in the logs when going via debug. --- app/socket/router/index.js | 7 +++++-- app/socket/utils/other.js | 15 +++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/socket/router/index.js b/app/socket/router/index.js index 3a97e346..a452838c 100644 --- a/app/socket/router/index.js +++ b/app/socket/router/index.js @@ -25,6 +25,7 @@ const router = { return [...connections]; }, init: (io, iorouter, handlers) => { + const start = Date.now(); // Set up routes for each type of message. iorouter.on('register', (socket, ctx, next) => { utils.log(socket, ctx.request.user, 'register'); @@ -53,13 +54,15 @@ const router = { // On client connection, attach the router and track the socket. io.on('connection', (socket) => { router.addConnection(socket); - utils.log(socket, '', `connected (${router.getConnections().length} total)`); + const ts = (Date.now() - start).toString(10).padStart(10, '0'); + utils.log(socket, '', `connected (${router.getConnections().length} total)`, console.log, ts); socket.use((packet, next) => { iorouter.attach(socket, packet, next); }); // When the socket disconnects, do an appropriate teardown. socket.on('disconnect', () => { - utils.log(socket, '', `disconnected (${router.getConnections().length - 1} total)`); + const ts = (Date.now() - start).toString(10).padStart(10, '0'); + utils.log(socket, '', `disconnected (${router.getConnections().length - 1} total)`, console.log, ts); handlers.removeSocketActivity(socket.id); router.removeUser(socket.id); router.removeConnection(socket); diff --git a/app/socket/utils/other.js b/app/socket/utils/other.js index 9524b777..82ca055f 100644 --- a/app/socket/utils/other.js +++ b/app/socket/utils/other.js @@ -17,9 +17,10 @@ const other = { } return userIds; }, - log: (socket, payload, group, logTo) => { + log: (socket, payload, group, logTo, ts) => { const outputTo = logTo || debug; - let text = `${new Date().toISOString()} | ${socket.id} | ${group}`; + const now = ts || new Date().toISOString(); + let text = `${now} | ${socket.id} | ${group}`; if (typeof payload === 'string') { if (payload) { text = `${text} => ${payload}`; @@ -43,8 +44,10 @@ const other = { if (!obj) { return {}; } - const nameParts = obj.name.split(' '); - const givenName = nameParts.shift(); + const name = obj.name || `${obj.forename} ${obj.surname}`; + const nameParts = name.split(' '); + const givenName = obj.forename || nameParts.shift(); + const familyName = obj.surname || nameParts.join(' '); return { sub: `${givenName}.${nameParts.join('-')}@mailinator.com`, uid: obj.id, @@ -53,9 +56,9 @@ const other = { 'caseworker-employment-leeds', 'caseworker' ], - name: obj.name, + name: name, given_name: givenName, - family_name: nameParts.join(' ') + family_name: familyName }; }, toUserString: (user) => { From 8b5881f50df06eb91ff78da199053a5af9c43bdf Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Mon, 14 Jun 2021 11:22:20 +0100 Subject: [PATCH 037/113] Update custom-environment-variables.yaml Socket TTLs in custom environment variables. --- config/custom-environment-variables.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/custom-environment-variables.yaml b/config/custom-environment-variables.yaml index 631efb5a..c5a44250 100644 --- a/config/custom-environment-variables.yaml +++ b/config/custom-environment-variables.yaml @@ -13,6 +13,9 @@ redis: keyPrefix: REDIS_KEY_PREFIX activityTtlSec: REDIS_ACTIVITY_TTL userDetailsTtlSec: REDIS_USER_DETAILS_TTL + socket: + activityTtlSec: REDIS_SOCKET_ACTIVITY_TTL + userDetailsTtlSec: REDIS_SOCKET_USER_DETAILS_TTL cache: user_info_enabled: CACHE_USER_INFO_ENABLED user_info_ttl: CACHE_USER_INFO_TTL From d37a5ee7ecf81933efea4b0368fc0b4b3ce21e7d Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Tue, 15 Jun 2021 10:49:33 +0100 Subject: [PATCH 038/113] No need for 'register' event Removed the 'register' event as the user details are now being passed as part of the initial socket setup. --- app/socket/index.js | 5 ++-- app/socket/router/index.js | 15 ++++-------- test/spec/app/socket/router/index.spec.js | 30 +++++++++-------------- 3 files changed, 19 insertions(+), 31 deletions(-) diff --git a/app/socket/index.js b/app/socket/index.js index 34300519..c62eb394 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -24,8 +24,9 @@ module.exports = (server, redis) => { allowEIO3: true, cors: { origin: '*', - methods: ['GET', 'POST'] - } + methods: ['GET', 'POST'], + credentials: true + }, }); const handlers = Handlers(activityService, socketServer); const watcher = redis.duplicate(); diff --git a/app/socket/router/index.js b/app/socket/router/index.js index a452838c..7b3f9e06 100644 --- a/app/socket/router/index.js +++ b/app/socket/router/index.js @@ -25,13 +25,7 @@ const router = { return [...connections]; }, init: (io, iorouter, handlers) => { - const start = Date.now(); // Set up routes for each type of message. - iorouter.on('register', (socket, ctx, next) => { - utils.log(socket, ctx.request.user, 'register'); - router.addUser(socket.id, ctx.request.user); - next(); - }); iorouter.on('view', (socket, ctx, next) => { const user = router.getUser(socket.id); utils.log(socket, `${ctx.request.caseId} (${user.name})`, 'view'); @@ -54,15 +48,16 @@ const router = { // On client connection, attach the router and track the socket. io.on('connection', (socket) => { router.addConnection(socket); - const ts = (Date.now() - start).toString(10).padStart(10, '0'); - utils.log(socket, '', `connected (${router.getConnections().length} total)`, console.log, ts); + router.addUser(socket.id, socket.handshake.query.user); + utils.log(socket, '', `connected (${router.getConnections().length} total)`); + utils.log(socket, '', `connected (${router.getConnections().length} total)`, console.log, Date.now()); socket.use((packet, next) => { iorouter.attach(socket, packet, next); }); // When the socket disconnects, do an appropriate teardown. socket.on('disconnect', () => { - const ts = (Date.now() - start).toString(10).padStart(10, '0'); - utils.log(socket, '', `disconnected (${router.getConnections().length - 1} total)`, console.log, ts); + utils.log(socket, '', `disconnected (${router.getConnections().length - 1} total)`); + utils.log(socket, '', `disconnected (${router.getConnections().length - 1} total)`, console.log, Date.now()); handlers.removeSocketActivity(socket.id); router.removeUser(socket.id); router.removeConnection(socket); diff --git a/test/spec/app/socket/router/index.spec.js b/test/spec/app/socket/router/index.spec.js index 6938edf4..65ff3269 100644 --- a/test/spec/app/socket/router/index.spec.js +++ b/test/spec/app/socket/router/index.spec.js @@ -47,6 +47,11 @@ describe('socket.router', () => { }; const MOCK_SOCKET = { id: 'socket-id', + handshake: { + query: { + user: { id: 'a', name: 'Bob Smith' } + } + }, rooms: ['socket-id'], events: {}, messages: [], @@ -101,7 +106,7 @@ describe('socket.router', () => { }); }); it('should have set up the appropriate events on the io router', () => { - const EXPECTED_EVENTS = ['register', 'view', 'edit', 'watch']; + const EXPECTED_EVENTS = ['view', 'edit', 'watch']; EXPECTED_EVENTS.forEach((event) => { expect(MOCK_IO_ROUTER.events[event]).to.be.a('function'); }); @@ -109,11 +114,6 @@ describe('socket.router', () => { }); describe('iorouter', () => { - const MOCK_CONTEXT_REGISTER = { - request: { - user: { id: 'a', name: 'Bob Smith' } - } - }; const MOCK_CONTEXT = { request: { caseId: '1234567890', @@ -121,11 +121,11 @@ describe('socket.router', () => { } }; beforeEach(() => { - // We need to register before each call as it sets up the user. - MOCK_IO_ROUTER.dispatch('register', MOCK_SOCKET, MOCK_CONTEXT_REGISTER, () => {}); + // Dispatch the connection each time. + MOCK_SOCKET_SERVER.dispatch('connection', MOCK_SOCKET); }); it('should appropriately handle registering a user', () => { - expect(router.getUser(MOCK_SOCKET.id)).to.deep.equal(MOCK_CONTEXT_REGISTER.request.user); + expect(router.getUser(MOCK_SOCKET.id)).to.deep.equal(MOCK_SOCKET.handshake.query.user); }); it('should appropriately handle viewing a case', () => { const ACTIVITY = 'view'; @@ -138,7 +138,7 @@ describe('socket.router', () => { expect(MOCK_HANDLERS.calls[0].params.socket).to.equal(MOCK_SOCKET); expect(MOCK_HANDLERS.calls[0].params.caseId).to.equal(MOCK_CONTEXT.request.caseId); // Note that the MOCK_CONTEXT doesn't include the user, which means we had to get it from elsewhere. - expect(MOCK_HANDLERS.calls[0].params.user).to.deep.equal(MOCK_CONTEXT_REGISTER.request.user); + expect(MOCK_HANDLERS.calls[0].params.user).to.deep.equal(MOCK_SOCKET.handshake.query.user); expect(MOCK_HANDLERS.calls[0].params.activity).to.equal(ACTIVITY); }); expect(nextCalled).to.be.true; @@ -154,7 +154,7 @@ describe('socket.router', () => { expect(MOCK_HANDLERS.calls[0].params.socket).to.equal(MOCK_SOCKET); expect(MOCK_HANDLERS.calls[0].params.caseId).to.equal(MOCK_CONTEXT.request.caseId); // Note that the MOCK_CONTEXT doesn't include the user, which means we had to get it from elsewhere. - expect(MOCK_HANDLERS.calls[0].params.user).to.deep.equal(MOCK_CONTEXT_REGISTER.request.user); + expect(MOCK_HANDLERS.calls[0].params.user).to.deep.equal(MOCK_SOCKET.handshake.query.user); expect(MOCK_HANDLERS.calls[0].params.activity).to.equal(ACTIVITY); }); expect(nextCalled).to.be.true; @@ -175,15 +175,7 @@ describe('socket.router', () => { }); describe('io', () => { - const MOCK_CONTEXT_REGISTER = { - request: { - user: { id: 'a', name: 'Bob Smith' } - } - }; beforeEach(() => { - // We need to register before each call as it sets up the user. - MOCK_IO_ROUTER.dispatch('register', MOCK_SOCKET, MOCK_CONTEXT_REGISTER, () => {}); - // Dispatch the connection each time. MOCK_SOCKET_SERVER.dispatch('connection', MOCK_SOCKET); }); From 67acec7db934f999564c4253ebb4b0ecd9733ac6 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Thu, 15 Jul 2021 11:36:20 +0100 Subject: [PATCH 039/113] Update activity-service.js Return the user id when indicating viewers and editors so that the client can filter out _its_ user, rather than doing the filtering on the server and sending different messages to each client. --- app/socket/service/activity-service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/socket/service/activity-service.js b/app/socket/service/activity-service.js index d19a6dfc..5ecf6c03 100644 --- a/app/socket/service/activity-service.js +++ b/app/socket/service/activity-service.js @@ -30,7 +30,7 @@ module.exports = (config, redis) => { return details.reduce((obj, item) => { if (item[1]) { const user = JSON.parse(item[1]); - obj[user.id] = { forename: user.forename, surname: user.surname }; + obj[user.id] = { id: user.id, forename: user.forename, surname: user.surname }; } return obj; }, {}); From 166cdfe84f44849b35cf083a9af0ccec598de842 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Thu, 15 Jul 2021 11:37:17 +0100 Subject: [PATCH 040/113] Update index.js The user must arrive as a string because it's being sent in the query string (for some reason). Therefore, this needs to be parsed as JSON before storing it. --- app/socket/router/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/socket/router/index.js b/app/socket/router/index.js index 7b3f9e06..10598cb2 100644 --- a/app/socket/router/index.js +++ b/app/socket/router/index.js @@ -48,7 +48,7 @@ const router = { // On client connection, attach the router and track the socket. io.on('connection', (socket) => { router.addConnection(socket); - router.addUser(socket.id, socket.handshake.query.user); + router.addUser(socket.id, JSON.parse(socket.handshake.query.user)); utils.log(socket, '', `connected (${router.getConnections().length} total)`); utils.log(socket, '', `connected (${router.getConnections().length} total)`, console.log, Date.now()); socket.use((packet, next) => { From 3b20e7e3f2790d1adc99d0f5e0fd28d63183ba05 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Thu, 15 Jul 2021 11:45:32 +0100 Subject: [PATCH 041/113] Update index.spec.js The user is now arriving as a JSON string and getting parsed. Updated the tests to reflect that. --- test/spec/app/socket/router/index.spec.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/spec/app/socket/router/index.spec.js b/test/spec/app/socket/router/index.spec.js index 65ff3269..e3cd9475 100644 --- a/test/spec/app/socket/router/index.spec.js +++ b/test/spec/app/socket/router/index.spec.js @@ -49,7 +49,7 @@ describe('socket.router', () => { id: 'socket-id', handshake: { query: { - user: { id: 'a', name: 'Bob Smith' } + user: JSON.stringify({ id: 'a', name: 'Bob Smith' }) } }, rooms: ['socket-id'], @@ -120,12 +120,13 @@ describe('socket.router', () => { caseIds: ['2345678901', '3456789012', '4567890123'] } }; + const MOCK_JSON_USER = JSON.parse(MOCK_SOCKET.handshake.query.user); beforeEach(() => { // Dispatch the connection each time. MOCK_SOCKET_SERVER.dispatch('connection', MOCK_SOCKET); }); it('should appropriately handle registering a user', () => { - expect(router.getUser(MOCK_SOCKET.id)).to.deep.equal(MOCK_SOCKET.handshake.query.user); + expect(router.getUser(MOCK_SOCKET.id)).to.deep.equal(MOCK_JSON_USER); }); it('should appropriately handle viewing a case', () => { const ACTIVITY = 'view'; @@ -138,7 +139,7 @@ describe('socket.router', () => { expect(MOCK_HANDLERS.calls[0].params.socket).to.equal(MOCK_SOCKET); expect(MOCK_HANDLERS.calls[0].params.caseId).to.equal(MOCK_CONTEXT.request.caseId); // Note that the MOCK_CONTEXT doesn't include the user, which means we had to get it from elsewhere. - expect(MOCK_HANDLERS.calls[0].params.user).to.deep.equal(MOCK_SOCKET.handshake.query.user); + expect(MOCK_HANDLERS.calls[0].params.user).to.deep.equal(MOCK_JSON_USER); expect(MOCK_HANDLERS.calls[0].params.activity).to.equal(ACTIVITY); }); expect(nextCalled).to.be.true; @@ -154,7 +155,7 @@ describe('socket.router', () => { expect(MOCK_HANDLERS.calls[0].params.socket).to.equal(MOCK_SOCKET); expect(MOCK_HANDLERS.calls[0].params.caseId).to.equal(MOCK_CONTEXT.request.caseId); // Note that the MOCK_CONTEXT doesn't include the user, which means we had to get it from elsewhere. - expect(MOCK_HANDLERS.calls[0].params.user).to.deep.equal(MOCK_SOCKET.handshake.query.user); + expect(MOCK_HANDLERS.calls[0].params.user).to.deep.equal(MOCK_JSON_USER); expect(MOCK_HANDLERS.calls[0].params.activity).to.equal(ACTIVITY); }); expect(nextCalled).to.be.true; From b2e40f11af99fef7543b3c67fafb00b7ff416d27 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Mon, 19 Jul 2021 16:55:09 +0100 Subject: [PATCH 042/113] Version Added some release notes and upped the version to a pre-release one with a minor increment. --- RELEASE-NOTES.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 RELEASE-NOTES.md diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md new file mode 100644 index 00000000..5b590aca --- /dev/null +++ b/RELEASE-NOTES.md @@ -0,0 +1,4 @@ +## RELEASE NOTES + +### Version 0.1.0-socket-alpha +**EUI-2976** Socket-based Activity Tracking. diff --git a/package.json b/package.json index ee064e61..fc488f36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ccd-case-activity-api", - "version": "0.0.2", + "version": "0.1.0-socket-alpha", "private": true, "scripts": { "setup": "cross-env NODE_PATH=. node --version", From 745a45ff9eac3b9e0f0cd8f05079f955e7ffda87 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Mon, 19 Jul 2021 17:03:43 +0100 Subject: [PATCH 043/113] Update values.preview.template.yaml Adding the MC PR to the whitelist. Let's see if this helps. --- charts/ccd-case-activity-api/values.preview.template.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/charts/ccd-case-activity-api/values.preview.template.yaml b/charts/ccd-case-activity-api/values.preview.template.yaml index af6b90c6..f8ee11bf 100644 --- a/charts/ccd-case-activity-api/values.preview.template.yaml +++ b/charts/ccd-case-activity-api/values.preview.template.yaml @@ -6,6 +6,7 @@ nodejs: REDIS_PORT: 6379 REDIS_PASSWORD: fake-password REDIS_SSL_ENABLED: "" + CORS_ORIGIN_WHITELIST: https://www-ccd.{{ .Values.global.environment }}.platform.hmcts.net, https://xui-webapp-pr-1172.service.core-compute-preview.internal keyVaults: redis: From 19b59f0aec9e69e3cfbc36469273c1299dd151a1 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Mon, 19 Jul 2021 17:24:27 +0100 Subject: [PATCH 044/113] Update values.preview.template.yaml No space between the whitelist items. --- charts/ccd-case-activity-api/values.preview.template.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/ccd-case-activity-api/values.preview.template.yaml b/charts/ccd-case-activity-api/values.preview.template.yaml index f8ee11bf..7c2319a5 100644 --- a/charts/ccd-case-activity-api/values.preview.template.yaml +++ b/charts/ccd-case-activity-api/values.preview.template.yaml @@ -6,7 +6,7 @@ nodejs: REDIS_PORT: 6379 REDIS_PASSWORD: fake-password REDIS_SSL_ENABLED: "" - CORS_ORIGIN_WHITELIST: https://www-ccd.{{ .Values.global.environment }}.platform.hmcts.net, https://xui-webapp-pr-1172.service.core-compute-preview.internal + CORS_ORIGIN_WHITELIST: https://www-ccd.{{ .Values.global.environment }}.platform.hmcts.net,https://xui-webapp-pr-1172.service.core-compute-preview.internal keyVaults: redis: From 32c725d8a0f744eafc921c4494edfbacaa1e5c10 Mon Sep 17 00:00:00 2001 From: Paul Graham Date: Wed, 28 Jul 2021 09:37:37 +0100 Subject: [PATCH 045/113] User name for logging Setting the user name property when it doesn't have one. This is purely a logging convenience. --- app/socket/router/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/socket/router/index.js b/app/socket/router/index.js index 10598cb2..c6cbb2e0 100644 --- a/app/socket/router/index.js +++ b/app/socket/router/index.js @@ -4,6 +4,9 @@ const users = {}; const connections = []; const router = { addUser: (socketId, user) => { + if (user && !user.name) { + user.name = `${user.forename} ${user.surname}`; + } users[socketId] = user; }, removeUser: (socketId) => { From 15350c336507414fcb981a720d177bffdcc4543e Mon Sep 17 00:00:00 2001 From: Paul Howes Date: Fri, 14 Jan 2022 11:15:16 +0000 Subject: [PATCH 046/113] Update packages --- yarn.lock | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index a7ace39c..4e9e28c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -484,6 +484,22 @@ body-parser@1.19.0: raw-body "2.4.0" type-is "~1.6.17" +body-parser@^1.18.2: + version "1.19.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.1.tgz#1499abbaa9274af3ecc9f6f10396c995943e31d4" + integrity sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA== + dependencies: + bytes "3.1.1" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.8.1" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.9.6" + raw-body "2.4.2" + type-is "~1.6.18" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -509,6 +525,11 @@ bytes@3.1.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== +bytes@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a" + integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg== + caching-transform@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" @@ -1591,6 +1612,17 @@ http-errors@1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" +http-errors@1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" + integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.1" + http-errors@~1.6.1: version "1.6.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" @@ -2771,6 +2803,11 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@6.9.6: + version "6.9.6" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee" + integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ== + qs@^6.5.1, qs@^6.9.1: version "6.9.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.1.tgz#20082c65cb78223635ab1a9eaca8875a29bf8ec9" @@ -2791,6 +2828,16 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" +raw-body@2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.2.tgz#baf3e9c21eebced59dd6533ac872b71f7b61cb32" + integrity sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ== + dependencies: + bytes "3.1.1" + http-errors "1.8.1" + iconv-lite "0.4.24" + unpipe "1.0.0" + read-pkg-up@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" @@ -3018,6 +3065,11 @@ setprototypeof@1.1.1: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -3405,6 +3457,11 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + tslib@^1.9.0: version "1.11.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" @@ -3447,10 +3504,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -underscore@^1.12.1, underscore@~1.9.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1" - integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g== +underscore@^1.13.1, underscore@~1.9.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.2.tgz#276cea1e8b9722a8dbed0100a407dda572125881" + integrity sha512-ekY1NhRzq0B08g4bGuX4wd2jZx5GnKz6mKSqFL4nqBlfyMGiG10gDFhDTMEfYmDL6Jy0FUIZp7wiRB+0BP7J2g== unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" From 4bf8c813c91b7e183ddf959fb55eb8f2ef1323ed Mon Sep 17 00:00:00 2001 From: Paul Howes Date: Fri, 14 Jan 2022 11:39:29 +0000 Subject: [PATCH 047/113] Yarn audit --- yarn-audit-known-issues | 1 + 1 file changed, 1 insertion(+) diff --git a/yarn-audit-known-issues b/yarn-audit-known-issues index e69de29b..a9cf81bd 100644 --- a/yarn-audit-known-issues +++ b/yarn-audit-known-issues @@ -0,0 +1 @@ +{"type":"auditAdvisory","data":{"resolution":{"id":1006871,"path":"socket.io>engine.io","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"5.1.1","paths":["socket.io>engine.io"]}],"metadata":null,"vulnerable_versions":">=5.0.0 <5.2.1","module_name":"engine.io","severity":"high","github_advisory_id":"GHSA-273r-mgr4-v34f","cves":["CVE-2022-21676"],"access":"public","patched_versions":">=5.2.1","updated":"2022-01-13T16:10:29.000Z","recommendation":"Upgrade to version 5.2.1 or later","cwe":"CWE-754","found_by":null,"deleted":null,"id":1006871,"references":"- https://github.com/socketio/engine.io/security/advisories/GHSA-273r-mgr4-v34f\n- https://nvd.nist.gov/vuln/detail/CVE-2022-21676\n- https://github.com/socketio/engine.io/commit/66f889fc1d966bf5bfa0de1939069153643874ab\n- https://github.com/socketio/engine.io/commit/a70800d7e96da32f6e6622804ef659ebc58659db\n- https://github.com/socketio/engine.io/commit/c0e194d44933bd83bf9a4b126fca68ba7bf5098c\n- https://github.com/socketio/engine.io/releases/tag/4.1.2\n- https://github.com/socketio/engine.io/releases/tag/5.2.1\n- https://github.com/socketio/engine.io/releases/tag/6.1.1\n- https://github.com/advisories/GHSA-273r-mgr4-v34f","created":"2022-01-13T17:00:45.667Z","reported_by":null,"title":"Uncaught Exception in engine.io","npm_advisory_id":null,"overview":"### Impact\n\nA specially crafted HTTP request can trigger an uncaught exception on the Engine.IO server, thus killing the Node.js process.\n\n> RangeError: Invalid WebSocket frame: RSV2 and RSV3 must be clear\n> at Receiver.getInfo (/.../node_modules/ws/lib/receiver.js:176:14)\n> at Receiver.startLoop (/.../node_modules/ws/lib/receiver.js:136:22)\n> at Receiver._write (/.../node_modules/ws/lib/receiver.js:83:10)\n> at writeOrBuffer (internal/streams/writable.js:358:12)\n\nThis impacts all the users of the [`engine.io`](https://www.npmjs.com/package/engine.io) package starting from version `4.0.0`, including those who uses depending packages like [`socket.io`](https://www.npmjs.com/package/socket.io).\n\n### Patches\n\nA fix has been released for each major branch:\n\n| Version range | Fixed version |\n| --- | --- |\n| `engine.io@4.x.x` | `4.1.2` |\n| `engine.io@5.x.x` | `5.2.1` |\n| `engine.io@6.x.x` | `6.1.1` |\n\nPrevious versions (`< 4.0.0`) are not impacted.\n\nFor `socket.io` users:\n\n| Version range | `engine.io` version | Needs minor update? |\n| --- | --- | --- |\n| `socket.io@4.4.x` | `~6.1.0` | -\n| `socket.io@4.3.x` | `~6.0.0` | Please upgrade to `socket.io@4.4.x`\n| `socket.io@4.2.x` | `~5.2.0` | -\n| `socket.io@4.1.x` | `~5.1.1` | Please upgrade to `socket.io@4.4.x`\n| `socket.io@4.0.x` | `~5.0.0` | Please upgrade to `socket.io@4.4.x`\n| `socket.io@3.1.x` | `~4.1.0` | -\n| `socket.io@3.0.x` | `~4.0.0` | Please upgrade to `socket.io@3.1.x` or `socket.io@4.4.x` (see [here](https://socket.io/docs/v4/migrating-from-3-x-to-4-0/))\n\nIn most cases, running `npm audit fix` should be sufficient. You can also use `npm update engine.io --depth=9999`.\n\n### Workarounds\n\nThere is no known workaround except upgrading to a safe version.\n\n### For more information\n\nIf you have any questions or comments about this advisory:\n\n* Open an issue in [`engine.io`](https://github.com/socketio/engine.io)\n\nThanks to Marcus Wejderot from Mevisio for the responsible disclosure.\n","url":"https://github.com/advisories/GHSA-273r-mgr4-v34f"}}} From f48e9721a4464fe796c1229616d36f1d8688c07b Mon Sep 17 00:00:00 2001 From: Paul Howes Date: Tue, 25 Jan 2022 06:19:01 +0000 Subject: [PATCH 048/113] Known issues temporary workaround --- yarn-audit-known-issues | 1 + 1 file changed, 1 insertion(+) diff --git a/yarn-audit-known-issues b/yarn-audit-known-issues index a9cf81bd..8e1af06e 100644 --- a/yarn-audit-known-issues +++ b/yarn-audit-known-issues @@ -1 +1,2 @@ {"type":"auditAdvisory","data":{"resolution":{"id":1006871,"path":"socket.io>engine.io","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"5.1.1","paths":["socket.io>engine.io"]}],"metadata":null,"vulnerable_versions":">=5.0.0 <5.2.1","module_name":"engine.io","severity":"high","github_advisory_id":"GHSA-273r-mgr4-v34f","cves":["CVE-2022-21676"],"access":"public","patched_versions":">=5.2.1","updated":"2022-01-13T16:10:29.000Z","recommendation":"Upgrade to version 5.2.1 or later","cwe":"CWE-754","found_by":null,"deleted":null,"id":1006871,"references":"- https://github.com/socketio/engine.io/security/advisories/GHSA-273r-mgr4-v34f\n- https://nvd.nist.gov/vuln/detail/CVE-2022-21676\n- https://github.com/socketio/engine.io/commit/66f889fc1d966bf5bfa0de1939069153643874ab\n- https://github.com/socketio/engine.io/commit/a70800d7e96da32f6e6622804ef659ebc58659db\n- https://github.com/socketio/engine.io/commit/c0e194d44933bd83bf9a4b126fca68ba7bf5098c\n- https://github.com/socketio/engine.io/releases/tag/4.1.2\n- https://github.com/socketio/engine.io/releases/tag/5.2.1\n- https://github.com/socketio/engine.io/releases/tag/6.1.1\n- https://github.com/advisories/GHSA-273r-mgr4-v34f","created":"2022-01-13T17:00:45.667Z","reported_by":null,"title":"Uncaught Exception in engine.io","npm_advisory_id":null,"overview":"### Impact\n\nA specially crafted HTTP request can trigger an uncaught exception on the Engine.IO server, thus killing the Node.js process.\n\n> RangeError: Invalid WebSocket frame: RSV2 and RSV3 must be clear\n> at Receiver.getInfo (/.../node_modules/ws/lib/receiver.js:176:14)\n> at Receiver.startLoop (/.../node_modules/ws/lib/receiver.js:136:22)\n> at Receiver._write (/.../node_modules/ws/lib/receiver.js:83:10)\n> at writeOrBuffer (internal/streams/writable.js:358:12)\n\nThis impacts all the users of the [`engine.io`](https://www.npmjs.com/package/engine.io) package starting from version `4.0.0`, including those who uses depending packages like [`socket.io`](https://www.npmjs.com/package/socket.io).\n\n### Patches\n\nA fix has been released for each major branch:\n\n| Version range | Fixed version |\n| --- | --- |\n| `engine.io@4.x.x` | `4.1.2` |\n| `engine.io@5.x.x` | `5.2.1` |\n| `engine.io@6.x.x` | `6.1.1` |\n\nPrevious versions (`< 4.0.0`) are not impacted.\n\nFor `socket.io` users:\n\n| Version range | `engine.io` version | Needs minor update? |\n| --- | --- | --- |\n| `socket.io@4.4.x` | `~6.1.0` | -\n| `socket.io@4.3.x` | `~6.0.0` | Please upgrade to `socket.io@4.4.x`\n| `socket.io@4.2.x` | `~5.2.0` | -\n| `socket.io@4.1.x` | `~5.1.1` | Please upgrade to `socket.io@4.4.x`\n| `socket.io@4.0.x` | `~5.0.0` | Please upgrade to `socket.io@4.4.x`\n| `socket.io@3.1.x` | `~4.1.0` | -\n| `socket.io@3.0.x` | `~4.0.0` | Please upgrade to `socket.io@3.1.x` or `socket.io@4.4.x` (see [here](https://socket.io/docs/v4/migrating-from-3-x-to-4-0/))\n\nIn most cases, running `npm audit fix` should be sufficient. You can also use `npm update engine.io --depth=9999`.\n\n### Workarounds\n\nThere is no known workaround except upgrading to a safe version.\n\n### For more information\n\nIf you have any questions or comments about this advisory:\n\n* Open an issue in [`engine.io`](https://github.com/socketio/engine.io)\n\nThanks to Marcus Wejderot from Mevisio for the responsible disclosure.\n","url":"https://github.com/advisories/GHSA-273r-mgr4-v34f"}}} +{"type":"auditAdvisory","data":{"resolution":{"id":1006899,"path":"node-fetch","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"2.6.1","paths":["node-fetch"]}],"metadata":null,"vulnerable_versions":"<2.6.7","module_name":"node-fetch","severity":"high","github_advisory_id":"GHSA-r683-j2x4-v87g","cves":["CVE-2022-0235"],"access":"public","patched_versions":">=2.6.7","updated":"2022-01-23T01:52:43.000Z","recommendation":"Upgrade to version 2.6.7 or later","cwe":"CWE-173","found_by":null,"deleted":null,"id":1006899,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2022-0235\n- https://github.com/node-fetch/node-fetch/commit/36e47e8a6406185921e4985dcbeff140d73eaa10\n- https://huntr.dev/bounties/d26ab655-38d6-48b3-be15-f9ad6b6ae6f7\n- https://github.com/node-fetch/node-fetch/pull/1453\n- https://github.com/advisories/GHSA-r683-j2x4-v87g","created":"2022-01-23T02:00:40.828Z","reported_by":null,"title":"node-fetch is vulnerable to Exposure of Sensitive Information to an Unauthorized Actor","npm_advisory_id":null,"overview":"node-fetch is vulnerable to Exposure of Sensitive Information to an Unauthorized Actor","url":"https://github.com/advisories/GHSA-r683-j2x4-v87g"}}} From 0da814d07bd4db537afcb053e8a1bea8464a2bb2 Mon Sep 17 00:00:00 2001 From: Phillip Whitaker Date: Fri, 7 Oct 2022 14:59:52 +0100 Subject: [PATCH 049/113] CVE test --- yarn-audit-known-issues | 1 - 1 file changed, 1 deletion(-) diff --git a/yarn-audit-known-issues b/yarn-audit-known-issues index 6cf0f2f6..e69de29b 100644 --- a/yarn-audit-known-issues +++ b/yarn-audit-known-issues @@ -1 +0,0 @@ -{"type":"auditAdvisory","data":{"resolution":{"id":1070492,"path":"socket.io>engine.io","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"5.1.1","paths":["socket.io>engine.io"]}],"metadata":null,"vulnerable_versions":">=5.0.0 <5.2.1","module_name":"engine.io","severity":"high","github_advisory_id":"GHSA-273r-mgr4-v34f","cves":["CVE-2022-21676"],"access":"public","patched_versions":">=5.2.1","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2022-06-15T18:39:17.000Z","recommendation":"Upgrade to version 5.2.1 or later","cwe":["CWE-754"],"found_by":null,"deleted":null,"id":1070492,"references":"- https://github.com/socketio/engine.io/security/advisories/GHSA-273r-mgr4-v34f\n- https://nvd.nist.gov/vuln/detail/CVE-2022-21676\n- https://github.com/socketio/engine.io/commit/66f889fc1d966bf5bfa0de1939069153643874ab\n- https://github.com/socketio/engine.io/commit/a70800d7e96da32f6e6622804ef659ebc58659db\n- https://github.com/socketio/engine.io/commit/c0e194d44933bd83bf9a4b126fca68ba7bf5098c\n- https://github.com/socketio/engine.io/releases/tag/4.1.2\n- https://github.com/socketio/engine.io/releases/tag/5.2.1\n- https://github.com/socketio/engine.io/releases/tag/6.1.1\n- https://security.netapp.com/advisory/ntap-20220209-0002/\n- https://github.com/advisories/GHSA-273r-mgr4-v34f","created":"2022-01-13T16:14:17.000Z","reported_by":null,"title":"Uncaught Exception in engine.io","npm_advisory_id":null,"overview":"### Impact\n\nA specially crafted HTTP request can trigger an uncaught exception on the Engine.IO server, thus killing the Node.js process.\n\n> RangeError: Invalid WebSocket frame: RSV2 and RSV3 must be clear\n> at Receiver.getInfo (/.../node_modules/ws/lib/receiver.js:176:14)\n> at Receiver.startLoop (/.../node_modules/ws/lib/receiver.js:136:22)\n> at Receiver._write (/.../node_modules/ws/lib/receiver.js:83:10)\n> at writeOrBuffer (internal/streams/writable.js:358:12)\n\nThis impacts all the users of the [`engine.io`](https://www.npmjs.com/package/engine.io) package starting from version `4.0.0`, including those who uses depending packages like [`socket.io`](https://www.npmjs.com/package/socket.io).\n\n### Patches\n\nA fix has been released for each major branch:\n\n| Version range | Fixed version |\n| --- | --- |\n| `engine.io@4.x.x` | `4.1.2` |\n| `engine.io@5.x.x` | `5.2.1` |\n| `engine.io@6.x.x` | `6.1.1` |\n\nPrevious versions (`< 4.0.0`) are not impacted.\n\nFor `socket.io` users:\n\n| Version range | `engine.io` version | Needs minor update? |\n| --- | --- | --- |\n| `socket.io@4.4.x` | `~6.1.0` | -\n| `socket.io@4.3.x` | `~6.0.0` | Please upgrade to `socket.io@4.4.x`\n| `socket.io@4.2.x` | `~5.2.0` | -\n| `socket.io@4.1.x` | `~5.1.1` | Please upgrade to `socket.io@4.4.x`\n| `socket.io@4.0.x` | `~5.0.0` | Please upgrade to `socket.io@4.4.x`\n| `socket.io@3.1.x` | `~4.1.0` | -\n| `socket.io@3.0.x` | `~4.0.0` | Please upgrade to `socket.io@3.1.x` or `socket.io@4.4.x` (see [here](https://socket.io/docs/v4/migrating-from-3-x-to-4-0/))\n\nIn most cases, running `npm audit fix` should be sufficient. You can also use `npm update engine.io --depth=9999`.\n\n### Workarounds\n\nThere is no known workaround except upgrading to a safe version.\n\n### For more information\n\nIf you have any questions or comments about this advisory:\n\n* Open an issue in [`engine.io`](https://github.com/socketio/engine.io)\n\nThanks to Marcus Wejderot from Mevisio for the responsible disclosure.\n","url":"https://github.com/advisories/GHSA-273r-mgr4-v34f"}}} From bf49b0c288b65a496cf1c95ec3ef149e61b85b64 Mon Sep 17 00:00:00 2001 From: Phillip Whitaker Date: Fri, 7 Oct 2022 15:07:18 +0100 Subject: [PATCH 050/113] Updated CVE audit known issues file --- yarn-audit-known-issues | 1 + 1 file changed, 1 insertion(+) diff --git a/yarn-audit-known-issues b/yarn-audit-known-issues index e69de29b..6cf0f2f6 100644 --- a/yarn-audit-known-issues +++ b/yarn-audit-known-issues @@ -0,0 +1 @@ +{"type":"auditAdvisory","data":{"resolution":{"id":1070492,"path":"socket.io>engine.io","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"5.1.1","paths":["socket.io>engine.io"]}],"metadata":null,"vulnerable_versions":">=5.0.0 <5.2.1","module_name":"engine.io","severity":"high","github_advisory_id":"GHSA-273r-mgr4-v34f","cves":["CVE-2022-21676"],"access":"public","patched_versions":">=5.2.1","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2022-06-15T18:39:17.000Z","recommendation":"Upgrade to version 5.2.1 or later","cwe":["CWE-754"],"found_by":null,"deleted":null,"id":1070492,"references":"- https://github.com/socketio/engine.io/security/advisories/GHSA-273r-mgr4-v34f\n- https://nvd.nist.gov/vuln/detail/CVE-2022-21676\n- https://github.com/socketio/engine.io/commit/66f889fc1d966bf5bfa0de1939069153643874ab\n- https://github.com/socketio/engine.io/commit/a70800d7e96da32f6e6622804ef659ebc58659db\n- https://github.com/socketio/engine.io/commit/c0e194d44933bd83bf9a4b126fca68ba7bf5098c\n- https://github.com/socketio/engine.io/releases/tag/4.1.2\n- https://github.com/socketio/engine.io/releases/tag/5.2.1\n- https://github.com/socketio/engine.io/releases/tag/6.1.1\n- https://security.netapp.com/advisory/ntap-20220209-0002/\n- https://github.com/advisories/GHSA-273r-mgr4-v34f","created":"2022-01-13T16:14:17.000Z","reported_by":null,"title":"Uncaught Exception in engine.io","npm_advisory_id":null,"overview":"### Impact\n\nA specially crafted HTTP request can trigger an uncaught exception on the Engine.IO server, thus killing the Node.js process.\n\n> RangeError: Invalid WebSocket frame: RSV2 and RSV3 must be clear\n> at Receiver.getInfo (/.../node_modules/ws/lib/receiver.js:176:14)\n> at Receiver.startLoop (/.../node_modules/ws/lib/receiver.js:136:22)\n> at Receiver._write (/.../node_modules/ws/lib/receiver.js:83:10)\n> at writeOrBuffer (internal/streams/writable.js:358:12)\n\nThis impacts all the users of the [`engine.io`](https://www.npmjs.com/package/engine.io) package starting from version `4.0.0`, including those who uses depending packages like [`socket.io`](https://www.npmjs.com/package/socket.io).\n\n### Patches\n\nA fix has been released for each major branch:\n\n| Version range | Fixed version |\n| --- | --- |\n| `engine.io@4.x.x` | `4.1.2` |\n| `engine.io@5.x.x` | `5.2.1` |\n| `engine.io@6.x.x` | `6.1.1` |\n\nPrevious versions (`< 4.0.0`) are not impacted.\n\nFor `socket.io` users:\n\n| Version range | `engine.io` version | Needs minor update? |\n| --- | --- | --- |\n| `socket.io@4.4.x` | `~6.1.0` | -\n| `socket.io@4.3.x` | `~6.0.0` | Please upgrade to `socket.io@4.4.x`\n| `socket.io@4.2.x` | `~5.2.0` | -\n| `socket.io@4.1.x` | `~5.1.1` | Please upgrade to `socket.io@4.4.x`\n| `socket.io@4.0.x` | `~5.0.0` | Please upgrade to `socket.io@4.4.x`\n| `socket.io@3.1.x` | `~4.1.0` | -\n| `socket.io@3.0.x` | `~4.0.0` | Please upgrade to `socket.io@3.1.x` or `socket.io@4.4.x` (see [here](https://socket.io/docs/v4/migrating-from-3-x-to-4-0/))\n\nIn most cases, running `npm audit fix` should be sufficient. You can also use `npm update engine.io --depth=9999`.\n\n### Workarounds\n\nThere is no known workaround except upgrading to a safe version.\n\n### For more information\n\nIf you have any questions or comments about this advisory:\n\n* Open an issue in [`engine.io`](https://github.com/socketio/engine.io)\n\nThanks to Marcus Wejderot from Mevisio for the responsible disclosure.\n","url":"https://github.com/advisories/GHSA-273r-mgr4-v34f"}}} From 799da22d8f1a1ff058b66a7744fd8a179d4f52cc Mon Sep 17 00:00:00 2001 From: LucaDelBuonoHMCTS Date: Fri, 10 Mar 2023 14:27:09 +0000 Subject: [PATCH 051/113] EUI-2976: Fix linting --- app/socket/redis/pub-sub.js | 2 +- app/socket/router/index.js | 2 ++ app/socket/service/handlers.js | 3 ++- app/socket/utils/index.js | 12 ++++++++---- app/socket/utils/other.js | 2 +- app/socket/utils/store.js | 14 +++++++------- 6 files changed, 21 insertions(+), 14 deletions(-) diff --git a/app/socket/redis/pub-sub.js b/app/socket/redis/pub-sub.js index 7cb287ad..271e0e2b 100644 --- a/app/socket/redis/pub-sub.js +++ b/app/socket/redis/pub-sub.js @@ -2,7 +2,7 @@ const keys = require('./keys'); module.exports = () => { return { - init: (watcher, caseNotifier) => { + init: (watcher, caseNotifier) => { if (watcher && typeof caseNotifier === 'function') { watcher.psubscribe(`${keys.prefixes.case}:*`); watcher.on('pmessage', (_, room) => { diff --git a/app/socket/router/index.js b/app/socket/router/index.js index c6cbb2e0..609eeb74 100644 --- a/app/socket/router/index.js +++ b/app/socket/router/index.js @@ -53,6 +53,7 @@ const router = { router.addConnection(socket); router.addUser(socket.id, JSON.parse(socket.handshake.query.user)); utils.log(socket, '', `connected (${router.getConnections().length} total)`); + // eslint-disable-next-line no-console utils.log(socket, '', `connected (${router.getConnections().length} total)`, console.log, Date.now()); socket.use((packet, next) => { iorouter.attach(socket, packet, next); @@ -60,6 +61,7 @@ const router = { // When the socket disconnects, do an appropriate teardown. socket.on('disconnect', () => { utils.log(socket, '', `disconnected (${router.getConnections().length - 1} total)`); + // eslint-disable-next-line no-console utils.log(socket, '', `disconnected (${router.getConnections().length - 1} total)`, console.log, Date.now()); handlers.removeSocketActivity(socket.id); router.removeUser(socket.id); diff --git a/app/socket/service/handlers.js b/app/socket/service/handlers.js index 09c5348f..a41fda84 100644 --- a/app/socket/service/handlers.js +++ b/app/socket/service/handlers.js @@ -19,7 +19,8 @@ module.exports = (activityService, socketServer) => { /** * Notify all users in a case room about any change to activity on a case. - * @param {*} caseId The id of the case that has activity and that people should be notified about. + * @param {*} caseId The id of the case that has activity and that people should be + * notified about. */ async function notify(caseId) { const cs = await activityService.getActivityForCases([caseId]); diff --git a/app/socket/utils/index.js b/app/socket/utils/index.js index c54179ce..5f906ecb 100644 --- a/app/socket/utils/index.js +++ b/app/socket/utils/index.js @@ -1,9 +1,13 @@ const other = require('./other'); +const get = require('./get'); +const remove = require('./remove'); +const store = require('./store'); +const watch = require('./watch'); module.exports = { ...other, - get: require('./get'), - remove: require('./remove'), - store: require('./store'), - watch: require('./watch') + get, + remove, + store, + watch }; diff --git a/app/socket/utils/other.js b/app/socket/utils/other.js index 82ca055f..8a9bcaa2 100644 --- a/app/socket/utils/other.js +++ b/app/socket/utils/other.js @@ -56,7 +56,7 @@ const other = { 'caseworker-employment-leeds', 'caseworker' ], - name: name, + name, given_name: givenName, family_name: familyName }; diff --git a/app/socket/utils/store.js b/app/socket/utils/store.js index f139bdfe..c7300894 100644 --- a/app/socket/utils/store.js +++ b/app/socket/utils/store.js @@ -1,6 +1,6 @@ const debug = require('debug')('ccd-case-activity-api:socket-utils-store'); const redisActivityKeys = require('../redis/keys'); -const toUserString = require('./other').toUserString; +const { toUserString } = require('./other'); const store = { userActivity: (activityKey, userId, score) => { @@ -9,15 +9,15 @@ const store = { }, userDetails: (user, ttl) => { const key = redisActivityKeys.user(user.uid); - const store = toUserString(user); - debug(`about to store details "${key}" for user "${user.uid}": ${store}`); - return ['set', key, store, 'EX', ttl]; + const userString = toUserString(user); + debug(`about to store details "${key}" for user "${user.uid}": ${userString}`); + return ['set', key, userString, 'EX', ttl]; }, socketActivity: (socketId, activityKey, caseId, userId, ttl) => { const key = redisActivityKeys.socket(socketId); - const store = JSON.stringify({ activityKey, caseId, userId }); - debug(`about to store activity "${key}" for socket "${socketId}": ${store}`); - return ['set', key, store, 'EX', ttl]; + const userString = JSON.stringify({ activityKey, caseId, userId }); + debug(`about to store activity "${key}" for socket "${socketId}": ${userString}`); + return ['set', key, userString, 'EX', ttl]; } }; From 61ba69ffa2f2b59c9f369bf66647f8294bd08a88 Mon Sep 17 00:00:00 2001 From: LucaDelBuonoHMCTS Date: Tue, 14 Mar 2023 14:39:07 +0000 Subject: [PATCH 052/113] EUI-2976: Force rebuild --- app/health.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/health.js b/app/health.js index 1eeb39d3..4bc2ede6 100644 --- a/app/health.js +++ b/app/health.js @@ -14,5 +14,4 @@ const activityHealth = healthcheck.configure({ .catch(() => healthcheck.down())), }, }); - module.exports = activityHealth; From b4f084534f221c4ef90ff200ff7dfc97bd75254e Mon Sep 17 00:00:00 2001 From: Dan Lysiak <50049163+danlysiak@users.noreply.github.com> Date: Wed, 15 Mar 2023 17:50:25 +0000 Subject: [PATCH 053/113] Resolve preview issue --- charts/ccd-case-activity-api/values.preview.template.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/charts/ccd-case-activity-api/values.preview.template.yaml b/charts/ccd-case-activity-api/values.preview.template.yaml index 7c2319a5..e82a6d81 100644 --- a/charts/ccd-case-activity-api/values.preview.template.yaml +++ b/charts/ccd-case-activity-api/values.preview.template.yaml @@ -11,3 +11,7 @@ nodejs: redis: enabled: true + architecture: standalone + auth: + enabled: true + password: "fake-password" From 2c4f0949e0105eb6dcf949f5ac8b7043252b641b Mon Sep 17 00:00:00 2001 From: LucaDelBuonoHMCTS Date: Mon, 20 Mar 2023 10:43:26 +0000 Subject: [PATCH 054/113] EUI-2976: Whitelist everything --- charts/ccd-case-activity-api/values.preview.template.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/ccd-case-activity-api/values.preview.template.yaml b/charts/ccd-case-activity-api/values.preview.template.yaml index e82a6d81..23efe2bd 100644 --- a/charts/ccd-case-activity-api/values.preview.template.yaml +++ b/charts/ccd-case-activity-api/values.preview.template.yaml @@ -6,7 +6,7 @@ nodejs: REDIS_PORT: 6379 REDIS_PASSWORD: fake-password REDIS_SSL_ENABLED: "" - CORS_ORIGIN_WHITELIST: https://www-ccd.{{ .Values.global.environment }}.platform.hmcts.net,https://xui-webapp-pr-1172.service.core-compute-preview.internal + CORS_ORIGIN_WHITELIST: * keyVaults: redis: From 685355f22c261e8353ccea95a45f93e859c43c10 Mon Sep 17 00:00:00 2001 From: LucaDelBuonoHMCTS Date: Mon, 20 Mar 2023 10:58:29 +0000 Subject: [PATCH 055/113] EUI-2976: Whitelist everything --- charts/ccd-case-activity-api/values.preview.template.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/ccd-case-activity-api/values.preview.template.yaml b/charts/ccd-case-activity-api/values.preview.template.yaml index 23efe2bd..555198fe 100644 --- a/charts/ccd-case-activity-api/values.preview.template.yaml +++ b/charts/ccd-case-activity-api/values.preview.template.yaml @@ -6,7 +6,7 @@ nodejs: REDIS_PORT: 6379 REDIS_PASSWORD: fake-password REDIS_SSL_ENABLED: "" - CORS_ORIGIN_WHITELIST: * + CORS_ORIGIN_WHITELIST: https://*:*,http://*:* keyVaults: redis: From 8ee832d5fa59de51c10783e555773bda8d5fe5ac Mon Sep 17 00:00:00 2001 From: LucaDelBuonoHMCTS Date: Mon, 20 Mar 2023 11:08:57 +0000 Subject: [PATCH 056/113] EUI-2976: Whitelist everything --- charts/ccd-case-activity-api/values.preview.template.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/ccd-case-activity-api/values.preview.template.yaml b/charts/ccd-case-activity-api/values.preview.template.yaml index 555198fe..487af142 100644 --- a/charts/ccd-case-activity-api/values.preview.template.yaml +++ b/charts/ccd-case-activity-api/values.preview.template.yaml @@ -6,7 +6,7 @@ nodejs: REDIS_PORT: 6379 REDIS_PASSWORD: fake-password REDIS_SSL_ENABLED: "" - CORS_ORIGIN_WHITELIST: https://*:*,http://*:* + CORS_ORIGIN_WHITELIST: "*" keyVaults: redis: From ea379e7fdd356df9411ad3a711022e93d8864bd8 Mon Sep 17 00:00:00 2001 From: LucaDelBuonoHMCTS Date: Tue, 21 Mar 2023 10:17:18 +0000 Subject: [PATCH 057/113] EUI-2976: Update node-fetch --- package.json | 2 +- yarn-audit-known-issues | 1 - yarn.lock | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index a8371e11..6b4a5c5d 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "nocache": "^2.1.0", "node-cache": "^5.1.0", "node-cron": "^1.2.1", - "node-fetch": "^2.6.1", + "node-fetch": "^2.6.7", "socket.io": "^4.1.2", "socket.io-router-middleware": "^1.1.2" }, diff --git a/yarn-audit-known-issues b/yarn-audit-known-issues index 6cf0f2f6..e69de29b 100644 --- a/yarn-audit-known-issues +++ b/yarn-audit-known-issues @@ -1 +0,0 @@ -{"type":"auditAdvisory","data":{"resolution":{"id":1070492,"path":"socket.io>engine.io","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"5.1.1","paths":["socket.io>engine.io"]}],"metadata":null,"vulnerable_versions":">=5.0.0 <5.2.1","module_name":"engine.io","severity":"high","github_advisory_id":"GHSA-273r-mgr4-v34f","cves":["CVE-2022-21676"],"access":"public","patched_versions":">=5.2.1","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2022-06-15T18:39:17.000Z","recommendation":"Upgrade to version 5.2.1 or later","cwe":["CWE-754"],"found_by":null,"deleted":null,"id":1070492,"references":"- https://github.com/socketio/engine.io/security/advisories/GHSA-273r-mgr4-v34f\n- https://nvd.nist.gov/vuln/detail/CVE-2022-21676\n- https://github.com/socketio/engine.io/commit/66f889fc1d966bf5bfa0de1939069153643874ab\n- https://github.com/socketio/engine.io/commit/a70800d7e96da32f6e6622804ef659ebc58659db\n- https://github.com/socketio/engine.io/commit/c0e194d44933bd83bf9a4b126fca68ba7bf5098c\n- https://github.com/socketio/engine.io/releases/tag/4.1.2\n- https://github.com/socketio/engine.io/releases/tag/5.2.1\n- https://github.com/socketio/engine.io/releases/tag/6.1.1\n- https://security.netapp.com/advisory/ntap-20220209-0002/\n- https://github.com/advisories/GHSA-273r-mgr4-v34f","created":"2022-01-13T16:14:17.000Z","reported_by":null,"title":"Uncaught Exception in engine.io","npm_advisory_id":null,"overview":"### Impact\n\nA specially crafted HTTP request can trigger an uncaught exception on the Engine.IO server, thus killing the Node.js process.\n\n> RangeError: Invalid WebSocket frame: RSV2 and RSV3 must be clear\n> at Receiver.getInfo (/.../node_modules/ws/lib/receiver.js:176:14)\n> at Receiver.startLoop (/.../node_modules/ws/lib/receiver.js:136:22)\n> at Receiver._write (/.../node_modules/ws/lib/receiver.js:83:10)\n> at writeOrBuffer (internal/streams/writable.js:358:12)\n\nThis impacts all the users of the [`engine.io`](https://www.npmjs.com/package/engine.io) package starting from version `4.0.0`, including those who uses depending packages like [`socket.io`](https://www.npmjs.com/package/socket.io).\n\n### Patches\n\nA fix has been released for each major branch:\n\n| Version range | Fixed version |\n| --- | --- |\n| `engine.io@4.x.x` | `4.1.2` |\n| `engine.io@5.x.x` | `5.2.1` |\n| `engine.io@6.x.x` | `6.1.1` |\n\nPrevious versions (`< 4.0.0`) are not impacted.\n\nFor `socket.io` users:\n\n| Version range | `engine.io` version | Needs minor update? |\n| --- | --- | --- |\n| `socket.io@4.4.x` | `~6.1.0` | -\n| `socket.io@4.3.x` | `~6.0.0` | Please upgrade to `socket.io@4.4.x`\n| `socket.io@4.2.x` | `~5.2.0` | -\n| `socket.io@4.1.x` | `~5.1.1` | Please upgrade to `socket.io@4.4.x`\n| `socket.io@4.0.x` | `~5.0.0` | Please upgrade to `socket.io@4.4.x`\n| `socket.io@3.1.x` | `~4.1.0` | -\n| `socket.io@3.0.x` | `~4.0.0` | Please upgrade to `socket.io@3.1.x` or `socket.io@4.4.x` (see [here](https://socket.io/docs/v4/migrating-from-3-x-to-4-0/))\n\nIn most cases, running `npm audit fix` should be sufficient. You can also use `npm update engine.io --depth=9999`.\n\n### Workarounds\n\nThere is no known workaround except upgrading to a safe version.\n\n### For more information\n\nIf you have any questions or comments about this advisory:\n\n* Open an issue in [`engine.io`](https://github.com/socketio/engine.io)\n\nThanks to Marcus Wejderot from Mevisio for the responsible disclosure.\n","url":"https://github.com/advisories/GHSA-273r-mgr4-v34f"}}} diff --git a/yarn.lock b/yarn.lock index 49e81ea7..b611ff02 100644 --- a/yarn.lock +++ b/yarn.lock @@ -848,7 +848,7 @@ __metadata: nock: ^12.0.3 node-cache: ^5.1.0 node-cron: ^1.2.1 - node-fetch: ^2.6.1 + node-fetch: ^2.6.7 node-mocks-http: ^1.7.0 nyc: ^15.0.0 proxyquire: ^2.1.3 @@ -3631,7 +3631,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.1": +"node-fetch@npm:^2.6.7": version: 2.6.9 resolution: "node-fetch@npm:2.6.9" dependencies: From 0a79c13c32e884e73d3a65f89a2d89103328d7a4 Mon Sep 17 00:00:00 2001 From: LucaDelBuonoHMCTS Date: Thu, 30 Mar 2023 10:00:02 +0100 Subject: [PATCH 058/113] EUI-2976: Reduce ttl to 30 secs for testing --- config/default.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/default.yaml b/config/default.yaml index 30f811b1..a4251053 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -13,7 +13,7 @@ redis: activityTtlSec: 5 userDetailsTtlSec: 2 socket: - activityTtlSec: 600 + activityTtlSec: 30 userDetailsTtlSec: 3600 cache: user_info_enabled: true From 0e04b63c105feaf1b8f2bb5a4c09bf81a735e40d Mon Sep 17 00:00:00 2001 From: Andy Wilkins Date: Wed, 30 Jul 2025 10:51:29 +0100 Subject: [PATCH 059/113] update dependencies --- package.json | 4 +- yarn-audit-known-issues | 3 - yarn.lock | 3974 ++++++++++++++++++++++----------------- 3 files changed, 2277 insertions(+), 1704 deletions(-) diff --git a/package.json b/package.json index 9380f13b..3a51d60a 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "node-cron": "^1.2.1", "node-fetch": "^2.6.7", "or": "^0.2.0", - "socket.io": "^4.1.2", + "socket.io": "^4.8.1", "socket.io-router-middleware": "^1.1.2", "yarn": "^1.22.22" }, @@ -80,7 +80,7 @@ "sinon-chai": "^3.5.0", "sinon-express-mock": "^2.2.1", "sonar-scanner": "^3.1.0", - "supertest": "^3.0.0" + "supertest": "^7.1.4" }, "resolutions": { "async": "^2.6.4", diff --git a/yarn-audit-known-issues b/yarn-audit-known-issues index 5f1f8357..e693a979 100644 --- a/yarn-audit-known-issues +++ b/yarn-audit-known-issues @@ -1,5 +1,2 @@ -{"value":"form-data","children":{"ID":1106507,"Issue":"form-data uses unsafe random function in form-data for choosing boundary","URL":"https://github.com/advisories/GHSA-fjxv-7rqg-78g4","Severity":"critical","Vulnerable Versions":">=4.0.0 <4.0.4","Tree Versions":["4.0.3"],"Dependents":["superagent@npm:9.0.2"]}} {"value":"lodash.clone","children":{"ID":"lodash.clone (deprecation)","Issue":"This package is deprecated. Use structuredClone instead.","Severity":"moderate","Vulnerable Versions":"4.5.0","Tree Versions":["4.5.0"],"Dependents":["ioredis@npm:3.2.2"]}} {"value":"lodash.pick","children":{"ID":"lodash.pick (deprecation)","Issue":"This package is deprecated. Use destructuring assignment syntax instead.","Severity":"moderate","Vulnerable Versions":"3.1.0","Tree Versions":["3.1.0"],"Dependents":["ioredis@npm:3.2.2"]}} -{"value":"on-headers","children":{"ID":1106485,"Issue":"on-headers is vulnerable to http response header manipulation","URL":"https://github.com/advisories/GHSA-76c9-3jph-rj3q","Severity":"low","Vulnerable Versions":"<1.1.0","Tree Versions":["1.0.2"],"Dependents":["connect-timeout@npm:1.9.0"]}} -{"value":"superagent","children":{"ID":"superagent (deprecation)","Issue":"Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net","Severity":"moderate","Vulnerable Versions":"9.0.2","Tree Versions":["9.0.2"],"Dependents":["@hmcts/nodejs-healthcheck@npm:1.8.5"]}} diff --git a/yarn.lock b/yarn.lock index 9045d179..f1e33a60 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,16 @@ __metadata: version: 8 cacheKey: 10 +"@ampproject/remapping@npm:^2.2.0": + version: 2.3.0 + resolution: "@ampproject/remapping@npm:2.3.0" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10/f3451525379c68a73eb0a1e65247fbf28c0cccd126d93af21c75fceff77773d43c0d4a2d51978fb131aff25b5f2cb41a9fe48cc296e61ae65e679c4f6918b0ab + languageName: node + linkType: hard + "@azure/abort-controller@npm:^2.0.0": version: 2.1.2 resolution: "@azure/abort-controller@npm:2.1.2" @@ -85,203 +95,205 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/code-frame@npm:7.8.3" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/code-frame@npm:7.27.1" dependencies: - "@babel/highlight": "npm:^7.8.3" - checksum: 10/bf152635a1160a570cd7b0199d8d94ad04b4114b2c3a3afb1d97c9b0c490c82928ac189f6a9e431b709c801649482845fbf14976eee5f3f1e68c8b6f5c65629a + "@babel/helper-validator-identifier": "npm:^7.27.1" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10/721b8a6e360a1fa0f1c9fe7351ae6c874828e119183688b533c477aa378f1010f37cc9afbfc4722c686d1f5cdd00da02eab4ba7278a0c504fa0d7a321dcd4fdf + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.27.2": + version: 7.28.0 + resolution: "@babel/compat-data@npm:7.28.0" + checksum: 10/1a56a5e48c7259f72cc4329adeca38e72fd650ea09de267ea4aa070e3da91e5c265313b6656823fff77d64a8bab9554f276c66dade9355fdc0d8604deea015aa languageName: node linkType: hard "@babel/core@npm:^7.7.5": - version: 7.8.6 - resolution: "@babel/core@npm:7.8.6" - dependencies: - "@babel/code-frame": "npm:^7.8.3" - "@babel/generator": "npm:^7.8.6" - "@babel/helpers": "npm:^7.8.4" - "@babel/parser": "npm:^7.8.6" - "@babel/template": "npm:^7.8.6" - "@babel/traverse": "npm:^7.8.6" - "@babel/types": "npm:^7.8.6" - convert-source-map: "npm:^1.7.0" + version: 7.28.0 + resolution: "@babel/core@npm:7.28.0" + dependencies: + "@ampproject/remapping": "npm:^2.2.0" + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.28.0" + "@babel/helper-compilation-targets": "npm:^7.27.2" + "@babel/helper-module-transforms": "npm:^7.27.3" + "@babel/helpers": "npm:^7.27.6" + "@babel/parser": "npm:^7.28.0" + "@babel/template": "npm:^7.27.2" + "@babel/traverse": "npm:^7.28.0" + "@babel/types": "npm:^7.28.0" + convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" - gensync: "npm:^1.0.0-beta.1" - json5: "npm:^2.1.0" - lodash: "npm:^4.17.13" - resolve: "npm:^1.3.2" - semver: "npm:^5.4.1" - source-map: "npm:^0.5.0" - checksum: 10/6946004107b271d4ed7557176ab9e203c3b1c8061fba8a39720d5fa744aca3382313a5a0c5358cc1950986bbb73a3b13826c611177e63bfee585b8d88fe0013d + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10/1c86eec8d76053f7b1c5f65296d51d7b8ac00f80d169ff76d3cd2e7d85ab222eb100d40cc3314f41b96c8cc06e9abab21c63d246161f0f3f70ef14c958419c33 languageName: node linkType: hard -"@babel/generator@npm:^7.8.6": - version: 7.8.6 - resolution: "@babel/generator@npm:7.8.6" +"@babel/generator@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/generator@npm:7.28.0" dependencies: - "@babel/types": "npm:^7.8.6" - jsesc: "npm:^2.5.1" - lodash: "npm:^4.17.13" - source-map: "npm:^0.5.0" - checksum: 10/d02347c08a6348b9ad0dee2c4eb9ef678030d99cbe402bc5a41004eccd88258b3f7b0ea5e5a167b150e0717c26581a75776e93c12cb068ea809f87a3a0693fd0 + "@babel/parser": "npm:^7.28.0" + "@babel/types": "npm:^7.28.0" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10/064c5ba4c07ecd7600377bd0022d5f6bdb3b35e9ff78d9378f6bd1e656467ca902c091647222ab2f0d2967f6d6c0ca33157d37dd9b1c51926c9b0e1527ab9b92 languageName: node linkType: hard -"@babel/helper-function-name@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/helper-function-name@npm:7.8.3" +"@babel/helper-compilation-targets@npm:^7.27.2": + version: 7.27.2 + resolution: "@babel/helper-compilation-targets@npm:7.27.2" dependencies: - "@babel/helper-get-function-arity": "npm:^7.8.3" - "@babel/template": "npm:^7.8.3" - "@babel/types": "npm:^7.8.3" - checksum: 10/1a65e8bd9f1c4d64b0f2d1abf106b07d69363b97274647f51880a52d8ca778e69c18d12fca9b9193f6758a8b7fe1174f167ae5b7ed71d0f20dc4b101c8ec5cf3 + "@babel/compat-data": "npm:^7.27.2" + "@babel/helper-validator-option": "npm:^7.27.1" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10/bd53c30a7477049db04b655d11f4c3500aea3bcbc2497cf02161de2ecf994fec7c098aabbcebe210ffabc2ecbdb1e3ffad23fb4d3f18723b814f423ea1749fe8 languageName: node linkType: hard -"@babel/helper-get-function-arity@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/helper-get-function-arity@npm:7.8.3" - dependencies: - "@babel/types": "npm:^7.8.3" - checksum: 10/f36d939bc565576f47c546ee636a37d0597ebdde30182db974cf47b27d4ee3a72a53233e45bdb57dac306ff5b03a2083d9d2fa8291d95d93bfe4f6213a6901e2 +"@babel/helper-globals@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/helper-globals@npm:7.28.0" + checksum: 10/91445f7edfde9b65dcac47f4f858f68dc1661bf73332060ab67ad7cc7b313421099a2bfc4bda30c3db3842cfa1e86fffbb0d7b2c5205a177d91b22c8d7d9cb47 languageName: node linkType: hard -"@babel/helper-split-export-declaration@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/helper-split-export-declaration@npm:7.8.3" +"@babel/helper-module-imports@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-module-imports@npm:7.27.1" dependencies: - "@babel/types": "npm:^7.8.3" - checksum: 10/a8b5ce6d309002ef85f1514346f3929653c7319f40d98b7d56014a26b7c8b7517cabca12007c71bda513d0f1a0b7548afe9646ee269cbad2b7e7e43455fa0eef + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10/58e792ea5d4ae71676e0d03d9fef33e886a09602addc3bd01388a98d87df9fcfd192968feb40ac4aedb7e287ec3d0c17b33e3ecefe002592041a91d8a1998a8d languageName: node linkType: hard -"@babel/helpers@npm:^7.8.4": - version: 7.8.4 - resolution: "@babel/helpers@npm:7.8.4" +"@babel/helper-module-transforms@npm:^7.27.3": + version: 7.27.3 + resolution: "@babel/helper-module-transforms@npm:7.27.3" dependencies: - "@babel/template": "npm:^7.8.3" - "@babel/traverse": "npm:^7.8.4" - "@babel/types": "npm:^7.8.3" - checksum: 10/1e7e32c97f3d86b777109477df91d5efaeaef362b6d48d9334b2d8cc3ca440f78931f1fdfb1cbc2a5ce15814031d13e12ab55d6f5c42ed57b81f913b9ba64b78 + "@babel/helper-module-imports": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.3" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10/47abc90ceb181b4bdea9bf1717adf536d1b5e5acb6f6d8a7a4524080318b5ca8a99e6d58677268c596bad71077d1d98834d2c3815f2443e6d3f287962300f15d languageName: node linkType: hard -"@babel/highlight@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/highlight@npm:7.8.3" - dependencies: - chalk: "npm:^2.0.0" - esutils: "npm:^2.0.2" - js-tokens: "npm:^4.0.0" - checksum: 10/25e5d54b6c3ef83891af01988e50bf17dc785739c48cf66456c5c274203c39ab68c95b387018fc1b37c8feb199c1f489dae266ee44e45e36fd8a30e21e2822fa +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 10/0ae29cc2005084abdae2966afdb86ed14d41c9c37db02c3693d5022fba9f5d59b011d039380b8e537c34daf117c549f52b452398f576e908fb9db3c7abbb3a00 languageName: node linkType: hard -"@babel/parser@npm:^7.7.5, @babel/parser@npm:^7.8.6": - version: 7.8.6 - resolution: "@babel/parser@npm:7.8.6" - bin: - parser: ./bin/babel-parser.js - checksum: 10/58dd96b56245f546e49c25051d5238e3accbf386c42fe3ebde32c09fab60d749e63eef5715e5e6452ce5b76537409d29b3be00da479e606038c6d94fc87d00b5 +"@babel/helper-validator-identifier@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-identifier@npm:7.27.1" + checksum: 10/75041904d21bdc0cd3b07a8ac90b11d64cd3c881e89cb936fa80edd734bf23c35e6bd1312611e8574c4eab1f3af0f63e8a5894f4699e9cfdf70c06fcf4252320 languageName: node linkType: hard -"@babel/template@npm:^7.7.4, @babel/template@npm:^7.8.3, @babel/template@npm:^7.8.6": - version: 7.8.6 - resolution: "@babel/template@npm:7.8.6" - dependencies: - "@babel/code-frame": "npm:^7.8.3" - "@babel/parser": "npm:^7.8.6" - "@babel/types": "npm:^7.8.6" - checksum: 10/96360ebce78e9f575e0d3814a69aa8a71a7985c33876bf58378970912191459c4f7ce960366fdf9c52498c74a0bf1d9e6b136880002d64b85d925942ecfebb04 +"@babel/helper-validator-option@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-option@npm:7.27.1" + checksum: 10/db73e6a308092531c629ee5de7f0d04390835b21a263be2644276cb27da2384b64676cab9f22cd8d8dbd854c92b1d7d56fc8517cf0070c35d1c14a8c828b0903 languageName: node linkType: hard -"@babel/traverse@npm:^7.7.4, @babel/traverse@npm:^7.8.4, @babel/traverse@npm:^7.8.6": - version: 7.8.6 - resolution: "@babel/traverse@npm:7.8.6" +"@babel/helpers@npm:^7.27.6": + version: 7.28.2 + resolution: "@babel/helpers@npm:7.28.2" dependencies: - "@babel/code-frame": "npm:^7.8.3" - "@babel/generator": "npm:^7.8.6" - "@babel/helper-function-name": "npm:^7.8.3" - "@babel/helper-split-export-declaration": "npm:^7.8.3" - "@babel/parser": "npm:^7.8.6" - "@babel/types": "npm:^7.8.6" - debug: "npm:^4.1.0" - globals: "npm:^11.1.0" - lodash: "npm:^4.17.13" - checksum: 10/ee8f85a4747e835415984dd05958c667438845d2ab40c94f89f8d102097898983e2ff30b81dcbc99604d0d5d571e9d0f10d98a06456cc365bcfa837270f44f64 + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.28.2" + checksum: 10/09fd7965e83d4777a4331a082677a1a2261cec451bf3307cb0fb62b2d32c83d55fb1cac494a5dab5c6ad9da459883b8d4e49142812b10ef3e36b54022b2de3a4 languageName: node linkType: hard -"@babel/types@npm:^7.8.3, @babel/types@npm:^7.8.6": - version: 7.8.6 - resolution: "@babel/types@npm:7.8.6" +"@babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/parser@npm:7.28.0" dependencies: - esutils: "npm:^2.0.2" - lodash: "npm:^4.17.13" - to-fast-properties: "npm:^2.0.0" - checksum: 10/28d63de2e47953a2879ee63de67b13f895766c9906ee1252b763df7d786e99c3b8617766ec17ce3ac8b03af3e0631137e22b8f4f19b4ca10ac9e93b56e92caa6 + "@babel/types": "npm:^7.28.0" + bin: + parser: ./bin/babel-parser.js + checksum: 10/2c14a0d2600bae9ab81924df0a85bbd34e427caa099c260743f7c6c12b2042e743e776043a0d1a2573229ae648f7e66a80cfb26fc27e2a9eb59b55932d44c817 languageName: node linkType: hard -"@gar/promisify@npm:^1.1.3": - version: 1.1.3 - resolution: "@gar/promisify@npm:1.1.3" - checksum: 10/052dd232140fa60e81588000cbe729a40146579b361f1070bce63e2a761388a22a16d00beeffc504bd3601cb8e055c57b21a185448b3ed550cf50716f4fd442e +"@babel/template@npm:^7.27.2": + version: 7.27.2 + resolution: "@babel/template@npm:7.27.2" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/parser": "npm:^7.27.2" + "@babel/types": "npm:^7.27.1" + checksum: 10/fed15a84beb0b9340e5f81566600dbee5eccd92e4b9cc42a944359b1aa1082373391d9d5fc3656981dff27233ec935d0bc96453cf507f60a4b079463999244d8 languageName: node linkType: hard -"@hapi/address@npm:^4.1.0": - version: 4.1.0 - resolution: "@hapi/address@npm:4.1.0" +"@babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.27.3, @babel/traverse@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/traverse@npm:7.28.0" dependencies: - "@hapi/hoek": "npm:^9.0.0" - checksum: 10/89da7cdcbaef078c6aec69ec167f12bbc3ba61f339678b5cf566b722cafe3e8f63af535a0dc95ba1c1fa3f125ead84821287c1e543b99cd215f58ef718f7da5b + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.28.0" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/parser": "npm:^7.28.0" + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.28.0" + debug: "npm:^4.3.1" + checksum: 10/c1c24b12b6cb46241ec5d11ddbd2989d6955c282715cbd8ee91a09fe156b3bdb0b88353ac33329c2992113e3dfb5198f616c834f8805bb3fa85da1f864bec5f3 languageName: node linkType: hard -"@hapi/formula@npm:^2.0.0": - version: 2.0.0 - resolution: "@hapi/formula@npm:2.0.0" - checksum: 10/13c1e066f237bfa2410ff19686e0ac08d48dceffcd51903acb2859644bddf4b313dc4bcc785a1f89690ec7d7e0eceb90c26473335117a09cc5b8d3202868f508 +"@babel/types@npm:^7.27.1, @babel/types@npm:^7.28.0, @babel/types@npm:^7.28.2": + version: 7.28.2 + resolution: "@babel/types@npm:7.28.2" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + checksum: 10/a8de404a2e3109651f346d892dc020ce2c82046068f4ce24de7f487738dfbfa7bd716b35f1dcd6d6c32dde96208dc74a56b7f56a2c0bcb5af0ddc56cbee13533 languageName: node linkType: hard -"@hapi/hoek@npm:^9.0.0": +"@hapi/hoek@npm:^9.0.0, @hapi/hoek@npm:^9.3.0": version: 9.3.0 resolution: "@hapi/hoek@npm:9.3.0" checksum: 10/ad83a223787749f3873bce42bd32a9a19673765bf3edece0a427e138859ff729469e68d5fdf9ff6bbee6fb0c8e21bab61415afa4584f527cfc40b59ea1957e70 languageName: node linkType: hard -"@hapi/pinpoint@npm:^2.0.0": - version: 2.0.0 - resolution: "@hapi/pinpoint@npm:2.0.0" - checksum: 10/a0f78856a2270f7cb2eb211b106e0c15b700c379171d1f322be450fddbc606cc6406621ed6c3b6784c94a5ea9f738dc583dcc7308ec525f7c4b722632d203e2f - languageName: node - linkType: hard - -"@hapi/topo@npm:^5.0.0": - version: 5.0.0 - resolution: "@hapi/topo@npm:5.0.0" +"@hapi/topo@npm:^5.1.0": + version: 5.1.0 + resolution: "@hapi/topo@npm:5.1.0" dependencies: "@hapi/hoek": "npm:^9.0.0" - checksum: 10/3cb6c5d655b7fec01ac301699b908567b13acb8016f00fd82c075a8e7bfbb522eb62d4b1a8c3df542cc23f92b7e51bc2657602d5f04cfe0b080d4827aff1f988 + checksum: 10/084bfa647015f4fd3fdd51fadb2747d09ef2f5e1443d6cbada2988b0c88494f85edf257ec606c790db146ac4e34ff57f3fcb22e3299b8e06ed5c87ba7583495c languageName: node linkType: hard "@hmcts/nodejs-healthcheck@npm:^1.8.0": - version: 1.8.0 - resolution: "@hmcts/nodejs-healthcheck@npm:1.8.0" + version: 1.8.6 + resolution: "@hmcts/nodejs-healthcheck@npm:1.8.6" dependencies: "@hmcts/nodejs-logging": "npm:^4.0.4" - js-yaml: "npm:^3.8.4" - superagent: "npm:7" - checksum: 10/9a910ce9662fa357c3a9db9cdc3a677f3035ab1a23976239623414a3cf9ce66fdb49246cb9a67295c1789999daefd18adecda012306d4ec0ef2d58e7fe5e9b68 + js-yaml: "npm:^4.0.0" + superagent: "npm:^10.2.2" + checksum: 10/baa0fcb6885938014465297feb4d5b1215874458fd24219202f87449aa5e2e0e25549405af0db7103f784337d98aa60313d86f6c23bb448001bb5175d5f2e48d languageName: node linkType: hard @@ -307,22 +319,80 @@ __metadata: languageName: node linkType: hard +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: "npm:^5.1.2" + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: "npm:^7.0.1" + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: "npm:^8.1.0" + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 10/e9ed5fd27c3aec1095e3a16e0c0cf148d1fee55a38665c35f7b3f86a9b5d00d042ddaabc98e8a1cb7463b9378c15f22a94eb35e99469c201453eb8375191f243 + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10/4412e9e6713c89c1e66d80bb0bb5a2a93192f10477623a27d08f228ba0316bb880affabc5bfe7f838f58a34d26c2c190da726e576cdfc18c49a72e89adabdcf5 + languageName: node + linkType: hard + "@istanbuljs/load-nyc-config@npm:^1.0.0": - version: 1.0.0 - resolution: "@istanbuljs/load-nyc-config@npm:1.0.0" + version: 1.1.0 + resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" dependencies: camelcase: "npm:^5.3.1" find-up: "npm:^4.1.0" + get-package-type: "npm:^0.1.0" js-yaml: "npm:^3.13.1" resolve-from: "npm:^5.0.0" - checksum: 10/2f5f58b6e94c41203475534e2525e3aa4a3bd3ef7955b09bdec3495c69552a992e4763f532377ec0ae1537aa7d4dd6717ddc4efcdd05227d3487b7cb1db12055 + checksum: 10/b000a5acd8d4fe6e34e25c399c8bdbb5d3a202b4e10416e17bfc25e12bab90bb56d33db6089ae30569b52686f4b35ff28ef26e88e21e69821d2b85884bd055b8 languageName: node linkType: hard "@istanbuljs/schema@npm:^0.1.2": - version: 0.1.2 - resolution: "@istanbuljs/schema@npm:0.1.2" - checksum: 10/e4a7fffc72fb2cfe2edfee8a09f68b2da18b1ab328a29d8be2933681f9e36103f0b083a5ae07129f4de26296eb7c2a1cc144cfea6cdb47f871a69766947144d3 + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 10/a9b1e49acdf5efc2f5b2359f2df7f90c5c725f2656f16099e8b2cd3a000619ecca9fc48cf693ba789cf0fd989f6e0df6a22bc05574be4223ecdbb7997d04384b + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.12 + resolution: "@jridgewell/gen-mapping@npm:0.3.12" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10/151667531566417a940d4dd0a319724979f7a90b9deb9f1617344e1183887d78c835bc1a9209c1ee10fc8a669cdd7ac8120a43a2b6bc8d0d5dd18a173059ff4b + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10/97106439d750a409c22c8bff822d648f6a71f3aa9bc8e5129efdc36343cd3096ddc4eeb1c62d2fe48e9bdd4db37b05d4646a17114ecebd3bbcacfa2de51c3c1d + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": + version: 1.5.4 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.4" + checksum: 10/f677787f52224c6c971a7a41b7a074243240a6917fa75eceb9f7a442866f374fb0522b505e0496ee10a650c5936727e76d11bf36a6d0ae9e6c3b726c9e284cc7 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.28": + version: 0.3.29 + resolution: "@jridgewell/trace-mapping@npm:0.3.29" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10/64e1ce0dc3a9e56b0118eaf1b2f50746fd59a36de37516cc6855b5370d5f367aa8229e1237536d738262e252c70ee229619cb04e3f3b822146ee3eb1b7ab297f languageName: node linkType: hard @@ -340,23 +410,25 @@ __metadata: languageName: node linkType: hard -"@npmcli/fs@npm:^2.1.0": - version: 2.1.2 - resolution: "@npmcli/fs@npm:2.1.2" +"@npmcli/agent@npm:^3.0.0": + version: 3.0.0 + resolution: "@npmcli/agent@npm:3.0.0" dependencies: - "@gar/promisify": "npm:^1.1.3" - semver: "npm:^7.3.5" - checksum: 10/c5d4dfee80de2236e1e4ed595d17e217aada72ebd8215183fc46096fa010f583dd2aaaa486758de7cc0b89440dbc31cfe8b276269d75d47af35c716e896f78ec + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^10.0.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10/775c9a7eb1f88c195dfb3bce70c31d0fe2a12b28b754e25c08a3edb4bc4816bfedb7ac64ef1e730579d078ca19dacf11630e99f8f3c3e0fd7b23caa5fd6d30a6 languageName: node linkType: hard -"@npmcli/move-file@npm:^2.0.0": - version: 2.0.1 - resolution: "@npmcli/move-file@npm:2.0.1" +"@npmcli/fs@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/fs@npm:4.0.0" dependencies: - mkdirp: "npm:^1.0.4" - rimraf: "npm:^3.0.2" - checksum: 10/52dc02259d98da517fae4cb3a0a3850227bdae4939dda1980b788a7670636ca2b4a01b58df03dd5f65c1e3cb70c50fa8ce5762b582b3f499ec30ee5ce1fd9380 + semver: "npm:^7.3.5" + checksum: 10/405c4490e1ff11cf299775449a3c254a366a4b1ffc79d87159b0ee7d5558ac9f6a2f8c0735fd6ff3873cef014cb1a44a5f9127cb6a1b2dbc408718cca9365b5a languageName: node linkType: hard @@ -436,9 +508,9 @@ __metadata: linkType: hard "@opentelemetry/semantic-conventions@npm:^1.19.0": - version: 1.34.0 - resolution: "@opentelemetry/semantic-conventions@npm:1.34.0" - checksum: 10/1892b4cc69c9e00456c809604a980e32696563e96463ff5f9d07e72d5aca73836a7378090509f28f54445ac6e072d2343a888c9d64d9ce287198e899082ff7aa + version: 1.36.0 + resolution: "@opentelemetry/semantic-conventions@npm:1.36.0" + checksum: 10/f1939066c30147348b326840d67cc48e73072b762f2e2af5c3ea894268d64c62fc4e73fad49a72ed4a52a543b2fa0824c969a676e658ae727f75182f52104007 languageName: node linkType: hard @@ -451,6 +523,43 @@ __metadata: languageName: node linkType: hard +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 10/115e8ceeec6bc69dff2048b35c0ab4f8bbee12d8bb6c1f4af758604586d802b6e669dcb02dda61d078de42c2b4ddce41b3d9e726d7daa6b4b850f4adbf7333ff + languageName: node + linkType: hard + +"@rtsao/scc@npm:^1.1.0": + version: 1.1.0 + resolution: "@rtsao/scc@npm:1.1.0" + checksum: 10/17d04adf404e04c1e61391ed97bca5117d4c2767a76ae3e879390d6dec7b317fcae68afbf9e98badee075d0b64fa60f287729c4942021b4d19cd01db77385c01 + languageName: node + linkType: hard + +"@sideway/address@npm:^4.1.5": + version: 4.1.5 + resolution: "@sideway/address@npm:4.1.5" + dependencies: + "@hapi/hoek": "npm:^9.0.0" + checksum: 10/c4c73ac0339504f34e016d3a687118e7ddf197c1c968579572123b67b230be84caa705f0f634efdfdde7f2e07a6e0224b3c70665dc420d8bc95bf400cfc4c998 + languageName: node + linkType: hard + +"@sideway/formula@npm:^3.0.1": + version: 3.0.1 + resolution: "@sideway/formula@npm:3.0.1" + checksum: 10/8d3ee7f80df4e5204b2cbe92a2a711ca89684965a5c9eb3b316b7051212d3522e332a65a0bb2a07cc708fcd1d0b27fcb30f43ff0bcd5089d7006c7160a89eefe + languageName: node + linkType: hard + +"@sideway/pinpoint@npm:^2.0.0": + version: 2.0.0 + resolution: "@sideway/pinpoint@npm:2.0.0" + checksum: 10/1ed21800128b2b23280ba4c9db26c8ff6142b97a8683f17639fd7f2128aa09046461574800b30fb407afc5b663c2331795ccf3b654d4b38fa096e41a5c786bf8 + languageName: node + linkType: hard + "@sinonjs/commons@npm:^2.0.0": version: 2.0.0 resolution: "@sinonjs/commons@npm:2.0.0" @@ -497,67 +606,48 @@ __metadata: linkType: hard "@socket.io/component-emitter@npm:~3.1.0": - version: 3.1.0 - resolution: "@socket.io/component-emitter@npm:3.1.0" - checksum: 10/db069d95425b419de1514dffe945cc439795f6a8ef5b9465715acf5b8b50798e2c91b8719cbf5434b3fe7de179d6cdcd503c277b7871cb3dd03febb69bdd50fa - languageName: node - linkType: hard - -"@tootallnate/once@npm:2": - version: 2.0.0 - resolution: "@tootallnate/once@npm:2.0.0" - checksum: 10/ad87447820dd3f24825d2d947ebc03072b20a42bfc96cbafec16bff8bbda6c1a81fcb0be56d5b21968560c5359a0af4038a68ba150c3e1694fe4c109a063bed8 + version: 3.1.2 + resolution: "@socket.io/component-emitter@npm:3.1.2" + checksum: 10/89888f00699eb34e3070624eb7b8161fa29f064aeb1389a48f02195d55dd7c52a504e52160016859f6d6dffddd54324623cdd47fd34b3d46f9ed96c18c456edc languageName: node linkType: hard "@types/chai@npm:4": - version: 4.2.10 - resolution: "@types/chai@npm:4.2.10" - checksum: 10/40d1af4d61de193f5745c73eb31e6712d2b5e8e3c1ef4809cce21959a71bbff60f20a0a14339335874267566ad1fdf731effa82a68f92074a1c07b0b18503840 - languageName: node - linkType: hard - -"@types/color-name@npm:^1.1.1": - version: 1.1.1 - resolution: "@types/color-name@npm:1.1.1" - checksum: 10/73e0e230a6708210bcfc040f3bd3d7c4c0bf5ff7338e8e497cca3d05d20a0c29fb74a7bde0fa2cdf3322d4b1ffd5194c456712908974ae52d56c0d060ca55ae2 - languageName: node - linkType: hard - -"@types/cookie@npm:^0.4.1": - version: 0.4.1 - resolution: "@types/cookie@npm:0.4.1" - checksum: 10/427c9220217d3d74f3e5d53d68cd39502f3bbebdb1af4ecf0d05076bcbe9ddab299ad6369fe0f517389296ba4ca49ddf9a8c22f68e5e9eb8ae6d0076cfab90b2 + version: 4.3.20 + resolution: "@types/chai@npm:4.3.20" + checksum: 10/94fd87036fb63f62c79caf58ccaec88e23cc109e4d41607d83adc609acd6b24eabc345feb7850095a53f76f99c470888251da9bd1b90849c8b2b5a813296bb19 languageName: node linkType: hard "@types/cookiejar@npm:*": - version: 2.1.1 - resolution: "@types/cookiejar@npm:2.1.1" - checksum: 10/5e658b7bef59a0a35fde6ad08932d4b581684ba0e53f977fb54bf7d26bc83abf7d27d930f0af51eef1fe383477e53d217b7bba7c848439b10b998eb7572ce22d + version: 2.1.5 + resolution: "@types/cookiejar@npm:2.1.5" + checksum: 10/04d5990e87b6387532d15a87d9ec9b2eb783039291193863751dcfd7fc723a3b3aa30ce4c06b03975cba58632e933772f1ff031af23eaa3ac7f94e71afa6e073 languageName: node linkType: hard "@types/cors@npm:^2.8.12": - version: 2.8.13 - resolution: "@types/cors@npm:2.8.13" + version: 2.8.19 + resolution: "@types/cors@npm:2.8.19" dependencies: "@types/node": "npm:*" - checksum: 10/7ef197ea19d2e5bf1313b8416baa6f3fd6dd887fd70191da1f804f557395357dafd8bc8bed0ac60686923406489262a7c8a525b55748f7b2b8afa686700de907 + checksum: 10/9545cc532c9218754443f48a0c98c1a9ba4af1fe54a3425c95de75ff3158147bb39e666cb7c6bf98cc56a9c6dc7b4ce5b2cbdae6b55d5942e50c81b76ed6b825 languageName: node linkType: hard -"@types/node@npm:*": - version: 13.7.7 - resolution: "@types/node@npm:13.7.7" - checksum: 10/dae2826eafdab2519a3b76caef8188e33fdf02b271c5b5b472cd7b863d2cf05e708b87e5050a90bcb646efc57febd5df8226286c658a4412a5772f979872ed3d +"@types/json5@npm:^0.0.29": + version: 0.0.29 + resolution: "@types/json5@npm:0.0.29" + checksum: 10/4e5aed58cabb2bbf6f725da13421aa50a49abb6bc17bfab6c31b8774b073fa7b50d557c61f961a09a85f6056151190f8ac95f13f5b48136ba5841f7d4484ec56 languageName: node linkType: hard -"@types/node@npm:>=10.0.0": - version: 18.15.0 - resolution: "@types/node@npm:18.15.0" - checksum: 10/0183b505fce90af7b80d803561126d06098497b592329dfe02625321767998e551a3034a6336f69a0cfab755a7e8cb841e39d16f49181d8d7c205fb99b887063 +"@types/node@npm:*, @types/node@npm:>=10.0.0": + version: 24.1.0 + resolution: "@types/node@npm:24.1.0" + dependencies: + undici-types: "npm:~7.8.0" + checksum: 10/02c3d91e1407a93a2363f97a245475fa0f6209d3f3e6ba9fdaabe65389e3e078f648da81b8738125dec6b6bf98c50fb928f36f4e72b8b015817bc21479a868c2 languageName: node linkType: hard @@ -568,13 +658,13 @@ __metadata: languageName: node linkType: hard -"@types/superagent@npm:^3.8.3": - version: 3.8.7 - resolution: "@types/superagent@npm:3.8.7" +"@types/superagent@npm:4.1.13": + version: 4.1.13 + resolution: "@types/superagent@npm:4.1.13" dependencies: "@types/cookiejar": "npm:*" "@types/node": "npm:*" - checksum: 10/1809da14ba3024446ccbc23b0d1c12aced1cf7daf34f71ec391f2f9d503ca34600d23461448471e92a308745dd2f4e046d14a9be655b9cb780e508e253d37dd5 + checksum: 10/20734256b4030be4a8cf4f5906c03f3ac92d42944efe263a13691ab6c79c785d43248644bb1f6af4cc889a9ae80fa7e951184118a6131fc85c9875438eb88a51 languageName: node linkType: hard @@ -589,20 +679,20 @@ __metadata: languageName: node linkType: hard -"abbrev@npm:^1.0.0": - version: 1.1.1 - resolution: "abbrev@npm:1.1.1" - checksum: 10/2d882941183c66aa665118bafdab82b7a177e9add5eb2776c33e960a4f3c89cff88a1b38aba13a456de01d0dd9d66a8bea7c903268b21ea91dd1097e1e2e8243 +"abbrev@npm:^3.0.0": + version: 3.0.1 + resolution: "abbrev@npm:3.0.1" + checksum: 10/ebd2c149dda6f543b66ce3779ea612151bb3aa9d0824f169773ee9876f1ca5a4e0adbcccc7eed048c04da7998e1825e2aa76fcca92d9e67dea50ac2b0a58dc2e languageName: node linkType: hard -"accepts@npm:^1.3.7": - version: 1.3.7 - resolution: "accepts@npm:1.3.7" +"accepts@npm:^1.3.7, accepts@npm:~1.3.4": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" dependencies: - mime-types: "npm:~2.1.24" - negotiator: "npm:0.6.2" - checksum: 10/599aa3cc775a2b4fb393f666be41ba7f3da4f46ba8bb422908a68042d3d59ef71f1631f1657b22842fe53f4cd562fc02f8bb42cfde6af0cec3a9b1f9508843cc + mime-types: "npm:~2.1.34" + negotiator: "npm:0.6.3" + checksum: 10/67eaaa90e2917c58418e7a9b89392002d2b1ccd69bcca4799135d0c632f3b082f23f4ae4ddeedbced5aa59bcc7bdf4699c69ebed4593696c922462b7bc5744d6 languageName: node linkType: hard @@ -616,16 +706,6 @@ __metadata: languageName: node linkType: hard -"accepts@npm:~1.3.4": - version: 1.3.8 - resolution: "accepts@npm:1.3.8" - dependencies: - mime-types: "npm:~2.1.34" - negotiator: "npm:0.6.3" - checksum: 10/67eaaa90e2917c58418e7a9b89392002d2b1ccd69bcca4799135d0c632f3b082f23f4ae4ddeedbced5aa59bcc7bdf4699c69ebed4593696c922462b7bc5744d6 - languageName: node - linkType: hard - "acorn-import-attributes@npm:^1.9.5": version: 1.9.5 resolution: "acorn-import-attributes@npm:1.9.5" @@ -636,20 +716,20 @@ __metadata: linkType: hard "acorn-jsx@npm:^5.2.0": - version: 5.2.0 - resolution: "acorn-jsx@npm:5.2.0" + version: 5.3.2 + resolution: "acorn-jsx@npm:5.3.2" peerDependencies: - acorn: ^6.0.0 || ^7.0.0 - checksum: 10/8e630b5834ec94ad2eb4a48c6ff29d7877c4014b5d85d36b93439220c54b699c6393efbf420a0b38d5cc54665bd30c2a66b9e0c950d08a4d1640412bfb0e04bb + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10/d4371eaef7995530b5b5ca4183ff6f062ca17901a6d3f673c9ac011b01ede37e7a1f7f61f8f5cfe709e88054757bb8f3277dc4061087cdf4f2a1f90ccbcdb977 languageName: node linkType: hard -"acorn@npm:^7.1.0": - version: 7.1.1 - resolution: "acorn@npm:7.1.1" +"acorn@npm:^7.1.1": + version: 7.4.1 + resolution: "acorn@npm:7.4.1" bin: acorn: bin/acorn - checksum: 10/24a5201151803bd7fda32e4bb186e6d63eb2cac11353f9a2dc52cd85c8e5024541a52b8b4ad5c7eda4d7e8feafc0779c71179db861cccb23d9987aa56bbcc735 + checksum: 10/8be2a40714756d713dfb62544128adce3b7102c6eb94bc312af196c2cc4af76e5b93079bd66b05e9ca31b35a9b0ce12171d16bc55f366cafdb794fdab9d753ec languageName: node linkType: hard @@ -662,15 +742,6 @@ __metadata: languageName: node linkType: hard -"agent-base@npm:6, agent-base@npm:^6.0.2": - version: 6.0.2 - resolution: "agent-base@npm:6.0.2" - dependencies: - debug: "npm:4" - checksum: 10/21fb903e0917e5cb16591b4d0ef6a028a54b83ac30cd1fca58dece3d4e0990512a8723f9f83130d88a41e2af8b1f7be1386fda3ea2d181bb1a62155e75e95e23 - languageName: node - linkType: hard - "agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": version: 7.1.3 resolution: "agent-base@npm:7.1.3" @@ -678,24 +749,13 @@ __metadata: languageName: node linkType: hard -"agentkeepalive@npm:^4.2.1": - version: 4.2.1 - resolution: "agentkeepalive@npm:4.2.1" - dependencies: - debug: "npm:^4.1.0" - depd: "npm:^1.1.2" - humanize-ms: "npm:^1.2.1" - checksum: 10/63961cba1afa26d708da94159f3b9428d46fdc137b783fbc399b848e750c5e28c97d96839efa8cb3c2d11ecd12dd411298c00d164600212f660e8c55369c9e55 - languageName: node - linkType: hard - "aggregate-error@npm:^3.0.0": - version: 3.0.1 - resolution: "aggregate-error@npm:3.0.1" + version: 3.1.0 + resolution: "aggregate-error@npm:3.1.0" dependencies: clean-stack: "npm:^2.0.0" indent-string: "npm:^4.0.0" - checksum: 10/1f922d00cc51cf9f7f6f729c0b925689ed5a464aefc1fac8309924f622000ee3741d314d864b2d776f9627236ea79daf5a83d093f6b72edc52160571160eff82 + checksum: 10/1101a33f21baa27a2fa8e04b698271e64616b886795fd43c31068c07533c7b3facfcaf4e9e0cab3624bd88f729a592f1c901a1a229c9e490eafce411a8644b79 languageName: node linkType: hard @@ -719,11 +779,11 @@ __metadata: linkType: hard "ansi-escapes@npm:^4.2.1": - version: 4.3.1 - resolution: "ansi-escapes@npm:4.3.1" + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" dependencies: - type-fest: "npm:^0.11.0" - checksum: 10/964adff8777113109cc08e2e053af948a49137c52e86e427dc3cbfb515d0363763a731b96a0ef573531db3f788e1b45322d3cf13a7f4db9029a0f5ba17e9a14a + type-fest: "npm:^0.21.3" + checksum: 10/8661034456193ffeda0c15c8c564a9636b0c04094b7f78bd01517929c17c504090a60f7a75f949f5af91289c264d3e1001d91492c1bd58efc8e100500ce04de2 languageName: node linkType: hard @@ -744,22 +804,28 @@ __metadata: linkType: hard "ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": - version: 4.2.1 - resolution: "ansi-styles@npm:4.2.1" + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" dependencies: - "@types/color-name": "npm:^1.1.1" color-convert: "npm:^2.0.1" - checksum: 10/7c74dbc7ec912b9e45dacbfaa7e2513bea6aa24d5357a0cd3255e7f83ecfc62e1454c77ab150a8df60de700c83c17fbbf040e7c204b4b6fc7aa250c8afcb865f + checksum: 10/b4494dfbfc7e4591b4711a396bd27e540f8153914123dccb4cdbbcb514015ada63a3809f362b9d8d4f6b17a706f1d7bea3c6f974b15fa5ae76b5b502070889ff + languageName: node + linkType: hard + +"ansi-styles@npm:^6.1.0": + version: 6.2.1 + resolution: "ansi-styles@npm:6.2.1" + checksum: 10/70fdf883b704d17a5dfc9cde206e698c16bcd74e7f196ab821511651aee4f9f76c9514bdfa6ca3a27b5e49138b89cb222a28caf3afe4567570139577f991df32 languageName: node linkType: hard "anymatch@npm:~3.1.1": - version: 3.1.1 - resolution: "anymatch@npm:3.1.1" + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" dependencies: normalize-path: "npm:^3.0.0" picomatch: "npm:^2.0.4" - checksum: 10/c951385862bf114807d594bdffccb769bd7219ddc14f24fc135cde075ad2477a97991567b8bb5032d4f279f96897f0c2af6468a350a6c674ac0a5ee3b62a26d6 + checksum: 10/3e044fd6d1d26545f235a9fe4d7a534e2029d8e59fa7fd9f2a6eb21230f6b5380ea1eaf55136e60cbf8e613544b3b766e7a6fa2102e2a3a117505466e3025dc2 languageName: node linkType: hard @@ -797,13 +863,6 @@ __metadata: languageName: node linkType: hard -"aproba@npm:^1.0.3 || ^2.0.0": - version: 2.0.0 - resolution: "aproba@npm:2.0.0" - checksum: 10/c2b9a631298e8d6f3797547e866db642f68493808f5b37cd61da778d5f6ada890d16f668285f7d60bd4fc3b03889bd590ffe62cf81b700e9bb353431238a0a7b - languageName: node - linkType: hard - "archy@npm:^1.0.0": version: 1.0.0 resolution: "archy@npm:1.0.0" @@ -811,16 +870,6 @@ __metadata: languageName: node linkType: hard -"are-we-there-yet@npm:^3.0.0": - version: 3.0.1 - resolution: "are-we-there-yet@npm:3.0.1" - dependencies: - delegates: "npm:^1.0.0" - readable-stream: "npm:^3.6.0" - checksum: 10/390731720e1bf9ed5d0efc635ea7df8cbc4c90308b0645a932f06e8495a0bf1ecc7987d3b97e805f62a17d6c4b634074b25200aa4d149be2a7b17250b9744bc4 - languageName: node - linkType: hard - "argparse@npm:^1.0.7": version: 1.0.10 resolution: "argparse@npm:1.0.10" @@ -830,24 +879,99 @@ __metadata: languageName: node linkType: hard -"array-includes@npm:^3.0.3": - version: 3.1.1 - resolution: "array-includes@npm:3.1.1" +"array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "array-buffer-byte-length@npm:1.0.2" dependencies: - define-properties: "npm:^1.1.3" - es-abstract: "npm:^1.17.0" - is-string: "npm:^1.0.5" - checksum: 10/212caf79a02b758d087b33cb171c64754a73d59e0084bf67e5208df665f4ab0a1c771caaac205865d56dbc7d099ddeb48ce80bdc7f506db8e47dd3e2019841a3 + call-bound: "npm:^1.0.3" + is-array-buffer: "npm:^3.0.5" + checksum: 10/0ae3786195c3211b423e5be8dd93357870e6fb66357d81da968c2c39ef43583ef6eece1f9cb1caccdae4806739c65dea832b44b8593414313cd76a89795fca63 languageName: node linkType: hard -"array.prototype.flat@npm:^1.2.1": - version: 1.2.3 - resolution: "array.prototype.flat@npm:1.2.3" +"array-includes@npm:^3.1.9": + version: 3.1.9 + resolution: "array-includes@npm:3.1.9" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.24.0" + es-object-atoms: "npm:^1.1.1" + get-intrinsic: "npm:^1.3.0" + is-string: "npm:^1.1.1" + math-intrinsics: "npm:^1.1.0" + checksum: 10/8bfe9a58df74f326b4a76b04ee05c13d871759e888b4ee8f013145297cf5eb3c02cfa216067ebdaac5d74eb9763ac5cad77cdf2773b8ab475833701e032173aa + languageName: node + linkType: hard + +"array.prototype.findlastindex@npm:^1.2.6": + version: 1.2.6 + resolution: "array.prototype.findlastindex@npm:1.2.6" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.9" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + es-shim-unscopables: "npm:^1.1.0" + checksum: 10/5ddb6420e820bef6ddfdcc08ce780d0fd5e627e97457919c27e32359916de5a11ce12f7c55073555e503856618eaaa70845d6ca11dcba724766f38eb1c22f7a2 + languageName: node + linkType: hard + +"array.prototype.flat@npm:^1.3.3": + version: 1.3.3 + resolution: "array.prototype.flat@npm:1.3.3" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10/f9b992fa0775d8f7c97abc91eb7f7b2f0ed8430dd9aeb9fdc2967ac4760cdd7fc2ef7ead6528fef40c7261e4d790e117808ce0d3e7e89e91514d4963a531cd01 + languageName: node + linkType: hard + +"array.prototype.flatmap@npm:^1.3.3": + version: 1.3.3 + resolution: "array.prototype.flatmap@npm:1.3.3" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10/473534573aa4b37b1d80705d0ce642f5933cccf5617c9f3e8a56686e9815ba93d469138e86a1f25d2fe8af999c3d24f54d703ec1fc2db2e6778d46d0f4ac951e + languageName: node + linkType: hard + +"array.prototype.reduce@npm:^1.0.6": + version: 1.0.8 + resolution: "array.prototype.reduce@npm:1.0.8" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.9" + es-array-method-boxes-properly: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + is-string: "npm:^1.1.1" + checksum: 10/63f4af812f6322fcf1961c348a33a4d2504dbea26d0bde614e43708d02bcdbdb729d225bad69392e8c168803333f0438d6870eb579194a7f2e94fb471fa518bb + languageName: node + linkType: hard + +"arraybuffer.prototype.slice@npm:^1.0.4": + version: 1.0.4 + resolution: "arraybuffer.prototype.slice@npm:1.0.4" dependencies: - define-properties: "npm:^1.1.3" - es-abstract: "npm:^1.17.0-next.1" - checksum: 10/e73101eb0989962db2b6ec4fef9fa5f3648accf1b3b6bd679fb587e8615b71edbbae4c43463a6eb2c84be9329ae4f63a9b19233821614b92b2e271926861d2c0 + array-buffer-byte-length: "npm:^1.0.1" + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + is-array-buffer: "npm:^3.0.4" + checksum: 10/4821ebdfe7d699f910c7f09bc9fa996f09b96b80bccb4f5dd4b59deae582f6ad6e505ecef6376f8beac1eda06df2dbc89b70e82835d104d6fcabd33c1aed1ae9 languageName: node linkType: hard @@ -872,6 +996,13 @@ __metadata: languageName: node linkType: hard +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10/1a09379937d846f0ce7614e75071c12826945d4e417db634156bf0e4673c495989302f52186dfa9767a1d9181794554717badd193ca2bbab046ef1da741d8efd + languageName: node + linkType: hard + "async-hook-jl@npm:^1.7.6": version: 1.7.6 resolution: "async-hook-jl@npm:1.7.6" @@ -907,10 +1038,19 @@ __metadata: languageName: node linkType: hard +"available-typed-arrays@npm:^1.0.7": + version: 1.0.7 + resolution: "available-typed-arrays@npm:1.0.7" + dependencies: + possible-typed-array-names: "npm:^1.0.0" + checksum: 10/6c9da3a66caddd83c875010a1ca8ef11eac02ba15fb592dc9418b2b5e7b77b645fa7729380a92d9835c2f05f2ca1b6251f39b993e0feb3f1517c74fa1af02cab + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": - version: 1.0.0 - resolution: "balanced-match@npm:1.0.0" - checksum: 10/9b67bfe558772f40cf743a3469b48b286aecec2ea9fe80c48d74845e53aab1cef524fafedf123a63019b49ac397760573ef5f173f539423061f7217cbb5fbd40 + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10/9706c088a283058a8a99e0bf91b0a2f75497f185980d9ffa8b304de1d9e58ebda7c72c07ebf01dadedaac5b2907b2c6f566f660d62bd336c3468e960403b9d65 languageName: node linkType: hard @@ -921,26 +1061,26 @@ __metadata: languageName: node linkType: hard -"basic-auth@npm:~2.0.0": - version: 2.0.0 - resolution: "basic-auth@npm:2.0.0" +"basic-auth@npm:~2.0.1": + version: 2.0.1 + resolution: "basic-auth@npm:2.0.1" dependencies: - safe-buffer: "npm:5.1.1" - checksum: 10/6ce40de06e7eacedad1228ccb8728457f688f9102117edc7e7f7d3f5b828b236f5df4082f3eedff56d2386c83bfdf8efdb4552be6b9e7f40c88ef0b5a3d109d9 + safe-buffer: "npm:5.1.2" + checksum: 10/3419b805d5dfc518f3a05dcf42aa53aa9ce820e50b6df5097f9e186322e1bc733c36722b624802cd37e791035aa73b828ed814d8362333d42d7f5cd04d7a5e48 languageName: node linkType: hard "binary-extensions@npm:^2.0.0": - version: 2.0.0 - resolution: "binary-extensions@npm:2.0.0" - checksum: 10/554f65d3378cf71c3185c17dec3ca58334b8ff6ae242db3107284765ce33b2af19efd20c11faec41907a40534929e34b3a98e7d391c61e4211b45732dccb1115 + version: 2.3.0 + resolution: "binary-extensions@npm:2.3.0" + checksum: 10/bcad01494e8a9283abf18c1b967af65ee79b0c6a9e6fcfafebfe91dbe6e0fc7272bafb73389e198b310516ae04f7ad17d79aacf6cb4c0d5d5202a7e2e52c7d98 languageName: node linkType: hard "bluebird@npm:^3.3.4": - version: 3.5.1 - resolution: "bluebird@npm:3.5.1" - checksum: 10/c4f0fb2961088bb4e8e6266d6c4710c106ed17ddaa2f1f18041d48f826e91f2abc52f543fa5dd0b074a30e70437a920e4b4512eee8390c171291ab01ef14884e + version: 3.7.2 + resolution: "bluebird@npm:3.7.2" + checksum: 10/007c7bad22c5d799c8dd49c85b47d012a1fe3045be57447721e6afbd1d5be43237af1db62e26cb9b0d9ba812d2e4ca3bac82f6d7e016b6b88de06ee25ceb96e7 languageName: node linkType: hard @@ -982,21 +1122,21 @@ __metadata: linkType: hard "brace-expansion@npm:^1.1.7": - version: 1.1.11 - resolution: "brace-expansion@npm:1.1.11" + version: 1.1.12 + resolution: "brace-expansion@npm:1.1.12" dependencies: balanced-match: "npm:^1.0.0" concat-map: "npm:0.0.1" - checksum: 10/faf34a7bb0c3fcf4b59c7808bc5d2a96a40988addf2e7e09dfbb67a2251800e0d14cd2bfc1aa79174f2f5095c54ff27f46fb1289fe2d77dac755b5eb3434cc07 + checksum: 10/12cb6d6310629e3048cadb003e1aca4d8c9bb5c67c3c321bafdd7e7a50155de081f78ea3e0ed92ecc75a9015e784f301efc8132383132f4f7904ad1ac529c562 languageName: node linkType: hard "braces@npm:~3.0.2": - version: 3.0.2 - resolution: "braces@npm:3.0.2" + version: 3.0.3 + resolution: "braces@npm:3.0.3" dependencies: - fill-range: "npm:^7.0.1" - checksum: 10/966b1fb48d193b9d155f810e5efd1790962f2c4e0829f8440b8ad236ba009222c501f70185ef732fef17a4c490bb33a03b90dab0631feafbdf447da91e8165b1 + fill-range: "npm:^7.1.1" + checksum: 10/fad11a0d4697a27162840b02b1fad249c1683cbc510cd5bf1a471f2f8085c046d41094308c577a50a03a579dd99d5a6b3724c4b5e8b14df2c4443844cfcda2c6 languageName: node linkType: hard @@ -1007,6 +1147,20 @@ __metadata: languageName: node linkType: hard +"browserslist@npm:^4.24.0": + version: 4.25.1 + resolution: "browserslist@npm:4.25.1" + dependencies: + caniuse-lite: "npm:^1.0.30001726" + electron-to-chromium: "npm:^1.5.173" + node-releases: "npm:^2.0.19" + update-browserslist-db: "npm:^1.1.3" + bin: + browserslist: cli.js + checksum: 10/bfb5511b425886279bbe2ea44d10e340c8aea85866c9d45083c13491d049b6362e254018c0afbf56d41ceeb64f994957ea8ae98dbba74ef1e54ef901c8732987 + languageName: node + linkType: hard + "bytes@npm:3.1.2, bytes@npm:^3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" @@ -1014,29 +1168,23 @@ __metadata: languageName: node linkType: hard -"cacache@npm:^16.1.0": - version: 16.1.3 - resolution: "cacache@npm:16.1.3" +"cacache@npm:^19.0.1": + version: 19.0.1 + resolution: "cacache@npm:19.0.1" dependencies: - "@npmcli/fs": "npm:^2.1.0" - "@npmcli/move-file": "npm:^2.0.0" - chownr: "npm:^2.0.0" - fs-minipass: "npm:^2.1.0" - glob: "npm:^8.0.1" - infer-owner: "npm:^1.0.4" - lru-cache: "npm:^7.7.1" - minipass: "npm:^3.1.6" - minipass-collect: "npm:^1.0.2" + "@npmcli/fs": "npm:^4.0.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^10.2.2" + lru-cache: "npm:^10.0.1" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" minipass-flush: "npm:^1.0.5" minipass-pipeline: "npm:^1.2.4" - mkdirp: "npm:^1.0.4" - p-map: "npm:^4.0.0" - promise-inflight: "npm:^1.0.1" - rimraf: "npm:^3.0.2" - ssri: "npm:^9.0.0" - tar: "npm:^6.1.11" - unique-filename: "npm:^2.0.0" - checksum: 10/a14524d90e377ee691d63a81173b33c473f8bc66eb299c64290b58e1d41b28842397f8d6c15a01b4c57ca340afcec019ae112a45c2f67a79f76130d326472e92 + p-map: "npm:^7.0.2" + ssri: "npm:^12.0.0" + tar: "npm:^7.4.3" + unique-filename: "npm:^4.0.0" + checksum: 10/ea026b27b13656330c2bbaa462a88181dcaa0435c1c2e705db89b31d9bdf7126049d6d0445ba746dca21454a0cfdf1d6f47fd39d34c8c8435296b30bc5738a13 languageName: node linkType: hard @@ -1052,13 +1200,35 @@ __metadata: languageName: node linkType: hard -"call-bind@npm:^1.0.0": +"call-bind-apply-helpers@npm:^1.0.0, call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": version: 1.0.2 - resolution: "call-bind@npm:1.0.2" + resolution: "call-bind-apply-helpers@npm:1.0.2" dependencies: - function-bind: "npm:^1.1.1" - get-intrinsic: "npm:^1.0.2" - checksum: 10/ca787179c1cbe09e1697b56ad499fd05dc0ae6febe5081d728176ade699ea6b1589240cb1ff1fe11fcf9f61538c1af60ad37e8eb2ceb4ef21cd6085dfd3ccedd + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10/00482c1f6aa7cfb30fb1dbeb13873edf81cfac7c29ed67a5957d60635a56b2a4a480f1016ddbdb3395cc37900d46037fb965043a51c5c789ffeab4fc535d18b5 + languageName: node + linkType: hard + +"call-bind@npm:^1.0.7, call-bind@npm:^1.0.8": + version: 1.0.8 + resolution: "call-bind@npm:1.0.8" + dependencies: + call-bind-apply-helpers: "npm:^1.0.0" + es-define-property: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.4" + set-function-length: "npm:^1.2.2" + checksum: 10/659b03c79bbfccf0cde3a79e7d52570724d7290209823e1ca5088f94b52192dc1836b82a324d0144612f816abb2f1734447438e38d9dafe0b3f82c2a1b9e3bce + languageName: node + linkType: hard + +"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3, call-bound@npm:^1.0.4": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + get-intrinsic: "npm:^1.3.0" + checksum: 10/ef2b96e126ec0e58a7ff694db43f4d0d44f80e641370c21549ed911fecbdbc2df3ebc9bddad918d6bbdefeafb60bb3337902006d5176d72bcd2da74820991af7 languageName: node linkType: hard @@ -1076,6 +1246,13 @@ __metadata: languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001726": + version: 1.0.30001731 + resolution: "caniuse-lite@npm:1.0.30001731" + checksum: 10/ad6771127c0cca13a711ca363bb0165a687f9a5c76f057b2c8c9746608a6bae2f15c7b6955063eefcd4ca0e5733d429f25292cc093a40a189d662f3a8647a020 + languageName: node + linkType: hard + "ccd-case-activity-api@workspace:.": version: 0.0.0-use.local resolution: "ccd-case-activity-api@workspace:." @@ -1118,59 +1295,49 @@ __metadata: sinon: "npm:^18.0.1" sinon-chai: "npm:^3.5.0" sinon-express-mock: "npm:^2.2.1" - socket.io: "npm:^4.1.2" + socket.io: "npm:^4.8.1" socket.io-router-middleware: "npm:^1.1.2" sonar-scanner: "npm:^3.1.0" - supertest: "npm:^3.0.0" + supertest: "npm:^7.1.4" yarn: "npm:^1.22.22" languageName: unknown linkType: soft "chai-arrays@npm:^2.0.0": - version: 2.0.0 - resolution: "chai-arrays@npm:2.0.0" - checksum: 10/ca977c52110f57d7ec36fdea91e7a05c29898e43149960d0298cb5637d8afb98827e2883ee383becb50e318c01872b3099d4b3b8fc4a60c048a65110c98c2c6d + version: 2.2.0 + resolution: "chai-arrays@npm:2.2.0" + checksum: 10/5e8d077b83a155c0cce2a37a2f838b698aca6783b61f0be2a43a98c684627986063945a5b38ce5bbdf75135afec13a90f4b9ad643d68ce2e379e106f3aed7235 languageName: node linkType: hard "chai-http@npm:^4.0.0": - version: 4.3.0 - resolution: "chai-http@npm:4.3.0" + version: 4.4.0 + resolution: "chai-http@npm:4.4.0" dependencies: "@types/chai": "npm:4" - "@types/superagent": "npm:^3.8.3" - cookiejar: "npm:^2.1.1" + "@types/superagent": "npm:4.1.13" + charset: "npm:^1.0.1" + cookiejar: "npm:^2.1.4" is-ip: "npm:^2.0.0" methods: "npm:^1.1.2" - qs: "npm:^6.5.1" - superagent: "npm:^3.7.0" - checksum: 10/c8b1ee8ee36f1c02dfae2d1981be5096440b9e6c9331262e27f6ba4699c8f841a350de0965122ded9b85b4fb6db22f0d38684eed0ee70bdab47019ea78474e12 + qs: "npm:^6.11.2" + superagent: "npm:^8.0.9" + checksum: 10/a311781f125b3858ae1ae8ccaefb0ccb2899f34d0ffadcaccfa99e465f0edab09f856210375209ffd0cf4fadaeb41d4fb933ff09cb23a99e62c08697dfa271d7 languageName: node linkType: hard "chai@npm:^4.3.6": - version: 4.3.6 - resolution: "chai@npm:4.3.6" + version: 4.5.0 + resolution: "chai@npm:4.5.0" dependencies: assertion-error: "npm:^1.1.0" - check-error: "npm:^1.0.2" - deep-eql: "npm:^3.0.1" - get-func-name: "npm:^2.0.0" - loupe: "npm:^2.3.1" + check-error: "npm:^1.0.3" + deep-eql: "npm:^4.1.3" + get-func-name: "npm:^2.0.2" + loupe: "npm:^2.3.6" pathval: "npm:^1.1.1" - type-detect: "npm:^4.0.5" - checksum: 10/29c754d12b5fc5e7d555c2b51c69b55cb2b8a95dfff812e39775ced0ee5081de3f5de4b256959d2df133ea37a648ec381d20138376b02a3a84762c4f72746081 - languageName: node - linkType: hard - -"chalk@npm:^2.0.0": - version: 2.3.2 - resolution: "chalk@npm:2.3.2" - dependencies: - ansi-styles: "npm:^3.2.1" - escape-string-regexp: "npm:^1.0.5" - supports-color: "npm:^5.3.0" - checksum: 10/a0fe064acdcb6bcedf94d765d658afacfc972e8b10116a15621f71d1e8054cf8161b35389a063285647c21698a89763b3edc4d471d0eb25ec1fb18f060f2d172 + type-detect: "npm:^4.1.0" + checksum: 10/cde341aee15b0a51559c7cfc20788dcfb4d586a498cfb93b937bb568fd45c777b73b1461274be6092b6bf868adb4e3a63f3fec13c89f7d8fb194f84c6fa42d5f languageName: node linkType: hard @@ -1185,13 +1352,13 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^3.0.0": - version: 3.0.0 - resolution: "chalk@npm:3.0.0" +"chalk@npm:^4.1.0": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" dependencies: ansi-styles: "npm:^4.1.0" supports-color: "npm:^7.1.0" - checksum: 10/37f90b31fd655fb49c2bd8e2a68aebefddd64522655d001ef417e6f955def0ed9110a867ffc878a533f2dafea5f2032433a37c8a7614969baa7f8a1cd424ddfc + checksum: 10/cb3f3e594913d63b1814d7ca7c9bafbf895f75fbf93b92991980610dfd7b48500af4e3a5d4e3a8f337990a96b168d7eb84ee55efdce965e2ee8efc20f8c8f139 languageName: node linkType: hard @@ -1202,10 +1369,19 @@ __metadata: languageName: node linkType: hard -"check-error@npm:^1.0.2": - version: 1.0.2 - resolution: "check-error@npm:1.0.2" - checksum: 10/011e74b2eac49bd42c5610f15d6949d982e7ec946247da0276278a90e7476e6b88d25d3c605a4115d5e3575312e1f5a11e91c82290c8a47ca275c92f5d0981db +"charset@npm:^1.0.1": + version: 1.0.1 + resolution: "charset@npm:1.0.1" + checksum: 10/3b6a8ba900d6ecbaafd0684a619fa0232840d382d2ac9dad3d68d071d026eaa68dd121f6607f079b6e6387f1d0f3be8e43a3a4e6303587c8cd128637164eb5a4 + languageName: node + linkType: hard + +"check-error@npm:^1.0.3": + version: 1.0.3 + resolution: "check-error@npm:1.0.3" + dependencies: + get-func-name: "npm:^2.0.2" + checksum: 10/e2131025cf059b21080f4813e55b3c480419256914601750b0fee3bd9b2b8315b531e551ef12560419b8b6d92a3636511322752b1ce905703239e7cc451b6399 languageName: node linkType: hard @@ -1228,10 +1404,10 @@ __metadata: languageName: node linkType: hard -"chownr@npm:^2.0.0": - version: 2.0.0 - resolution: "chownr@npm:2.0.0" - checksum: 10/c57cf9dd0791e2f18a5ee9c1a299ae6e801ff58fee96dc8bfd0dcb4738a6ce58dd252a3605b1c93c6418fe4f9d5093b28ffbf4d66648cb2a9c67eaef9679be2f +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10/b63cb1f73d171d140a2ed8154ee6566c8ab775d3196b0e03a2a94b5f6a0ce7777ee5685ca56849403c8d17bd457a6540672f9a60696a6137c7a409097495b82c languageName: node linkType: hard @@ -1258,10 +1434,10 @@ __metadata: languageName: node linkType: hard -"cli-width@npm:^2.0.0": - version: 2.2.0 - resolution: "cli-width@npm:2.2.0" - checksum: 10/05f1cf7de5d1716581ce388cc2c04587a532cf30903cd65acaa1120b1754c545b71021c61cccb333a8ac857d981341842a9bf2c1988f690f54cbdc46de671b72 +"cli-width@npm:^3.0.0": + version: 3.0.0 + resolution: "cli-width@npm:3.0.0" + checksum: 10/8730848b04fb189666ab037a35888d191c8f05b630b1d770b0b0e4c920b47bb5cc14bddf6b8ffe5bfc66cee97c8211d4d18e756c1ffcc75d7dbe7e1186cd7826 languageName: node linkType: hard @@ -1306,9 +1482,9 @@ __metadata: linkType: hard "cluster-key-slot@npm:^1.0.6": - version: 1.0.8 - resolution: "cluster-key-slot@npm:1.0.8" - checksum: 10/b1569fd9fba7c506b0dbce0cafed5adda7086f4cd22e62a8db06200d3c6936f141f7b75ec6bef0bf7018561405df06b70f98243f2375a9989a2cd4734c016140 + version: 1.1.2 + resolution: "cluster-key-slot@npm:1.1.2" + checksum: 10/516ed8b5e1a14d9c3a9c96c72ef6de2d70dfcdbaa0ec3a90bc7b9216c5457e39c09a5775750c272369070308542e671146120153062ab5f2f481bed5de2c925f languageName: node linkType: hard @@ -1323,11 +1499,11 @@ __metadata: linkType: hard "color-convert@npm:^1.9.0": - version: 1.9.1 - resolution: "color-convert@npm:1.9.1" + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" dependencies: - color-name: "npm:^1.1.1" - checksum: 10/f0d52137d427bf780405c61601ad9a0ece076782d9c97b5c7483134165a24781bb90e22f13e0f6636eba7b653e180b0538cd2d5f7af6e180de95c425b9e70e10 + color-name: "npm:1.1.3" + checksum: 10/ffa319025045f2973919d155f25e7c00d08836b6b33ea2d205418c59bd63a665d713c52d9737a9e0fe467fb194b40fbef1d849bae80d674568ee220a31ef3d10 languageName: node linkType: hard @@ -1340,7 +1516,7 @@ __metadata: languageName: node linkType: hard -"color-name@npm:^1.1.1": +"color-name@npm:1.1.3": version: 1.1.3 resolution: "color-name@npm:1.1.3" checksum: 10/09c5d3e33d2105850153b14466501f2bfb30324a2f76568a408763a3b7433b0e50e5b4ab1947868e65cb101bb7cb75029553f2c333b6d4b8138a73fcc133d69d @@ -1354,15 +1530,6 @@ __metadata: languageName: node linkType: hard -"color-support@npm:^1.1.3": - version: 1.1.3 - resolution: "color-support@npm:1.1.3" - bin: - color-support: bin.js - checksum: 10/4bcfe30eea1498fe1cabc852bbda6c9770f230ea0e4faf4611c5858b1b9e4dde3730ac485e65f54ca182f4c50b626c1bea7c8441ceda47367a54a818c248aa7a - languageName: node - linkType: hard - "colors@npm:1.0.x": version: 1.0.3 resolution: "colors@npm:1.0.3" @@ -1370,7 +1537,7 @@ __metadata: languageName: node linkType: hard -"combined-stream@npm:^1.0.6, combined-stream@npm:^1.0.8": +"combined-stream@npm:^1.0.8": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" dependencies: @@ -1386,10 +1553,10 @@ __metadata: languageName: node linkType: hard -"component-emitter@npm:^1.2.0, component-emitter@npm:^1.3.0": - version: 1.3.0 - resolution: "component-emitter@npm:1.3.0" - checksum: 10/dfc1ec2e7aa2486346c068f8d764e3eefe2e1ca0b24f57506cd93b2ae3d67829a7ebd7cc16e2bf51368fac2f45f78fcff231718e40b1975647e4a86be65e1d05 +"component-emitter@npm:^1.3.0, component-emitter@npm:^1.3.1": + version: 1.3.1 + resolution: "component-emitter@npm:1.3.1" + checksum: 10/94550aa462c7bd5a61c1bc480e28554aa306066930152d1b1844a0dd3845d4e5db7e261ddec62ae184913b3e59b55a2ad84093b9d3596a8f17c341514d6c483d languageName: node linkType: hard @@ -1409,36 +1576,31 @@ __metadata: languageName: node linkType: hard -"confusing-browser-globals@npm:^1.0.7": - version: 1.0.9 - resolution: "confusing-browser-globals@npm:1.0.9" - checksum: 10/585f244fc05bdcede9bd8831c41ff22b3fb0629e3ac6a495330a59c1aa9c30f3c1544498b6ff4f5a0bb302f7bedfbbb3f59d591556856e80622fd51b1dff351c +"confusing-browser-globals@npm:^1.0.10": + version: 1.0.11 + resolution: "confusing-browser-globals@npm:1.0.11" + checksum: 10/3afc635abd37e566477f610e7978b15753f0e84025c25d49236f1f14d480117185516bdd40d2a2167e6bed8048641a9854964b9c067e3dcdfa6b5d0ad3c3a5ef languageName: node linkType: hard "connect-timeout@npm:^1.9.0": - version: 1.9.0 - resolution: "connect-timeout@npm:1.9.0" + version: 1.9.1 + resolution: "connect-timeout@npm:1.9.1" dependencies: http-errors: "npm:~1.6.1" ms: "npm:2.0.0" on-finished: "npm:~2.3.0" - on-headers: "npm:~1.0.1" - checksum: 10/e5c15ed3d03c7b431142b0f65af5291e288a871dd0c66dd95f5b1ecee7838457368d2f853c3563cef84eb26ce48838fa74816e37bfaaa528f09c65ca8f864094 - languageName: node - linkType: hard - -"console-control-strings@npm:^1.1.0": - version: 1.1.0 - resolution: "console-control-strings@npm:1.1.0" - checksum: 10/27b5fa302bc8e9ae9e98c03c66d76ca289ad0c61ce2fe20ab288d288bee875d217512d2edb2363fc83165e88f1c405180cf3f5413a46e51b4fe1a004840c6cdb + on-headers: "npm:~1.1.0" + checksum: 10/6b47dd331c55bc184dcf281176df224bd23e24c43639182ae83a8448638a3510cdf4449ee9ab2bd90976eee08976a291fa7667ca4a6d629c561de6886fe9237e languageName: node linkType: hard -"contains-path@npm:^0.1.0": - version: 0.1.0 - resolution: "contains-path@npm:0.1.0" - checksum: 10/94ecfd944e0bc51be8d3fc596dcd17d705bd4c8a1a627952a3a8c5924bac01c7ea19034cf40b4b4f89e576cdead130a7e5fd38f5f7f07ef67b4b261d875871e3 +"content-disposition@npm:^0.5.3": + version: 0.5.4 + resolution: "content-disposition@npm:0.5.4" + dependencies: + safe-buffer: "npm:5.2.1" + checksum: 10/b7f4ce176e324f19324be69b05bf6f6e411160ac94bc523b782248129eb1ef3be006f6cff431aaea5e337fe5d176ce8830b8c2a1b721626ead8933f0cbe78720 languageName: node linkType: hard @@ -1469,11 +1631,16 @@ __metadata: linkType: hard "convert-source-map@npm:^1.7.0": - version: 1.7.0 - resolution: "convert-source-map@npm:1.7.0" - dependencies: - safe-buffer: "npm:~5.1.1" - checksum: 10/0d0dd324ad15850cf1d44520560ab524ba3fce7ed8296df10d9aa466a0e964df9c9de0dcb78fb70a60493800b256ffe40d64f24968e32a48a1bcbff117102022 + version: 1.9.0 + resolution: "convert-source-map@npm:1.9.0" + checksum: 10/dc55a1f28ddd0e9485ef13565f8f756b342f9a46c4ae18b843fe3c30c675d058d6a4823eff86d472f187b176f0adf51ea7b69ea38be34be4a63cbbf91b0593c8 + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10/c987be3ec061348cdb3c2bfb924bec86dea1eacad10550a85ca23edb0fe3556c3a61c7399114f3331ccb3499d7fd0285ab24566e5745929412983494c3926e15 languageName: node linkType: hard @@ -1484,20 +1651,13 @@ __metadata: languageName: node linkType: hard -"cookie@npm:^0.7.0, cookie@npm:^0.7.1": +"cookie@npm:^0.7.0, cookie@npm:^0.7.1, cookie@npm:~0.7.2": version: 0.7.2 resolution: "cookie@npm:0.7.2" checksum: 10/24b286c556420d4ba4e9bc09120c9d3db7d28ace2bd0f8ccee82422ce42322f73c8312441271e5eefafbead725980e5996cc02766dbb89a90ac7f5636ede608f languageName: node linkType: hard -"cookie@npm:~0.4.1": - version: 0.4.2 - resolution: "cookie@npm:0.4.2" - checksum: 10/2e1de9fdedca54881eab3c0477aeb067f281f3155d9cfee9d28dfb252210d09e85e9d175c0a60689661feb9e35e588515352f2456bc1f8e8db4267e05fd70137 - languageName: node - linkType: hard - "cookiejar@npm:^2.1.4": version: 2.1.4 resolution: "cookiejar@npm:2.1.4" @@ -1505,13 +1665,6 @@ __metadata: languageName: node linkType: hard -"core-util-is@npm:~1.0.0": - version: 1.0.2 - resolution: "core-util-is@npm:1.0.2" - checksum: 10/d0f7587346b44a1fe6c269267e037dd34b4787191e473c3e685f507229d88561c40eb18872fabfff02977301815d474300b7bfbd15396c13c5377393f7e87ec3 - languageName: node - linkType: hard - "cors@npm:~2.8.5": version: 2.8.5 resolution: "cors@npm:2.8.5" @@ -1552,6 +1705,39 @@ __metadata: languageName: node linkType: hard +"data-view-buffer@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-buffer@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.2" + checksum: 10/c10b155a4e93999d3a215d08c23eea95f865e1f510b2e7748fcae1882b776df1afe8c99f483ace7fc0e5a3193ab08da138abebc9829d12003746c5a338c4d644 + languageName: node + linkType: hard + +"data-view-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-byte-length@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.2" + checksum: 10/2a47055fcf1ab3ec41b00b6f738c6461a841391a643c9ed9befec1117c1765b4d492661d97fb7cc899200c328949dca6ff189d2c6537d96d60e8a02dfe3c95f7 + languageName: node + linkType: hard + +"data-view-byte-offset@npm:^1.0.1": + version: 1.0.1 + resolution: "data-view-byte-offset@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.1" + checksum: 10/fa3bdfa0968bea6711ee50375094b39f561bce3f15f9e558df59de9c25f0bdd4cddc002d9c1d70ac7772ebd36854a7e22d1761e7302a934e6f1c2263bcf44aa2 + languageName: node + linkType: hard + "debug@npm:2.6.9, debug@npm:^2.6.9, debug@npm:~2.6.3": version: 2.6.9 resolution: "debug@npm:2.6.9" @@ -1570,19 +1756,19 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:~4.3.1, debug@npm:~4.3.2": - version: 4.3.4 - resolution: "debug@npm:4.3.4" +"debug@npm:4, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.7, debug@npm:^4.4.0": + version: 4.4.1 + resolution: "debug@npm:4.4.1" dependencies: - ms: "npm:2.1.2" + ms: "npm:^2.1.3" peerDependenciesMeta: supports-color: optional: true - checksum: 10/0073c3bcbd9cb7d71dd5f6b55be8701af42df3e56e911186dfa46fac3a5b9eb7ce7f377dd1d3be6db8977221f8eb333d945216f645cf56f6b688cd484837d255 + checksum: 10/8e2709b2144f03c7950f8804d01ccb3786373df01e406a0f66928e47001cf2d336cbed9ee137261d4f90d68d8679468c755e3548ed83ddacdc82b194d2468afe languageName: node linkType: hard -"debug@npm:^3.1.0": +"debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" dependencies: @@ -1591,24 +1777,15 @@ __metadata: languageName: node linkType: hard -"debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1": - version: 4.1.1 - resolution: "debug@npm:4.1.1" - dependencies: - ms: "npm:^2.1.1" - checksum: 10/19bd01e5b1e5869eacfb8e1ee9873dc90e1f90edfd9c460e388326b163e662189af291fcb67e3614dcfbeae29c1c7780a9a7b4bcea39b201316abdc058be89be - languageName: node - linkType: hard - -"debug@npm:^4.3.5, debug@npm:^4.4.0": - version: 4.4.1 - resolution: "debug@npm:4.4.1" +"debug@npm:~4.3.1, debug@npm:~4.3.2, debug@npm:~4.3.4": + version: 4.3.7 + resolution: "debug@npm:4.3.7" dependencies: ms: "npm:^2.1.3" peerDependenciesMeta: supports-color: optional: true - checksum: 10/8e2709b2144f03c7950f8804d01ccb3786373df01e406a0f66928e47001cf2d336cbed9ee137261d4f90d68d8679468c755e3548ed83ddacdc82b194d2468afe + checksum: 10/71168908b9a78227ab29d5d25fe03c5867750e31ce24bf2c44a86efc5af041758bb56569b0a3d48a9b5344c00a24a777e6f4100ed6dfd9534a42c1dde285125a languageName: node linkType: hard @@ -1619,32 +1796,32 @@ __metadata: languageName: node linkType: hard -"deep-eql@npm:^3.0.1": - version: 3.0.1 - resolution: "deep-eql@npm:3.0.1" +"deep-eql@npm:^4.1.3": + version: 4.1.4 + resolution: "deep-eql@npm:4.1.4" dependencies: type-detect: "npm:^4.0.0" - checksum: 10/d8f8e141ece42b7945ca85f08094c80540ed277bcea268b0da1801cfa5b001e164d2548c8d7ba17e935f001da401ccb33a1b6d2005713f1684a0e7dadc4e52d1 + checksum: 10/f04f4d581f044a824a6322fe4f68fbee4d6780e93fc710cd9852cbc82bfc7010df00f0e05894b848abbe14dc3a25acac44f424e181ae64d12f2ab9d0a875a5ef languageName: node linkType: hard "deep-is@npm:~0.1.3": - version: 0.1.3 - resolution: "deep-is@npm:0.1.3" - checksum: 10/dee1094e987a784a9a9c8549fc65eeca3422aef3bf2f9579f76c126085f280311d09273826c2f430d84fd09d64f6a578e5e7a4ac6ba1d50ea6cff0ddf605c025 + version: 0.1.4 + resolution: "deep-is@npm:0.1.4" + checksum: 10/ec12d074aef5ae5e81fa470b9317c313142c9e8e2afe3f8efa124db309720db96d1d222b82b84c834e5f87e7a614b44a4684b6683583118b87c833b3be40d4d8 languageName: node linkType: hard "default-require-extensions@npm:^3.0.0": - version: 3.0.0 - resolution: "default-require-extensions@npm:3.0.0" + version: 3.0.1 + resolution: "default-require-extensions@npm:3.0.1" dependencies: strip-bom: "npm:^4.0.0" - checksum: 10/0b5bdb6786ebb0ff6ef55386f37c8d221963fbbd3009588fe71032c85ca16da05eff2ad01bfe9bfc8bac5ce95a18f66b38c50d454482e3e9d2de1142424a3e7c + checksum: 10/45882fc971dd157faf6716ced04c15cf252c0a2d6f5c5844b66ca49f46ed03396a26cd940771aa569927aee22923a961bab789e74b25aabc94d90742c9dd1217 languageName: node linkType: hard -"define-data-property@npm:^1.0.1": +"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.4": version: 1.1.4 resolution: "define-data-property@npm:1.1.4" dependencies: @@ -1655,7 +1832,7 @@ __metadata: languageName: node linkType: hard -"define-properties@npm:^1.1.2": +"define-properties@npm:^1.1.2, define-properties@npm:^1.2.1": version: 1.2.1 resolution: "define-properties@npm:1.2.1" dependencies: @@ -1666,15 +1843,6 @@ __metadata: languageName: node linkType: hard -"define-properties@npm:^1.1.3": - version: 1.1.3 - resolution: "define-properties@npm:1.1.3" - dependencies: - object-keys: "npm:^1.0.12" - checksum: 10/33125cafaf4de2c9934cfba20e0a45bccc53fa6d85370a48c0b5a9a0c76c7d0497a5fdf01bc5c1186cb61f2747f19f43520ca6fdd37b4d0290f552c6747e0a17 - languageName: node - linkType: hard - "delayed-stream@npm:~1.0.0": version: 1.0.0 resolution: "delayed-stream@npm:1.0.0" @@ -1682,35 +1850,21 @@ __metadata: languageName: node linkType: hard -"delegates@npm:^1.0.0": - version: 1.0.0 - resolution: "delegates@npm:1.0.0" - checksum: 10/a51744d9b53c164ba9c0492471a1a2ffa0b6727451bdc89e31627fdf4adda9d51277cfcbfb20f0a6f08ccb3c436f341df3e92631a3440226d93a8971724771fd - languageName: node - linkType: hard - "denque@npm:^1.1.0": - version: 1.2.3 - resolution: "denque@npm:1.2.3" - checksum: 10/65ed42312debebbcfe7810bc5d6f153f92828cb5e045581f9cca935f4a0fbe99455de7fffef926b17e55f70c561a51f1a329bc5f52214eb1bd65d4dc98929075 - languageName: node - linkType: hard - -"depd@npm:1.1.1": - version: 1.1.1 - resolution: "depd@npm:1.1.1" - checksum: 10/02658c14dfa6b7c7ffbd7c6424546b6180f4b1d89f6b992bed64c4f52a7c32d299e884f31f6500aea44308ecfa05bcb3a669004d011b5f64f9186c306938f943 + version: 1.5.1 + resolution: "denque@npm:1.5.1" + checksum: 10/dbde01a987d95205f7563c67411e0964073a6b38e4cf2ff190cf91f71e2ce3f51c40bacd31f2a5497e0ff82366bcfd8231d3659cb03f987279130058d512aa29 languageName: node linkType: hard -"depd@npm:2.0.0, depd@npm:^2.0.0": +"depd@npm:2.0.0, depd@npm:^2.0.0, depd@npm:~2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: 10/c0c8ff36079ce5ada64f46cc9d6fd47ebcf38241105b6e0c98f412e8ad91f084bcf906ff644cc3a4bd876ca27a62accb8b0fff72ea6ed1a414b89d8506f4a5ca languageName: node linkType: hard -"depd@npm:^1.1.0, depd@npm:^1.1.2, depd@npm:~1.1.2": +"depd@npm:^1.1.0, depd@npm:~1.1.2": version: 1.1.2 resolution: "depd@npm:1.1.2" checksum: 10/2ed6966fc14463a9e85451db330ab8ba041efed0b9a1a472dbfc6fbf2f82bab66491915f996b25d8517dddc36c8c74e24c30879b34877f3c4410733444a51d1d @@ -1766,13 +1920,12 @@ __metadata: languageName: node linkType: hard -"doctrine@npm:1.5.0": - version: 1.5.0 - resolution: "doctrine@npm:1.5.0" +"doctrine@npm:^2.1.0": + version: 2.1.0 + resolution: "doctrine@npm:2.1.0" dependencies: esutils: "npm:^2.0.2" - isarray: "npm:^1.0.0" - checksum: 10/3ac7d891225f95292f9b9cfc1fe24e75e05ea53b08706298bbf4bf2451f8e1b9de25b1017f5dac23a8deeb8f3ba15fe2c1b454e78d1e97a0921af30aa6d5e753 + checksum: 10/555684f77e791b17173ea86e2eea45ef26c22219cb64670669c4f4bebd26dbc95cd90ec1f4159e9349a6bb9eb892ce4dde8cd0139e77bedd8bf4518238618474 languageName: node linkType: hard @@ -1785,6 +1938,24 @@ __metadata: languageName: node linkType: hard +"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10/5add88a3d68d42d6e6130a0cac450b7c2edbe73364bbd2fc334564418569bea97c6943a8fcd70e27130bf32afc236f30982fc4905039b703f23e9e0433c29934 + languageName: node + linkType: hard + +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 10/9b1d3e1baefeaf7d70799db8774149cef33b97183a6addceeba0cf6b85ba23ee2686f302f14482006df32df75d32b17c509c143a3689627929e4a8efaf483952 + languageName: node + linkType: hard + "ee-first@npm:1.1.1": version: 1.1.1 resolution: "ee-first@npm:1.1.1" @@ -1792,6 +1963,13 @@ __metadata: languageName: node linkType: hard +"electron-to-chromium@npm:^1.5.173": + version: 1.5.192 + resolution: "electron-to-chromium@npm:1.5.192" + checksum: 10/99f5dc94b077738a8dadba553e673fc8da33407df58efe870d7c4872895207862e044049bda0d41e2743a97918b2028d1c7e8ebb6a6c7786ec179a5a47bbe0b2 + languageName: node + linkType: hard + "emitter-listener@npm:^1.0.1, emitter-listener@npm:^1.1.1": version: 1.1.2 resolution: "emitter-listener@npm:1.1.2" @@ -1815,6 +1993,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 10/915acf859cea7131dac1b2b5c9c8e35c4849e325a1d114c30adb8cd615970f6dca0e27f64f3a4949d7d6ed86ecd79a1c5c63f02e697513cddd7b5835c90948b8 + languageName: node + linkType: hard + "encodeurl@npm:^2.0.0": version: 2.0.0 resolution: "encodeurl@npm:2.0.0" @@ -1831,28 +2016,27 @@ __metadata: languageName: node linkType: hard -"engine.io-parser@npm:~5.0.3": - version: 5.0.6 - resolution: "engine.io-parser@npm:5.0.6" - checksum: 10/8e36e6b8907d39816ac977283817e2a6c9d3ae526daa998ea93a5144c39cad6247221cbf9352229bf1aac08e925905af699d2eac6bd66ee0cb540ecb6c26fb48 +"engine.io-parser@npm:~5.2.1": + version: 5.2.3 + resolution: "engine.io-parser@npm:5.2.3" + checksum: 10/eb0023fff5766e7ae9d59e52d92df53fea06d472cfd7b52e5d2c36b4c1dbf78cab5fde1052bcb3d4bb85bdb5aee10ae85d8a1c6c04676dac0c6cdf16bcba6380 languageName: node linkType: hard -"engine.io@npm:~6.4.1": - version: 6.4.1 - resolution: "engine.io@npm:6.4.1" +"engine.io@npm:~6.6.0": + version: 6.6.4 + resolution: "engine.io@npm:6.6.4" dependencies: - "@types/cookie": "npm:^0.4.1" "@types/cors": "npm:^2.8.12" "@types/node": "npm:>=10.0.0" accepts: "npm:~1.3.4" base64id: "npm:2.0.0" - cookie: "npm:~0.4.1" + cookie: "npm:~0.7.2" cors: "npm:~2.8.5" debug: "npm:~4.3.1" - engine.io-parser: "npm:~5.0.3" - ws: "npm:~8.11.0" - checksum: 10/0e86bde93374a51419d5ff83a761d35a49f496ceb91a46b60f7471631c309efcd37e23bfb5863d59c62f4def6da1603399acf2ed78bc89375caeb8fa62ed0e9f + engine.io-parser: "npm:~5.2.1" + ws: "npm:~8.17.1" + checksum: 10/005b43b392d5b4b9bb196d1ae2a8cc1334a7dc70af3cfb50627d257de407ca1afae725fcd8571f9621cd12ed437abaac819c64cf22f09d5ae02b954a7e7bf4f8 languageName: node linkType: hard @@ -1870,35 +2054,76 @@ __metadata: languageName: node linkType: hard -"error-ex@npm:^1.2.0": - version: 1.3.2 - resolution: "error-ex@npm:1.3.2" - dependencies: - is-arrayish: "npm:^0.2.1" - checksum: 10/d547740aa29c34e753fb6fed2c5de81802438529c12b3673bd37b6bb1fe49b9b7abdc3c11e6062fe625d8a296b3cf769a80f878865e25e685f787763eede3ffb - languageName: node - linkType: hard - -"es-abstract@npm:^1.17.0, es-abstract@npm:^1.17.0-next.1": - version: 1.17.4 - resolution: "es-abstract@npm:1.17.4" +"es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0": + version: 1.24.0 + resolution: "es-abstract@npm:1.24.0" dependencies: - es-to-primitive: "npm:^1.2.1" - function-bind: "npm:^1.1.1" - has: "npm:^1.0.3" - has-symbols: "npm:^1.0.1" - is-callable: "npm:^1.1.5" - is-regex: "npm:^1.0.5" - object-inspect: "npm:^1.7.0" + array-buffer-byte-length: "npm:^1.0.2" + arraybuffer.prototype.slice: "npm:^1.0.4" + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + data-view-buffer: "npm:^1.0.2" + data-view-byte-length: "npm:^1.0.2" + data-view-byte-offset: "npm:^1.0.1" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + es-set-tostringtag: "npm:^2.1.0" + es-to-primitive: "npm:^1.3.0" + function.prototype.name: "npm:^1.1.8" + get-intrinsic: "npm:^1.3.0" + get-proto: "npm:^1.0.1" + get-symbol-description: "npm:^1.1.0" + globalthis: "npm:^1.0.4" + gopd: "npm:^1.2.0" + has-property-descriptors: "npm:^1.0.2" + has-proto: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + internal-slot: "npm:^1.1.0" + is-array-buffer: "npm:^3.0.5" + is-callable: "npm:^1.2.7" + is-data-view: "npm:^1.0.2" + is-negative-zero: "npm:^2.0.3" + is-regex: "npm:^1.2.1" + is-set: "npm:^2.0.3" + is-shared-array-buffer: "npm:^1.0.4" + is-string: "npm:^1.1.1" + is-typed-array: "npm:^1.1.15" + is-weakref: "npm:^1.1.1" + math-intrinsics: "npm:^1.1.0" + object-inspect: "npm:^1.13.4" object-keys: "npm:^1.1.1" - object.assign: "npm:^4.1.0" - string.prototype.trimleft: "npm:^2.1.1" - string.prototype.trimright: "npm:^2.1.1" - checksum: 10/d56fe922b5ef1e6e5f2a78a64f34522ff530339e0eed3833a356f15253aaa30186786652d2a3aca2f20aede050760bdfa91c454bb5156f7dcde20df2d53f3721 + object.assign: "npm:^4.1.7" + own-keys: "npm:^1.0.1" + regexp.prototype.flags: "npm:^1.5.4" + safe-array-concat: "npm:^1.1.3" + safe-push-apply: "npm:^1.0.0" + safe-regex-test: "npm:^1.1.0" + set-proto: "npm:^1.0.0" + stop-iteration-iterator: "npm:^1.1.0" + string.prototype.trim: "npm:^1.2.10" + string.prototype.trimend: "npm:^1.0.9" + string.prototype.trimstart: "npm:^1.0.8" + typed-array-buffer: "npm:^1.0.3" + typed-array-byte-length: "npm:^1.0.3" + typed-array-byte-offset: "npm:^1.0.4" + typed-array-length: "npm:^1.0.7" + unbox-primitive: "npm:^1.1.0" + which-typed-array: "npm:^1.1.19" + checksum: 10/64e07a886f7439cf5ccfc100f9716e6173e10af6071a50a5031afbdde474a3dbc9619d5965da54e55f8908746a9134a46be02af8c732d574b7b81ed3124e2daf + languageName: node + linkType: hard + +"es-array-method-boxes-properly@npm:^1.0.0": + version: 1.0.0 + resolution: "es-array-method-boxes-properly@npm:1.0.0" + checksum: 10/27a8a21acf20f3f51f69dce8e643f151e380bffe569e95dc933b9ded9fcd89a765ee21b5229c93f9206c93f87395c6b75f80be8ac8c08a7ceb8771e1822ff1fb languageName: node linkType: hard -"es-define-property@npm:^1.0.0": +"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": version: 1.0.1 resolution: "es-define-property@npm:1.0.1" checksum: 10/f8dc9e660d90919f11084db0a893128f3592b781ce967e4fccfb8f3106cb83e400a4032c559184ec52ee1dbd4b01e7776c7cd0b3327b1961b1a4a7008920fe78 @@ -1912,14 +2137,44 @@ __metadata: languageName: node linkType: hard -"es-to-primitive@npm:^1.2.1": - version: 1.2.1 - resolution: "es-to-primitive@npm:1.2.1" +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10/54fe77de288451dae51c37bfbfe3ec86732dc3778f98f3eb3bdb4bf48063b2c0b8f9c93542656986149d08aa5be3204286e2276053d19582b76753f1a2728867 + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10/86814bf8afbcd8966653f731415888019d4bc4aca6b6c354132a7a75bb87566751e320369654a101d23a91c87a85c79b178bcf40332839bd347aff437c4fb65f + languageName: node + linkType: hard + +"es-shim-unscopables@npm:^1.0.2, es-shim-unscopables@npm:^1.1.0": + version: 1.1.0 + resolution: "es-shim-unscopables@npm:1.1.0" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10/c351f586c30bbabc62355be49564b2435468b52c3532b8a1663672e3d10dc300197e69c247869dd173e56d86423ab95fc0c10b0939cdae597094e0fdca078cba + languageName: node + linkType: hard + +"es-to-primitive@npm:^1.3.0": + version: 1.3.0 + resolution: "es-to-primitive@npm:1.3.0" dependencies: - is-callable: "npm:^1.1.4" - is-date-object: "npm:^1.0.1" - is-symbol: "npm:^1.0.2" - checksum: 10/74aeeefe2714cf99bb40cab7ce3012d74e1e2c1bd60d0a913b467b269edde6e176ca644b5ba03a5b865fb044a29bca05671cd445c85ca2cdc2de155d7fc8fe9b + is-callable: "npm:^1.2.7" + is-date-object: "npm:^1.0.5" + is-symbol: "npm:^1.0.4" + checksum: 10/17faf35c221aad59a16286cbf58ef6f080bf3c485dff202c490d074d8e74da07884e29b852c245d894eac84f73c58330ec956dfd6d02c0b449d75eb1012a3f9b languageName: node linkType: hard @@ -1930,6 +2185,13 @@ __metadata: languageName: node linkType: hard +"escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10/9d7169e3965b2f9ae46971afa392f6e5a25545ea30f2e2dd99c9b0a95a3f52b5653681a84f5b2911a413ddad2d7a93d3514165072f349b5ffc59c75a899970d6 + languageName: node + linkType: hard + "escape-html@npm:^1.0.3": version: 1.0.3 resolution: "escape-html@npm:1.0.3" @@ -1945,68 +2207,78 @@ __metadata: linkType: hard "eslint-config-airbnb-base@npm:^14.0.0": - version: 14.0.0 - resolution: "eslint-config-airbnb-base@npm:14.0.0" + version: 14.2.1 + resolution: "eslint-config-airbnb-base@npm:14.2.1" dependencies: - confusing-browser-globals: "npm:^1.0.7" - object.assign: "npm:^4.1.0" - object.entries: "npm:^1.1.0" + confusing-browser-globals: "npm:^1.0.10" + object.assign: "npm:^4.1.2" + object.entries: "npm:^1.1.2" peerDependencies: - eslint: ^5.16.0 || ^6.1.0 - eslint-plugin-import: ^2.18.2 - checksum: 10/427497e788332dedd961512103d7c668e4d9f3658188da69f1e7b8f4a82ff40cf0b9e5e100c87dc40ad5ec812cb0502a2e6a72687cd300740ade2fe78c839731 + eslint: ^5.16.0 || ^6.8.0 || ^7.2.0 + eslint-plugin-import: ^2.22.1 + checksum: 10/0d679b6fe8030e18be9d5876bdf4d112988f9a1bc23fbb87a835447d448877041191caae6f9f656238bf5b883da8ea80199d6769075fe3493018c5e74d5fa0dd languageName: node linkType: hard -"eslint-import-resolver-node@npm:^0.3.2": - version: 0.3.3 - resolution: "eslint-import-resolver-node@npm:0.3.3" +"eslint-import-resolver-node@npm:^0.3.9": + version: 0.3.9 + resolution: "eslint-import-resolver-node@npm:0.3.9" dependencies: - debug: "npm:^2.6.9" - resolve: "npm:^1.13.1" - checksum: 10/ce2dba73e5acdd2e833af771b6854afdd35bbb1f51553393de7064a1bdc0106238ba740616d7903fd7be4c71cc66300aa8da8976a0445c5718f77d0bf51e89cf + debug: "npm:^3.2.7" + is-core-module: "npm:^2.13.0" + resolve: "npm:^1.22.4" + checksum: 10/d52e08e1d96cf630957272e4f2644dcfb531e49dcfd1edd2e07e43369eb2ec7a7d4423d417beee613201206ff2efa4eb9a582b5825ee28802fc7c71fcd53ca83 languageName: node linkType: hard -"eslint-module-utils@npm:^2.4.1": - version: 2.5.2 - resolution: "eslint-module-utils@npm:2.5.2" +"eslint-module-utils@npm:^2.12.1": + version: 2.12.1 + resolution: "eslint-module-utils@npm:2.12.1" dependencies: - debug: "npm:^2.6.9" - pkg-dir: "npm:^2.0.0" - checksum: 10/b42bc52c32714c4cd1f3f3657b457a9630698e05a03955a175867f650916e5a04775a96159d3cb84a8c3b39f0297a708dc9eb382dc2091115e664708601c4c5d + debug: "npm:^3.2.7" + peerDependenciesMeta: + eslint: + optional: true + checksum: 10/bd25d6610ec3abaa50e8f1beb0119541562bbb8dd02c035c7e887976fe1e0c5dd8175f4607ca8d86d1146df24d52a071bd3d1dd329f6902bd58df805a8ca16d3 languageName: node linkType: hard "eslint-plugin-import@npm:^2.14.0": - version: 2.20.1 - resolution: "eslint-plugin-import@npm:2.20.1" - dependencies: - array-includes: "npm:^3.0.3" - array.prototype.flat: "npm:^1.2.1" - contains-path: "npm:^0.1.0" - debug: "npm:^2.6.9" - doctrine: "npm:1.5.0" - eslint-import-resolver-node: "npm:^0.3.2" - eslint-module-utils: "npm:^2.4.1" - has: "npm:^1.0.3" - minimatch: "npm:^3.0.4" - object.values: "npm:^1.1.0" - read-pkg-up: "npm:^2.0.0" - resolve: "npm:^1.12.0" + version: 2.32.0 + resolution: "eslint-plugin-import@npm:2.32.0" + dependencies: + "@rtsao/scc": "npm:^1.1.0" + array-includes: "npm:^3.1.9" + array.prototype.findlastindex: "npm:^1.2.6" + array.prototype.flat: "npm:^1.3.3" + array.prototype.flatmap: "npm:^1.3.3" + debug: "npm:^3.2.7" + doctrine: "npm:^2.1.0" + eslint-import-resolver-node: "npm:^0.3.9" + eslint-module-utils: "npm:^2.12.1" + hasown: "npm:^2.0.2" + is-core-module: "npm:^2.16.1" + is-glob: "npm:^4.0.3" + minimatch: "npm:^3.1.2" + object.fromentries: "npm:^2.0.8" + object.groupby: "npm:^1.0.3" + object.values: "npm:^1.2.1" + semver: "npm:^6.3.1" + string.prototype.trimend: "npm:^1.0.9" + tsconfig-paths: "npm:^3.15.0" peerDependencies: - eslint: 2.x - 6.x - checksum: 10/7cec73f8146fcd9c81421ab1e92b4f3d74885207759616eae901d9a036a06ae06e83671e56b079af7fda36057579c73ce9653472cb745f2a01c79ac23b3c89f7 + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + checksum: 10/1bacf4967e9ebf99e12176a795f0d6d3a87d1c9a030c2207f27b267e10d96a1220be2647504c7fc13ab543cdf13ffef4b8f5620e0447032dba4ff0d3922f7c9e languageName: node linkType: hard "eslint-scope@npm:^5.0.0": - version: 5.0.0 - resolution: "eslint-scope@npm:5.0.0" + version: 5.1.1 + resolution: "eslint-scope@npm:5.1.1" dependencies: - esrecurse: "npm:^4.1.0" + esrecurse: "npm:^4.3.0" estraverse: "npm:^4.1.1" - checksum: 10/28611a3b59bb3d71de2bfb03380f893ac7110620eceba5ab0953628f4950b7ee1dae5d9ef25fa4f328d1182204f50a2afa2e0d7a50d651b28030f5f71700315e + checksum: 10/c541ef384c92eb5c999b7d3443d80195fcafb3da335500946f6db76539b87d5826c8f2e1d23bf6afc3154ba8cd7c8e566f8dc00f1eea25fdf3afc8fb9c87b238 languageName: node linkType: hard @@ -2020,9 +2292,9 @@ __metadata: linkType: hard "eslint-visitor-keys@npm:^1.1.0": - version: 1.1.0 - resolution: "eslint-visitor-keys@npm:1.1.0" - checksum: 10/d9f1f2b5e0f8ccf7e0256db24723555ecbc15d049557464ae626f3e0fa812fd5a3718bf91ca21e34b3bb382da8488ea25756398c49c0531015380f685c7dc1ad + version: 1.3.0 + resolution: "eslint-visitor-keys@npm:1.3.0" + checksum: 10/595ab230e0fcb52f86ba0986a9a473b9fcae120f3729b43f1157f88f27f8addb1e545c4e3d444185f2980e281ca15be5ada6f65b4599eec227cf30e41233b762 languageName: node linkType: hard @@ -2074,13 +2346,13 @@ __metadata: linkType: hard "espree@npm:^6.1.2": - version: 6.2.0 - resolution: "espree@npm:6.2.0" + version: 6.2.1 + resolution: "espree@npm:6.2.1" dependencies: - acorn: "npm:^7.1.0" + acorn: "npm:^7.1.1" acorn-jsx: "npm:^5.2.0" eslint-visitor-keys: "npm:^1.1.0" - checksum: 10/9cfe958eaa16058290b44953d1125a1abc78e5d7a20bdca736884d09b0995cde1c2ecc9e64330d42910bb376c76796f54943c60eea43feccbd718215da983f6e + checksum: 10/e8b1edc0f8c6cdb1ef7c40e633ff1f1ea1585c46aa75c16f5525a3ca7f1a518197ad5fd40cedee31936ff4e1b5a396d585e6742e1f8a4c7dc2a17b3ed1d64c88 languageName: node linkType: hard @@ -2095,27 +2367,20 @@ __metadata: linkType: hard "esquery@npm:^1.0.1": - version: 1.1.0 - resolution: "esquery@npm:1.1.0" + version: 1.6.0 + resolution: "esquery@npm:1.6.0" dependencies: - estraverse: "npm:^4.0.0" - checksum: 10/4bfc69795c017996c94dc2627e90d8786c9f11a78af80d958c985ec08a58c1a53f92b67ee0a7c479db7a03ea179e350eb32500750acc11df4a328d565bae551e + estraverse: "npm:^5.1.0" + checksum: 10/c587fb8ec9ed83f2b1bc97cf2f6854cc30bf784a79d62ba08c6e358bf22280d69aee12827521cf38e69ae9761d23fb7fde593ce315610f85655c139d99b05e5a languageName: node linkType: hard -"esrecurse@npm:^4.1.0": - version: 4.2.1 - resolution: "esrecurse@npm:4.2.1" +"esrecurse@npm:^4.3.0": + version: 4.3.0 + resolution: "esrecurse@npm:4.3.0" dependencies: - estraverse: "npm:^4.1.0" - checksum: 10/5fa3c58456e6a4a079bbe87850431f7938a8516103e2478736b7b214a7ec595382772c7b7a2ee60ab72864a2c4900284937a4c972843154235ec2504d00e0dbb - languageName: node - linkType: hard - -"estraverse@npm:^4.0.0, estraverse@npm:^4.1.0": - version: 4.2.0 - resolution: "estraverse@npm:4.2.0" - checksum: 10/dd8d3e94b1be5ab32c345985bbad53f8d2df001da1b5d0bbcf00fc62ac9e1e7dc6398d16418f010c25af467b2db3b197b729415744f9f565c9546be8dc10a97e + estraverse: "npm:^5.2.0" + checksum: 10/44ffcd89e714ea6b30143e7f119b104fc4d75e77ee913f34d59076b40ef2d21967f84e019f84e1fd0465b42cdbf725db449f232b5e47f29df29ed76194db8e16 languageName: node linkType: hard @@ -2126,6 +2391,13 @@ __metadata: languageName: node linkType: hard +"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 10/37cbe6e9a68014d34dbdc039f90d0baf72436809d02edffcc06ba3c2a12eb298048f877511353b130153e532aac8d68ba78430c0dd2f44806ebc7c014b01585e + languageName: node + linkType: hard + "esutils@npm:^2.0.2": version: 2.0.3 resolution: "esutils@npm:2.0.3" @@ -2140,6 +2412,13 @@ __metadata: languageName: node linkType: hard +"exponential-backoff@npm:^3.1.1": + version: 3.1.2 + resolution: "exponential-backoff@npm:3.1.2" + checksum: 10/ca2f01f1aa4dafd3f3917bd531ab5be08c6f5f4b2389d2e974f903de3cbeb50b9633374353516b6afd70905775e33aba11afab1232d3acf0aa2963b98a611c51 + languageName: node + linkType: hard + "express@npm:^5.0.1": version: 5.1.0 resolution: "express@npm:5.1.0" @@ -2175,13 +2454,6 @@ __metadata: languageName: node linkType: hard -"extend@npm:^3.0.0": - version: 3.0.2 - resolution: "extend@npm:3.0.2" - checksum: 10/59e89e2dc798ec0f54b36d82f32a27d5f6472c53974f61ca098db5d4648430b725387b53449a34df38fd0392045434426b012f302b3cc049a6500ccf82877e4e - languageName: node - linkType: hard - "external-editor@npm:^3.0.3": version: 3.1.0 resolution: "external-editor@npm:3.1.0" @@ -2201,9 +2473,9 @@ __metadata: linkType: hard "fast-deep-equal@npm:^3.1.1": - version: 3.1.1 - resolution: "fast-deep-equal@npm:3.1.1" - checksum: 10/98bcc0eecef31601173aa82257f61c09789b3bd05673c0a602b449b70461ae087d6f38b3f77f9445ec79ab2f6c1ff8b6a525a2450b617b7f415a46b7c4ed691a + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10/e21a9d8d84f53493b6aa15efc9cfd53dd5b714a1f23f67fb5dc8f574af80df889b3bce25dc081887c6d25457cce704e636395333abad896ccdec03abaf1f3f9d languageName: node linkType: hard @@ -2228,6 +2500,18 @@ __metadata: languageName: node linkType: hard +"fdir@npm:^6.4.4": + version: 6.4.6 + resolution: "fdir@npm:6.4.6" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10/c186ba387e7b75ccf874a098d9bc5fe0af0e9c52fc56f8eac8e80aa4edb65532684bf2bf769894ff90f53bf221d6136692052d31f07a9952807acae6cbe7ee50 + languageName: node + linkType: hard + "figures@npm:^3.0.0": version: 3.2.0 resolution: "figures@npm:3.2.0" @@ -2256,12 +2540,12 @@ __metadata: languageName: node linkType: hard -"fill-range@npm:^7.0.1": - version: 7.0.1 - resolution: "fill-range@npm:7.0.1" +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" dependencies: to-regex-range: "npm:^5.0.1" - checksum: 10/e260f7592fd196b4421504d3597cc76f4a1ca7a9488260d533b611fc3cefd61e9a9be1417cb82d3b01ad9f9c0ff2dbf258e1026d2445e26b0cf5148ff4250429 + checksum: 10/a7095cb39e5bc32fada2aa7c7249d3f6b01bd1ce461a61b0adabacccabd9198500c6fb1f68a7c851a657e273fce2233ba869638897f3d7ed2e87a2d89b4436ea languageName: node linkType: hard @@ -2280,13 +2564,13 @@ __metadata: linkType: hard "find-cache-dir@npm:^3.2.0": - version: 3.3.0 - resolution: "find-cache-dir@npm:3.3.0" + version: 3.3.2 + resolution: "find-cache-dir@npm:3.3.2" dependencies: commondir: "npm:^1.0.1" make-dir: "npm:^3.0.2" pkg-dir: "npm:^4.1.0" - checksum: 10/50d9aafcfbb5731911d6adef98f339487dc4a6055ccbc4f0fe8a4f8086f06074abefce228c572b08da67c82194b16624e149ad0618001b8d3ff946da3afc8ec2 + checksum: 10/3907c2e0b15132704ed67083686cd3e68ab7d9ecc22e50ae9da20678245d488b01fa22c0e34c0544dc6edc4354c766f016c8c186a787be7c17f7cde8c5281e85 languageName: node linkType: hard @@ -2299,15 +2583,6 @@ __metadata: languageName: node linkType: hard -"find-up@npm:^2.0.0, find-up@npm:^2.1.0": - version: 2.1.0 - resolution: "find-up@npm:2.1.0" - dependencies: - locate-path: "npm:^2.0.0" - checksum: 10/43284fe4da09f89011f08e3c32cd38401e786b19226ea440b75386c1b12a4cb738c94969808d53a84f564ede22f732c8409e3cfc3f7fb5b5c32378ad0bbf28bd - languageName: node - linkType: hard - "find-up@npm:^4.0.0, find-up@npm:^4.1.0": version: 4.1.0 resolution: "find-up@npm:4.1.0" @@ -2339,9 +2614,9 @@ __metadata: linkType: hard "flatted@npm:^2.0.0": - version: 2.0.1 - resolution: "flatted@npm:2.0.1" - checksum: 10/251447389c2544aa44da1f025e98cdff728bc9cc0ccef8d92256568a3f7b868b895122d77dad138c788cd6917ba80236ddb723111fb688f30b298ad56bb2ce01 + version: 2.0.2 + resolution: "flatted@npm:2.0.2" + checksum: 10/473c754db7a529e125a22057098f1a4c905ba17b8cc269c3acf77352f0ffa6304c851eb75f6a1845f74461f560e635129ca6b0b8a78fb253c65cea4de3d776f2 languageName: node linkType: hard @@ -2352,6 +2627,15 @@ __metadata: languageName: node linkType: hard +"for-each@npm:^0.3.3, for-each@npm:^0.3.5": + version: 0.3.5 + resolution: "for-each@npm:0.3.5" + dependencies: + is-callable: "npm:^1.2.7" + checksum: 10/330cc2439f85c94f4609de3ee1d32c5693ae15cdd7fe3d112c4fd9efd4ce7143f2c64ef6c2c9e0cfdb0058437f33ef05b5bdae5b98fcc903fb2143fbaf0fea0f + languageName: node + linkType: hard + "foreground-child@npm:^2.0.0": version: 2.0.0 resolution: "foreground-child@npm:2.0.0" @@ -2362,44 +2646,38 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^2.3.1": - version: 2.5.1 - resolution: "form-data@npm:2.5.1" +"foreground-child@npm:^3.1.0": + version: 3.3.1 + resolution: "foreground-child@npm:3.3.1" dependencies: - asynckit: "npm:^0.4.0" - combined-stream: "npm:^1.0.6" - mime-types: "npm:^2.1.12" - checksum: 10/2e2e5e927979ba3623f9b4c4bcc939275fae3f2dea9dafc8db3ca656a3d75476605de2c80f0e6f1487987398e056f0b4c738972d6e1edd83392d5686d0952eed + cross-spawn: "npm:^7.0.6" + signal-exit: "npm:^4.0.1" + checksum: 10/427b33f997a98073c0424e5c07169264a62cda806d8d2ded159b5b903fdfc8f0a1457e06b5fc35506497acb3f1e353f025edee796300209ac6231e80edece835 languageName: node linkType: hard -"form-data@npm:^4.0.0": - version: 4.0.0 - resolution: "form-data@npm:4.0.0" +"form-data@npm:^4.0.0, form-data@npm:^4.0.4": + version: 4.0.4 + resolution: "form-data@npm:4.0.4" dependencies: asynckit: "npm:^0.4.0" combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + hasown: "npm:^2.0.2" mime-types: "npm:^2.1.12" - checksum: 10/7264aa760a8cf09482816d8300f1b6e2423de1b02bba612a136857413fdc96d7178298ced106817655facc6b89036c6e12ae31c9eb5bdc16aabf502ae8a5d805 - languageName: node - linkType: hard - -"formidable@npm:^1.2.0": - version: 1.2.2 - resolution: "formidable@npm:1.2.2" - checksum: 10/85fc8e5adaa2234052f012e34e98841392d9b760eabf2717b7308d0ddfab4512202c5d284f50e33e66157421db2759822e9edb88390c81ffa516a439e826c69c + checksum: 10/a4b62e21932f48702bc468cc26fb276d186e6b07b557e3dd7cc455872bdbb82db7db066844a64ad3cf40eaf3a753c830538183570462d3649fdfd705601cbcfb languageName: node linkType: hard -"formidable@npm:^2.0.1": - version: 2.1.2 - resolution: "formidable@npm:2.1.2" +"formidable@npm:^2.1.2": + version: 2.1.5 + resolution: "formidable@npm:2.1.5" dependencies: + "@paralleldrive/cuid2": "npm:^2.2.2" dezalgo: "npm:^1.0.4" - hexoid: "npm:^1.0.0" once: "npm:^1.4.0" qs: "npm:^6.11.0" - checksum: 10/d385180e0461f65e6f7b70452859fe1c32aa97a290c2ca33f00cdc33145ef44fa68bbc9b93af2c3af73ae726e42c3477c6619c49f3c34b49934e9481275b7b4c + checksum: 10/ee96de12e91d63fe86479ffe5bf59004bb3f43e00ce7ccecd1b1ff10b5d1a89a19b1ede727e1fe57ef596c377b9f9300212a5f7bab14fd28f3c4ffe12dbb4cc7 languageName: node linkType: hard @@ -2436,18 +2714,18 @@ __metadata: linkType: hard "fromentries@npm:^1.2.0": - version: 1.2.0 - resolution: "fromentries@npm:1.2.0" - checksum: 10/89d38a4a4df72ae8d60a4ff0e3ee089d6257b60b359122c76fda6e3e1b1d9a743d669b8054c7b55df5a392bbf6daa53970c5c1022a44f2f559c732ebf3de5e34 + version: 1.3.2 + resolution: "fromentries@npm:1.3.2" + checksum: 10/10d6e07d289db102c0c1eaf5c3e3fa55ddd6b50033d7de16d99a7cd89f1e1a302dfadb26457031f9bb5d2ed95a179aaf0396092dde5abcae06e8a2f0476826be languageName: node linkType: hard -"fs-minipass@npm:^2.0.0, fs-minipass@npm:^2.1.0": - version: 2.1.0 - resolution: "fs-minipass@npm:2.1.0" +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" dependencies: - minipass: "npm:^3.0.0" - checksum: 10/03191781e94bc9a54bd376d3146f90fe8e082627c502185dbf7b9b3032f66b0b142c1115f3b2cc5936575fc1b44845ce903dd4c21bec2a8d69f3bd56f9cee9ec + minipass: "npm:^7.0.3" + checksum: 10/af143246cf6884fe26fa281621d45cfe111d34b30535a475bfa38dafe343dadb466c047a924ffc7d6b7b18265df4110224ce3803806dbb07173bf2087b648d7f languageName: node linkType: hard @@ -2459,18 +2737,18 @@ __metadata: linkType: hard "fsevents@npm:~2.1.1": - version: 2.1.2 - resolution: "fsevents@npm:2.1.2" + version: 2.1.3 + resolution: "fsevents@npm:2.1.3" dependencies: node-gyp: "npm:latest" - checksum: 10/dbd8ea22e5c44c76827610e8d06b1a560fc6b80f53649bd555a54280f2eec54a200327d1a023da63e3c19a00f996bd56fbb52f21e2575694a714a470ba6069b1 + checksum: 10/b604991f31d9ec772e278831bbe069eed8b6824b09b707eeb5c792ceb79fafa9db377981acf7555deab8f5818a75e5487d37b366f55e31d6ea62ea0e06fc777b conditions: os=darwin languageName: node linkType: hard "fsevents@patch:fsevents@npm%3A~2.1.1#optional!builtin": - version: 2.1.2 - resolution: "fsevents@patch:fsevents@npm%3A2.1.2#optional!builtin::version=2.1.2&hash=31d12a" + version: 2.1.3 + resolution: "fsevents@patch:fsevents@npm%3A2.1.3#optional!builtin::version=2.1.3&hash=31d12a" dependencies: node-gyp: "npm:latest" conditions: os=darwin @@ -2484,6 +2762,20 @@ __metadata: languageName: node linkType: hard +"function.prototype.name@npm:^1.1.6, function.prototype.name@npm:^1.1.8": + version: 1.1.8 + resolution: "function.prototype.name@npm:1.1.8" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + functions-have-names: "npm:^1.2.3" + hasown: "npm:^2.0.2" + is-callable: "npm:^1.2.7" + checksum: 10/25b9e5bea936732a6f0c0c08db58cc0d609ac1ed458c6a07ead46b32e7b9bf3fe5887796c3f83d35994efbc4fdde81c08ac64135b2c399b8f2113968d44082bc + languageName: node + linkType: hard + "functional-red-black-tree@npm:^1.0.1": version: 1.0.1 resolution: "functional-red-black-tree@npm:1.0.1" @@ -2491,19 +2783,10 @@ __metadata: languageName: node linkType: hard -"gauge@npm:^4.0.3": - version: 4.0.4 - resolution: "gauge@npm:4.0.4" - dependencies: - aproba: "npm:^1.0.3 || ^2.0.0" - color-support: "npm:^1.1.3" - console-control-strings: "npm:^1.1.0" - has-unicode: "npm:^2.0.1" - signal-exit: "npm:^3.0.7" - string-width: "npm:^4.2.3" - strip-ansi: "npm:^6.0.1" - wide-align: "npm:^1.1.5" - checksum: 10/09535dd53b5ced6a34482b1fa9f3929efdeac02f9858569cde73cef3ed95050e0f3d095706c1689614059898924b7a74aa14042f51381a1ccc4ee5c29d2389c4 +"functions-have-names@npm:^1.2.3": + version: 1.2.3 + resolution: "functions-have-names@npm:1.2.3" + checksum: 10/0ddfd3ed1066a55984aaecebf5419fbd9344a5c38dd120ffb0739fac4496758dcf371297440528b115e4367fc46e3abc86a2cc0ff44612181b175ae967a11a05 languageName: node linkType: hard @@ -2516,10 +2799,10 @@ __metadata: languageName: node linkType: hard -"gensync@npm:^1.0.0-beta.1": - version: 1.0.0-beta.1 - resolution: "gensync@npm:1.0.0-beta.1" - checksum: 10/7d3cb8640977103fbeba7e3533bfe5ec7af70b73f91aaf41766eea8bcd3da7d3f482800ee5ad9801ac06025d8815568a3a8017c84f349cc53406093d61a110dd +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: 10/17d8333460204fbf1f9160d067e1e77f908a5447febb49424b8ab043026049835c9ef3974445c57dbd39161f4d2b04356d7de12b2eecaa27a7a7ea7d871cbedd languageName: node linkType: hard @@ -2537,21 +2820,56 @@ __metadata: languageName: node linkType: hard -"get-func-name@npm:^2.0.0": - version: 2.0.0 - resolution: "get-func-name@npm:2.0.0" - checksum: 10/8d82e69f3e7fab9e27c547945dfe5cc0c57fc0adf08ce135dddb01081d75684a03e7a0487466f478872b341d52ac763ae49e660d01ab83741f74932085f693c3 +"get-func-name@npm:^2.0.1, get-func-name@npm:^2.0.2": + version: 2.0.2 + resolution: "get-func-name@npm:2.0.2" + checksum: 10/3f62f4c23647de9d46e6f76d2b3eafe58933a9b3830c60669e4180d6c601ce1b4aa310ba8366143f55e52b139f992087a9f0647274e8745621fa2af7e0acf13b languageName: node linkType: hard -"get-intrinsic@npm:^1.0.2": - version: 1.1.2 - resolution: "get-intrinsic@npm:1.1.2" +"get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0": + version: 1.3.0 + resolution: "get-intrinsic@npm:1.3.0" dependencies: - function-bind: "npm:^1.1.1" - has: "npm:^1.0.3" - has-symbols: "npm:^1.0.3" - checksum: 10/0364e4d4538486672d3125ca6e3e3ce30f1ac0eebfbaed1ffb27f588697a49b9d8ccf9e9fc30b915663942f5c24063cfd81008d13d02c9358f72b3c70b4c74f4 + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10/6e9dd920ff054147b6f44cb98104330e87caafae051b6d37b13384a45ba15e71af33c3baeac7cb630a0aaa23142718dcf25b45cfdd86c184c5dcb4e56d953a10 + languageName: node + linkType: hard + +"get-package-type@npm:^0.1.0": + version: 0.1.0 + resolution: "get-package-type@npm:0.1.0" + checksum: 10/bba0811116d11e56d702682ddef7c73ba3481f114590e705fc549f4d868972263896af313c57a25c076e3c0d567e11d919a64ba1b30c879be985fc9d44f96148 + languageName: node + linkType: hard + +"get-proto@npm:^1.0.0, get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10/4fc96afdb58ced9a67558698b91433e6b037aaa6f1493af77498d7c85b141382cf223c0e5946f334fb328ee85dfe6edd06d218eaf09556f4bc4ec6005d7f5f7b + languageName: node + linkType: hard + +"get-symbol-description@npm:^1.1.0": + version: 1.1.0 + resolution: "get-symbol-description@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + checksum: 10/a353e3a9595a74720b40fb5bae3ba4a4f826e186e83814d93375182384265676f59e49998b9cdfac4a2225ce95a3d32a68f502a2c5619303987f1c183ab80494 languageName: node linkType: hard @@ -2578,30 +2896,33 @@ __metadata: languageName: node linkType: hard -"glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": - version: 7.1.6 - resolution: "glob@npm:7.1.6" +"glob@npm:^10.2.2": + version: 10.4.5 + resolution: "glob@npm:10.4.5" dependencies: - fs.realpath: "npm:^1.0.0" - inflight: "npm:^1.0.4" - inherits: "npm:2" - minimatch: "npm:^3.0.4" - once: "npm:^1.3.0" - path-is-absolute: "npm:^1.0.0" - checksum: 10/7d6ec98bc746980d5fe4d764b9c7ada727e3fbd2a7d85cd96dd95fb18638c9c54a70c692fd2ab5d68a186dc8cd9d6a4192d3df220beed891f687db179c430237 + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10/698dfe11828b7efd0514cd11e573eaed26b2dff611f0400907281ce3eab0c1e56143ef9b35adc7c77ecc71fba74717b510c7c223d34ca8a98ec81777b293d4ac languageName: node linkType: hard -"glob@npm:^8.0.1": - version: 8.1.0 - resolution: "glob@npm:8.1.0" +"glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": + version: 7.2.3 + resolution: "glob@npm:7.2.3" dependencies: fs.realpath: "npm:^1.0.0" inflight: "npm:^1.0.4" inherits: "npm:2" - minimatch: "npm:^5.0.1" + minimatch: "npm:^3.1.1" once: "npm:^1.3.0" - checksum: 10/9aab1c75eb087c35dbc41d1f742e51d0507aa2b14c910d96fb8287107a10a22f4bbdce26fc0a3da4c69a20f7b26d62f1640b346a4f6e6becfff47f335bb1dc5e + path-is-absolute: "npm:^1.0.0" + checksum: 10/59452a9202c81d4508a43b8af7082ca5c76452b9fcc4a9ab17655822e6ce9b21d4f8fbadabe4fe3faef448294cec249af305e2cd824b7e9aaf689240e5e96a7b languageName: node linkType: hard @@ -2619,61 +2940,50 @@ __metadata: languageName: node linkType: hard -"globals@npm:^11.1.0": - version: 11.12.0 - resolution: "globals@npm:11.12.0" - checksum: 10/9f054fa38ff8de8fa356502eb9d2dae0c928217b8b5c8de1f09f5c9b6c8a96d8b9bd3afc49acbcd384a98a81fea713c859e1b09e214c60509517bb8fc2bc13c2 - languageName: node - linkType: hard - "globals@npm:^12.1.0": - version: 12.3.0 - resolution: "globals@npm:12.3.0" + version: 12.4.0 + resolution: "globals@npm:12.4.0" dependencies: type-fest: "npm:^0.8.1" - checksum: 10/283eb0cba249104dffd3bb1ac4314c21fe90e45d0620f5b84745ca5f64230cd698b48aa81218ef94a6c6fb2c023b7564b592140883d353d5cedec0d1f3230e7a + checksum: 10/11b38ef0077f5d8d616b1bc5effac20667247fc42c65c6f8fac4fc3758cd14ad73ccd36eff376c29ef395caf5f4c33e8460b78f79c37ce44cf3ab568a3b7623b + languageName: node + linkType: hard + +"globalthis@npm:^1.0.4": + version: 1.0.4 + resolution: "globalthis@npm:1.0.4" + dependencies: + define-properties: "npm:^1.2.1" + gopd: "npm:^1.0.1" + checksum: 10/1f1fd078fb2f7296306ef9dd51019491044ccf17a59ed49d375b576ca108ff37e47f3d29aead7add40763574a992f16a5367dd1e2173b8634ef18556ab719ac4 languageName: node linkType: hard "globule@npm:^1.0.0": - version: 1.3.1 - resolution: "globule@npm:1.3.1" + version: 1.3.4 + resolution: "globule@npm:1.3.4" dependencies: glob: "npm:~7.1.1" - lodash: "npm:~4.17.12" + lodash: "npm:^4.17.21" minimatch: "npm:~3.0.2" - checksum: 10/48ddbb091a25524e1a32d21d03c935ba1259dcd2ef08a74a8d798862c022378045c8aba6338fab66fdff3bd58842c0871c33a1184d2ca76fb13d3244da4e961a + checksum: 10/04ac30656f9fc34e7e30a700ef39bfc357629a9214e2e228ee714bc0f1be60c5e4e2a78facafa5588889b02d25f02012d9e8c057704040e19e86b920effe54d5 languageName: node linkType: hard -"gopd@npm:^1.0.1": +"gopd@npm:^1.0.1, gopd@npm:^1.2.0": version: 1.2.0 resolution: "gopd@npm:1.2.0" checksum: 10/94e296d69f92dc1c0768fcfeecfb3855582ab59a7c75e969d5f96ce50c3d201fd86d5a2857c22565764d5bb8a816c7b1e58f133ec318cd56274da36c5e3fb1a1 languageName: node linkType: hard -"graceful-fs@npm:^4.1.15": +"graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 languageName: node linkType: hard -"graceful-fs@npm:^4.1.2": - version: 4.2.3 - resolution: "graceful-fs@npm:4.2.3" - checksum: 10/393a22c479c62d4c52798640c56a8c09ba4161aeea3cfd3c860b806bfd91e31974f6aef605f098505916d63c6141c8a58685c26ffd22935e492317e74d0369ab - languageName: node - linkType: hard - -"graceful-fs@npm:^4.2.6": - version: 4.2.10 - resolution: "graceful-fs@npm:4.2.10" - checksum: 10/0c83c52b62c68a944dcfb9d66b0f9f10f7d6e3d081e8067b9bfdc9e5f3a8896584d576036f82915773189eec1eba599397fc620e75c03c0610fb3d67c6713c1a - languageName: node - linkType: hard - "growl@npm:1.10.5, growl@npm:^1.10.5": version: 1.10.5 resolution: "growl@npm:1.10.5" @@ -2681,6 +2991,13 @@ __metadata: languageName: node linkType: hard +"has-bigints@npm:^1.0.2": + version: 1.1.0 + resolution: "has-bigints@npm:1.1.0" + checksum: 10/90fb1b24d40d2472bcd1c8bd9dd479037ec240215869bdbff97b2be83acef57d28f7e96bdd003a21bed218d058b49097f4acc8821c05b1629cc5d48dd7bfcccd + languageName: node + linkType: hard + "has-flag@npm:^3.0.0": version: 3.0.0 resolution: "has-flag@npm:3.0.0" @@ -2695,7 +3012,7 @@ __metadata: languageName: node linkType: hard -"has-property-descriptors@npm:^1.0.0": +"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2": version: 1.0.2 resolution: "has-property-descriptors@npm:1.0.2" dependencies: @@ -2704,50 +3021,38 @@ __metadata: languageName: node linkType: hard -"has-symbols@npm:^1.0.0": - version: 1.1.0 - resolution: "has-symbols@npm:1.1.0" - checksum: 10/959385c98696ebbca51e7534e0dc723ada325efa3475350951363cce216d27373e0259b63edb599f72eb94d6cde8577b4b2375f080b303947e560f85692834fa - languageName: node - linkType: hard - -"has-symbols@npm:^1.0.1": - version: 1.0.1 - resolution: "has-symbols@npm:1.0.1" - checksum: 10/d7a6d0b8f2b4595d6d5aafd4e020f65785779a654b52b77457f69c33e2c36400780ece296b964ae885714e4c83b503b01e2024d682d95794628d9c5a83c113bf - languageName: node - linkType: hard - -"has-symbols@npm:^1.0.3": - version: 1.0.3 - resolution: "has-symbols@npm:1.0.3" - checksum: 10/464f97a8202a7690dadd026e6d73b1ceeddd60fe6acfd06151106f050303eaa75855aaa94969df8015c11ff7c505f196114d22f7386b4a471038da5874cf5e9b +"has-proto@npm:^1.2.0": + version: 1.2.0 + resolution: "has-proto@npm:1.2.0" + dependencies: + dunder-proto: "npm:^1.0.0" + checksum: 10/7eaed07728eaa28b77fadccabce53f30de467ff186a766872669a833ac2e87d8922b76a22cc58339d7e0277aefe98d6d00762113b27a97cdf65adcf958970935 languageName: node linkType: hard -"has-unicode@npm:^2.0.1": - version: 2.0.1 - resolution: "has-unicode@npm:2.0.1" - checksum: 10/041b4293ad6bf391e21c5d85ed03f412506d6623786b801c4ab39e4e6ca54993f13201bceb544d92963f9e0024e6e7fbf0cb1d84c9d6b31cb9c79c8c990d13d8 +"has-symbols@npm:^1.0.0, has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10/959385c98696ebbca51e7534e0dc723ada325efa3475350951363cce216d27373e0259b63edb599f72eb94d6cde8577b4b2375f080b303947e560f85692834fa languageName: node linkType: hard -"has@npm:^1.0.3": - version: 1.0.3 - resolution: "has@npm:1.0.3" +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" dependencies: - function-bind: "npm:^1.1.1" - checksum: 10/a449f3185b1d165026e8d25f6a8c3390bd25c201ff4b8c1aaf948fc6a5fcfd6507310b8c00c13a3325795ea9791fcc3d79d61eafa313b5750438fc19183df57b + has-symbols: "npm:^1.0.3" + checksum: 10/c74c5f5ceee3c8a5b8bc37719840dc3749f5b0306d818974141dda2471a1a2ca6c8e46b9d6ac222c5345df7a901c9b6f350b1e6d62763fec877e26609a401bfe languageName: node linkType: hard "hasha@npm:^5.0.0": - version: 5.2.0 - resolution: "hasha@npm:5.2.0" + version: 5.2.2 + resolution: "hasha@npm:5.2.2" dependencies: is-stream: "npm:^2.0.0" type-fest: "npm:^0.8.0" - checksum: 10/8b3adbdac6df8132a4337454cb2050dde932d2313fbb50ebbe2cd4ac4cb1e9480d80906c2134ecd7ccd343c33add37da9f3ac5828c025513b4c764cd4d22dd79 + checksum: 10/06cc474bed246761ff61c19d629977eb5f53fa817be4313a255a64ae0f433e831a29e83acb6555e3f4592b348497596f1d1653751008dda4f21c9c21ca60ac5a languageName: node linkType: hard @@ -2769,33 +3074,17 @@ __metadata: languageName: node linkType: hard -"hexoid@npm:^1.0.0": - version: 1.0.0 - resolution: "hexoid@npm:1.0.0" - checksum: 10/f2271b8b6b0e13fb5a1eccf740f53ce8bae689c80b9498b854c447f9dc94f75f44e0de064c0e4660ecdbfa8942bb2b69973fdcb080187b45bbb409a3c71f19d4 - languageName: node - linkType: hard - -"hosted-git-info@npm:^3.0.8": - version: 3.0.8 - resolution: "hosted-git-info@npm:3.0.8" - dependencies: - lru-cache: "npm:^6.0.0" - checksum: 10/fac26fe551d87f271b31e80e5a7519cbb50a3c30ea89cad734da8068930f27288a049258e6ed9c39e20ebec9cf4b67c5cb02055bd73230962ef34db0d45da3e7 - languageName: node - linkType: hard - "html-escaper@npm:^2.0.0": - version: 2.0.0 - resolution: "html-escaper@npm:2.0.0" - checksum: 10/3e16f5672758645aaaf37d25a644d05138729442a48e33a5489c28200d29ea38038f9ab0df4e60160bff55d54e0fe388be7bbc7ca6f751dbd903db9f33dccc80 + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 10/034d74029dcca544a34fb6135e98d427acd73019796ffc17383eaa3ec2fe1c0471dcbbc8f8ed39e46e86d43ccd753a160631615e4048285e313569609b66d5b7 languageName: node linkType: hard -"http-cache-semantics@npm:^4.1.0": - version: 4.1.1 - resolution: "http-cache-semantics@npm:4.1.1" - checksum: 10/362d5ed66b12ceb9c0a328fb31200b590ab1b02f4a254a697dc796850cc4385603e75f53ec59f768b2dad3bfa1464bd229f7de278d2899a0e3beffc634b6683f +"http-cache-semantics@npm:^4.1.1": + version: 4.2.0 + resolution: "http-cache-semantics@npm:4.2.0" + checksum: 10/4efd2dfcfeea9d5e88c84af450b9980be8a43c2c8179508b1c57c7b4421c855f3e8efe92fa53e0b3f4a43c85824ada930eabbc306d1b3beab750b6dcc5187693 languageName: node linkType: hard @@ -2813,25 +3102,14 @@ __metadata: linkType: hard "http-errors@npm:~1.6.1": - version: 1.6.2 - resolution: "http-errors@npm:1.6.2" + version: 1.6.3 + resolution: "http-errors@npm:1.6.3" dependencies: - depd: "npm:1.1.1" + depd: "npm:~1.1.2" inherits: "npm:2.0.3" - setprototypeof: "npm:1.0.3" - statuses: "npm:>= 1.3.1 < 2" - checksum: 10/eea1c804526b60f5c3ca85f894ae05eba1f4450a8cb41d397b43d8f72b85bea73912050db852753b6ba25b176dece90d7b648f39651eb9a1bc449c3eb9d4c58c - languageName: node - linkType: hard - -"http-proxy-agent@npm:^5.0.0": - version: 5.0.0 - resolution: "http-proxy-agent@npm:5.0.0" - dependencies: - "@tootallnate/once": "npm:2" - agent-base: "npm:6" - debug: "npm:4" - checksum: 10/5ee19423bc3e0fd5f23ce991b0755699ad2a46a440ce9cec99e8126bb98448ad3479d2c0ea54be5519db5b19a4ffaa69616bac01540db18506dd4dac3dc418f0 + setprototypeof: "npm:1.1.0" + statuses: "npm:>= 1.4.0 < 2" + checksum: 10/e48732657ea0b4a09853d2696a584fa59fa2a8c1ba692af7af3137b5491a997d7f9723f824e7e08eb6a87098532c09ce066966ddf0f9f3dd30905e52301acadb languageName: node linkType: hard @@ -2845,17 +3123,7 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:^5.0.0": - version: 5.0.1 - resolution: "https-proxy-agent@npm:5.0.1" - dependencies: - agent-base: "npm:6" - debug: "npm:4" - checksum: 10/f0dce7bdcac5e8eaa0be3c7368bb8836ed010fb5b6349ffb412b172a203efe8f807d9a6681319105ea1b6901e1972c7b5ea899672a7b9aad58309f766dcbe0df - languageName: node - linkType: hard - -"https-proxy-agent@npm:^7.0.0": +"https-proxy-agent@npm:^7.0.0, https-proxy-agent@npm:^7.0.1": version: 7.0.6 resolution: "https-proxy-agent@npm:7.0.6" dependencies: @@ -2865,15 +3133,6 @@ __metadata: languageName: node linkType: hard -"humanize-ms@npm:^1.2.1": - version: 1.2.1 - resolution: "humanize-ms@npm:1.2.1" - dependencies: - ms: "npm:^2.0.0" - checksum: 10/9c7a74a2827f9294c009266c82031030eae811ca87b0da3dceb8d6071b9bde22c9f3daef0469c3c533cc67a97d8a167cd9fc0389350e5f415f61a79b171ded16 - languageName: node - linkType: hard - "iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" @@ -2900,12 +3159,12 @@ __metadata: linkType: hard "import-fresh@npm:^3.0.0": - version: 3.2.1 - resolution: "import-fresh@npm:3.2.1" + version: 3.3.1 + resolution: "import-fresh@npm:3.3.1" dependencies: parent-module: "npm:^1.0.0" resolve-from: "npm:^4.0.0" - checksum: 10/caef42418a087c3951fb676943a7f21ba8971aa07f9b622dff4af7edcef4160e1b172dccd85a88d7eb109cf41406a4592f70259e6b3b33aeafd042bb61f81d96 + checksum: 10/a06b19461b4879cc654d46f8a6244eb55eb053437afd4cbb6613cad6be203811849ed3e4ea038783092879487299fda24af932b86bdfff67c9055ba3612b8c87 languageName: node linkType: hard @@ -2935,13 +3194,6 @@ __metadata: languageName: node linkType: hard -"infer-owner@npm:^1.0.4": - version: 1.0.4 - resolution: "infer-owner@npm:1.0.4" - checksum: 10/181e732764e4a0611576466b4b87dac338972b839920b2a8cde43642e4ed6bd54dc1fb0b40874728f2a2df9a1b097b8ff83b56d5f8f8e3927f837fdcb47d8a89 - languageName: node - linkType: hard - "inflight@npm:^1.0.4": version: 1.0.6 resolution: "inflight@npm:1.0.6" @@ -2952,14 +3204,14 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:~2.0.3": +"inherits@npm:2, inherits@npm:2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 languageName: node linkType: hard -"inherits@npm:2.0.3, inherits@npm:^2.0.3": +"inherits@npm:2.0.3": version: 2.0.3 resolution: "inherits@npm:2.0.3" checksum: 10/8771303d66c51be433b564427c16011a8e3fbc3449f1f11ea50efb30a4369495f1d0e89f0fc12bdec0bd7e49102ced5d137e031d39ea09821cb3c717fcf21e69 @@ -2967,23 +3219,34 @@ __metadata: linkType: hard "inquirer@npm:^7.0.0": - version: 7.0.5 - resolution: "inquirer@npm:7.0.5" + version: 7.3.3 + resolution: "inquirer@npm:7.3.3" dependencies: ansi-escapes: "npm:^4.2.1" - chalk: "npm:^3.0.0" + chalk: "npm:^4.1.0" cli-cursor: "npm:^3.1.0" - cli-width: "npm:^2.0.0" + cli-width: "npm:^3.0.0" external-editor: "npm:^3.0.3" figures: "npm:^3.0.0" - lodash: "npm:^4.17.15" + lodash: "npm:^4.17.19" mute-stream: "npm:0.0.8" run-async: "npm:^2.4.0" - rxjs: "npm:^6.5.3" + rxjs: "npm:^6.6.0" string-width: "npm:^4.1.0" strip-ansi: "npm:^6.0.0" through: "npm:^2.3.6" - checksum: 10/467adf9fae1f28ba3d8873f435deeab2a16261eb14b68acbc6ae0028c58a0f00c31ecf718ee2ae300d70423e36d4a796cd89e6d3092856f45b680c66ddbb8e2c + checksum: 10/052c6fce2d467343ced6500080b4b70eaf2ca996933fc3b5c9b0dd1ea275dd9c2a1070880f5f163f42bd13acf25c1ab8ab384444c1a413050db34aab69112583 + languageName: node + linkType: hard + +"internal-slot@npm:^1.1.0": + version: 1.1.0 + resolution: "internal-slot@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + hasown: "npm:^2.0.2" + side-channel: "npm:^1.1.0" + checksum: 10/1d5219273a3dab61b165eddf358815eefc463207db33c20fcfca54717da02e3f492003757721f972fd0bf21e4b426cab389c5427b99ceea4b8b670dc88ee6d4a languageName: node linkType: hard @@ -3018,6 +3281,16 @@ __metadata: languageName: node linkType: hard +"ip-address@npm:^9.0.5": + version: 9.0.5 + resolution: "ip-address@npm:9.0.5" + dependencies: + jsbn: "npm:1.1.0" + sprintf-js: "npm:^1.1.3" + checksum: 10/1ed81e06721af012306329b31f532b5e24e00cb537be18ddc905a84f19fe8f83a09a1699862bf3a1ec4b9dea93c55a3fa5faf8b5ea380431469df540f38b092c + languageName: node + linkType: hard + "ip-regex@npm:^2.0.0": version: 2.1.0 resolution: "ip-regex@npm:2.1.0" @@ -3025,13 +3298,6 @@ __metadata: languageName: node linkType: hard -"ip@npm:^2.0.0": - version: 2.0.0 - resolution: "ip@npm:2.0.0" - checksum: 10/1270b11e534a466fb4cf4426cbcc3a907c429389f7f4e4e3b288b42823562e88d6a509ceda8141a507de147ca506141f745005c0aa144569d94cf24a54eb52bc - languageName: node - linkType: hard - "ipaddr.js@npm:1.9.1": version: 1.9.1 resolution: "ipaddr.js@npm:1.9.1" @@ -3039,10 +3305,36 @@ __metadata: languageName: node linkType: hard -"is-arrayish@npm:^0.2.1": - version: 0.2.1 - resolution: "is-arrayish@npm:0.2.1" - checksum: 10/73ced84fa35e59e2c57da2d01e12cd01479f381d7f122ce41dcbb713f09dbfc651315832cd2bf8accba7681a69e4d6f1e03941d94dd10040d415086360e7005e +"is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5": + version: 3.0.5 + resolution: "is-array-buffer@npm:3.0.5" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + get-intrinsic: "npm:^1.2.6" + checksum: 10/ef1095c55b963cd0dcf6f88a113e44a0aeca91e30d767c475e7d746d28d1195b10c5076b94491a7a0cd85020ca6a4923070021d74651d093dc909e9932cf689b + languageName: node + linkType: hard + +"is-async-function@npm:^2.0.0": + version: 2.1.1 + resolution: "is-async-function@npm:2.1.1" + dependencies: + async-function: "npm:^1.0.0" + call-bound: "npm:^1.0.3" + get-proto: "npm:^1.0.1" + has-tostringtag: "npm:^1.0.2" + safe-regex-test: "npm:^1.1.0" + checksum: 10/7c2ac7efdf671e03265e74a043bcb1c0a32e226bc2a42dfc5ec8644667df668bbe14b91c08e6c1414f392f8cf86cd1d489b3af97756e2c7a49dd1ba63fd40ca6 + languageName: node + linkType: hard + +"is-bigint@npm:^1.1.0": + version: 1.1.0 + resolution: "is-bigint@npm:1.1.0" + dependencies: + has-bigints: "npm:^1.0.2" + checksum: 10/10cf327310d712fe227cfaa32d8b11814c214392b6ac18c827f157e1e85363cf9c8e2a22df526689bd5d25e53b58cc110894787afb54e138e7c504174dba15fd languageName: node linkType: hard @@ -3055,14 +3347,24 @@ __metadata: languageName: node linkType: hard -"is-callable@npm:^1.1.4, is-callable@npm:^1.1.5": - version: 1.1.5 - resolution: "is-callable@npm:1.1.5" - checksum: 10/eca402fa6a24b82a52d85ff494c8fa376fcdd5555d946f2ef9a3cd12d400ec381787171800e3ee8465221f018aad853026e40dbf01e92d9eabb825fafbe0d93b +"is-boolean-object@npm:^1.2.1": + version: 1.2.2 + resolution: "is-boolean-object@npm:1.2.2" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10/051fa95fdb99d7fbf653165a7e6b2cba5d2eb62f7ffa81e793a790f3fb5366c91c1b7b6af6820aa2937dd86c73aa3ca9d9ca98f500988457b1c59692c52ba911 + languageName: node + linkType: hard + +"is-callable@npm:^1.2.7": + version: 1.2.7 + resolution: "is-callable@npm:1.2.7" + checksum: 10/48a9297fb92c99e9df48706241a189da362bff3003354aea4048bd5f7b2eb0d823cd16d0a383cece3d76166ba16d85d9659165ac6fcce1ac12e6c649d66dbdb9 languageName: node linkType: hard -"is-core-module@npm:^2.16.0": +"is-core-module@npm:^2.13.0, is-core-module@npm:^2.16.0, is-core-module@npm:^2.16.1": version: 2.16.1 resolution: "is-core-module@npm:2.16.1" dependencies: @@ -3071,10 +3373,24 @@ __metadata: languageName: node linkType: hard -"is-date-object@npm:^1.0.1": +"is-data-view@npm:^1.0.1, is-data-view@npm:^1.0.2": version: 1.0.2 - resolution: "is-date-object@npm:1.0.2" - checksum: 10/96c56c04631f866b3a3aea4b889eac6120c13d8a06dc7e105479ffd6f57e5ea3668f1d779ef30063d4b27aa8e9b235ea7d15bbdab54b056affc678c4769ff143 + resolution: "is-data-view@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + get-intrinsic: "npm:^1.2.6" + is-typed-array: "npm:^1.1.13" + checksum: 10/357e9a48fa38f369fd6c4c3b632a3ab2b8adca14997db2e4b3fe94c4cd0a709af48e0fb61b02c64a90c0dd542fd489d49c2d03157b05ae6c07f5e4dec9e730a8 + languageName: node + linkType: hard + +"is-date-object@npm:^1.0.5, is-date-object@npm:^1.1.0": + version: 1.1.0 + resolution: "is-date-object@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.2" + has-tostringtag: "npm:^1.0.2" + checksum: 10/3a811b2c3176fb31abee1d23d3dc78b6c65fd9c07d591fcb67553cab9e7f272728c3dd077d2d738b53f9a2103255b0a6e8dfc9568a7805c56a78b2563e8d1dec languageName: node linkType: hard @@ -3085,6 +3401,15 @@ __metadata: languageName: node linkType: hard +"is-finalizationregistry@npm:^1.1.0": + version: 1.1.1 + resolution: "is-finalizationregistry@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10/0bfb145e9a1ba852ddde423b0926d2169ae5fe9e37882cde9e8f69031281a986308df4d982283e152396e88b86562ed2256cbaa5e6390fb840a4c25ab54b8a80 + languageName: node + linkType: hard + "is-fullwidth-code-point@npm:^2.0.0": version: 2.0.0 resolution: "is-fullwidth-code-point@npm:2.0.0" @@ -3099,12 +3424,24 @@ __metadata: languageName: node linkType: hard -"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:~4.0.1": - version: 4.0.1 - resolution: "is-glob@npm:4.0.1" +"is-generator-function@npm:^1.0.10": + version: 1.1.0 + resolution: "is-generator-function@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + get-proto: "npm:^1.0.0" + has-tostringtag: "npm:^1.0.2" + safe-regex-test: "npm:^1.1.0" + checksum: 10/5906ff51a856a5fbc6b90a90fce32040b0a6870da905f98818f1350f9acadfc9884f7c3dec833fce04b83dd883937b86a190b6593ede82e8b1af8b6c4ecf7cbd + languageName: node + linkType: hard + +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" dependencies: is-extglob: "npm:^2.1.1" - checksum: 10/998cdc412db39a9ad10b5484bbbe43f8dfb6eb0467380c49d53a5105108ac2e590cca3c3ac0ff5e0dcc9c3342c5c235e77fd699576bcd16384eb5a62d4dd086a + checksum: 10/3ed74f2b0cdf4f401f38edb0442ddfde3092d79d7d35c9919c86641efdbcbb32e45aa3c0f70ce5eecc946896cd5a0f26e4188b9f2b881876f7cb6c505b82da11 languageName: node linkType: hard @@ -3117,10 +3454,27 @@ __metadata: languageName: node linkType: hard -"is-lambda@npm:^1.0.1": - version: 1.0.1 - resolution: "is-lambda@npm:1.0.1" - checksum: 10/93a32f01940220532e5948538699ad610d5924ac86093fcee83022252b363eb0cc99ba53ab084a04e4fb62bf7b5731f55496257a4c38adf87af9c4d352c71c35 +"is-map@npm:^2.0.3": + version: 2.0.3 + resolution: "is-map@npm:2.0.3" + checksum: 10/8de7b41715b08bcb0e5edb0fb9384b80d2d5bcd10e142188f33247d19ff078abaf8e9b6f858e2302d8d05376a26a55cd23a3c9f8ab93292b02fcd2cc9e4e92bb + languageName: node + linkType: hard + +"is-negative-zero@npm:^2.0.3": + version: 2.0.3 + resolution: "is-negative-zero@npm:2.0.3" + checksum: 10/8fe5cffd8d4fb2ec7b49d657e1691889778d037494c6f40f4d1a524cadd658b4b53ad7b6b73a59bcb4b143ae9a3d15829af864b2c0f9d65ac1e678c4c80f17e5 + languageName: node + linkType: hard + +"is-number-object@npm:^1.1.1": + version: 1.1.1 + resolution: "is-number-object@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10/a5922fb8779ab1ea3b8a9c144522b3d0bea5d9f8f23f7a72470e61e1e4df47714e28e0154ac011998b709cce260c3c9447ad3cd24a96c2f2a0abfdb2cbdc76c8 languageName: node linkType: hard @@ -3132,16 +3486,9 @@ __metadata: linkType: hard "is-object@npm:~1.0.1": - version: 1.0.1 - resolution: "is-object@npm:1.0.1" - checksum: 10/c3a6f2bb14f35dcf5605dfc085c824fe1ccbdf7484e3ab74a963d49eb2cacb0648cea67eee2761fabef0eb79c1290f17dcc058ffb5db91409d8746fe0356c8ee - languageName: node - linkType: hard - -"is-promise@npm:^2.1.0": - version: 2.1.0 - resolution: "is-promise@npm:2.1.0" - checksum: 10/ae31d22c2e0b8a8706bb4a6890998a94a993e70f07323c826e5ea39a8b373ac7ffb50bedfcab465dcbe973a599f3cd337547eed5cd8c7d073ff6a5e13dcf50f7 + version: 1.0.2 + resolution: "is-object@npm:1.0.2" + checksum: 10/db53971751c50277f0ed31d065d93038d23cb9785090ab5c8070a903cf5bab16cdb18f05b8855599ad87ec19eb4c85afa05980bcda77dd4a8482120b6348c73c languageName: node linkType: hard @@ -3152,35 +3499,68 @@ __metadata: languageName: node linkType: hard -"is-regex@npm:^1.0.5": - version: 1.0.5 - resolution: "is-regex@npm:1.0.5" +"is-regex@npm:^1.2.1": + version: 1.2.1 + resolution: "is-regex@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10/c42b7efc5868a5c9a4d8e6d3e9816e8815c611b09535c00fead18a1138455c5cb5e1887f0023a467ad3f9c419d62ba4dc3d9ba8bafe55053914d6d6454a945d2 + languageName: node + linkType: hard + +"is-set@npm:^2.0.3": + version: 2.0.3 + resolution: "is-set@npm:2.0.3" + checksum: 10/5685df33f0a4a6098a98c72d94d67cad81b2bc72f1fb2091f3d9283c4a1c582123cd709145b02a9745f0ce6b41e3e43f1c944496d1d74d4ea43358be61308669 + languageName: node + linkType: hard + +"is-shared-array-buffer@npm:^1.0.4": + version: 1.0.4 + resolution: "is-shared-array-buffer@npm:1.0.4" dependencies: - has: "npm:^1.0.3" - checksum: 10/3e2f855833e8986ae3b0ffc8257bf7a6d6c28870a0fb80597b1f8cefe0d959ba80e6c7d8a4d7f94831b59f844e165f48807e3cbe87ac8d6f1c345dfe7054d58c + call-bound: "npm:^1.0.3" + checksum: 10/0380d7c60cc692856871526ffcd38a8133818a2ee42d47bb8008248a0cd2121d8c8b5f66b6da3cac24bc5784553cacb6faaf678f66bc88c6615b42af2825230e languageName: node linkType: hard "is-stream@npm:^2.0.0": - version: 2.0.0 - resolution: "is-stream@npm:2.0.0" - checksum: 10/4dc47738e26bc4f1b3be9070b6b9e39631144f204fc6f87db56961220add87c10a999ba26cf81699f9ef9610426f69cb08a4713feff8deb7d8cadac907826935 + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: 10/b8e05ccdf96ac330ea83c12450304d4a591f9958c11fd17bed240af8d5ffe08aedafa4c0f4cfccd4d28dc9d4d129daca1023633d5c11601a6cbc77521f6fae66 languageName: node linkType: hard -"is-string@npm:^1.0.5": - version: 1.0.5 - resolution: "is-string@npm:1.0.5" - checksum: 10/aaf13faa599cb831705eec248aaa8a7355554f397841ada961a08642711022ea27ef8176ae0c3f7ba66eee1f6b584ab31bd42cd354878a58bdade388fe163a79 +"is-string@npm:^1.1.1": + version: 1.1.1 + resolution: "is-string@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10/5277cb9e225a7cc8a368a72623b44a99f2cfa139659c6b203553540681ad4276bfc078420767aad0e73eef5f0bd07d4abf39a35d37ec216917879d11cebc1f8b languageName: node linkType: hard -"is-symbol@npm:^1.0.2": - version: 1.0.3 - resolution: "is-symbol@npm:1.0.3" +"is-symbol@npm:^1.0.4, is-symbol@npm:^1.1.1": + version: 1.1.1 + resolution: "is-symbol@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.2" + has-symbols: "npm:^1.1.0" + safe-regex-test: "npm:^1.1.0" + checksum: 10/db495c0d8cd0a7a66b4f4ef7fccee3ab5bd954cb63396e8ac4d32efe0e9b12fdfceb851d6c501216a71f4f21e5ff20fc2ee845a3d52d455e021c466ac5eb2db2 + languageName: node + linkType: hard + +"is-typed-array@npm:^1.1.13, is-typed-array@npm:^1.1.14, is-typed-array@npm:^1.1.15": + version: 1.1.15 + resolution: "is-typed-array@npm:1.1.15" dependencies: - has-symbols: "npm:^1.0.1" - checksum: 10/4854604be4abb5f9d885d4bbc9f9318b7dbda9402fbe172c09861bb8910d97e70fac6dabbf1023a7ec56986f457c92abb08f1c99decce83c06c944130a0b1cd1 + which-typed-array: "npm:^1.1.16" + checksum: 10/e8cf60b9ea85667097a6ad68c209c9722cfe8c8edf04d6218366469e51944c5cc25bae45ffb845c23f811d262e4314d3b0168748eb16711aa34d12724cdf0735 languageName: node linkType: hard @@ -3191,6 +3571,32 @@ __metadata: languageName: node linkType: hard +"is-weakmap@npm:^2.0.2": + version: 2.0.2 + resolution: "is-weakmap@npm:2.0.2" + checksum: 10/a7b7e23206c542dcf2fa0abc483142731788771527e90e7e24f658c0833a0d91948a4f7b30d78f7a65255a48512e41a0288b778ba7fc396137515c12e201fd11 + languageName: node + linkType: hard + +"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.1": + version: 1.1.1 + resolution: "is-weakref@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10/543506fd8259038b371bb083aac25b16cb4fd8b12fc58053aa3d45ac28dfd001cd5c6dffbba7aeea4213c74732d46b6cb2cfb5b412eed11f2db524f3f97d09a0 + languageName: node + linkType: hard + +"is-weakset@npm:^2.0.3": + version: 2.0.4 + resolution: "is-weakset@npm:2.0.4" + dependencies: + call-bound: "npm:^1.0.3" + get-intrinsic: "npm:^1.2.6" + checksum: 10/1d5e1d0179beeed3661125a6faa2e59bfb48afda06fc70db807f178aa0ebebc3758fb6358d76b3d528090d5ef85148c345dcfbf90839592fe293e3e5e82f2134 + languageName: node + linkType: hard + "is-windows@npm:^1.0.2": version: 1.0.2 resolution: "is-windows@npm:1.0.2" @@ -3198,10 +3604,10 @@ __metadata: languageName: node linkType: hard -"isarray@npm:^1.0.0, isarray@npm:~1.0.0": - version: 1.0.0 - resolution: "isarray@npm:1.0.0" - checksum: 10/f032df8e02dce8ec565cf2eb605ea939bdccea528dbcf565cdf92bfa2da9110461159d86a537388ef1acef8815a330642d7885b29010e8f7eac967c9993b65ab +"isarray@npm:^2.0.5": + version: 2.0.5 + resolution: "isarray@npm:2.0.5" + checksum: 10/1d8bc7911e13bb9f105b1b3e0b396c787a9e63046af0b8fe0ab1414488ab06b2b099b87a2d8a9e31d21c9a6fad773c7fc8b257c4880f2d957274479d28ca3414 languageName: node linkType: hard @@ -3212,6 +3618,13 @@ __metadata: languageName: node linkType: hard +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 10/7fe1931ee4e88eb5aa524cd3ceb8c882537bc3a81b02e438b240e47012eef49c86904d0f0e593ea7c3a9996d18d0f1f3be8d3eaa92333977b0c3a9d353d5563e + languageName: node + linkType: hard + "isstream@npm:0.1.x": version: 0.1.2 resolution: "isstream@npm:0.1.2" @@ -3219,20 +3632,13 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-coverage@npm:^3.0.0": +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.2 resolution: "istanbul-lib-coverage@npm:3.2.2" checksum: 10/40bbdd1e937dfd8c830fa286d0f665e81b7a78bdabcd4565f6d5667c99828bda3db7fb7ac6b96a3e2e8a2461ddbc5452d9f8bc7d00cb00075fa6a3e99f5b6a81 languageName: node linkType: hard -"istanbul-lib-coverage@npm:^3.0.0-alpha.1": - version: 3.0.0 - resolution: "istanbul-lib-coverage@npm:3.0.0" - checksum: 10/eb0ba205890ee02ea9d76b31d6adf196f532b28a158c0c4db0db6ee6b60de476aca7bba34a9321d17fc396853db758d9430f1202ed28a7a6060e9d1cc8f555c0 - languageName: node - linkType: hard - "istanbul-lib-hook@npm:^3.0.0": version: 3.0.0 resolution: "istanbul-lib-hook@npm:3.0.0" @@ -3243,64 +3649,73 @@ __metadata: linkType: hard "istanbul-lib-instrument@npm:^4.0.0": - version: 4.0.1 - resolution: "istanbul-lib-instrument@npm:4.0.1" + version: 4.0.3 + resolution: "istanbul-lib-instrument@npm:4.0.3" dependencies: "@babel/core": "npm:^7.7.5" - "@babel/parser": "npm:^7.7.5" - "@babel/template": "npm:^7.7.4" - "@babel/traverse": "npm:^7.7.4" "@istanbuljs/schema": "npm:^0.1.2" istanbul-lib-coverage: "npm:^3.0.0" semver: "npm:^6.3.0" - checksum: 10/69f7e6809cf5e74ab91419b8c19ed5453604bd3541a24b27c418264448519912dbe3d2dcb26b8f6a007c83edd35968cc4658dd12f30011e779eca608b8c7a013 + checksum: 10/6e04ab365b95644ec4954b645f901be90be8ad81233d6df536300cdafcf70dd1ed22a912ceda38b32053c7fc9830c44cd23550c603f493329a8532073d1d6c42 languageName: node linkType: hard "istanbul-lib-processinfo@npm:^2.0.2": - version: 2.0.2 - resolution: "istanbul-lib-processinfo@npm:2.0.2" + version: 2.0.3 + resolution: "istanbul-lib-processinfo@npm:2.0.3" dependencies: archy: "npm:^1.0.0" - cross-spawn: "npm:^7.0.0" - istanbul-lib-coverage: "npm:^3.0.0-alpha.1" - make-dir: "npm:^3.0.0" + cross-spawn: "npm:^7.0.3" + istanbul-lib-coverage: "npm:^3.2.0" p-map: "npm:^3.0.0" rimraf: "npm:^3.0.0" - uuid: "npm:^3.3.3" - checksum: 10/40efb26ea9d96a4c7571a70cf657ff7dc3e9fde3863020c3086c482bd8851320b127cc0a7e80e403a70e26b2b88579b007ca1a15af6ed351db6d4ec63fc2792d + uuid: "npm:^8.3.2" + checksum: 10/60e7b3441687249460f34a817c7204967b07830a69b6e430e60a45615319c2ab4e2b2eaeb8b3decf549fccd419cd600d21173961632229967608d7d1b194f39e languageName: node linkType: hard "istanbul-lib-report@npm:^3.0.0": - version: 3.0.0 - resolution: "istanbul-lib-report@npm:3.0.0" + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" dependencies: istanbul-lib-coverage: "npm:^3.0.0" - make-dir: "npm:^3.0.0" + make-dir: "npm:^4.0.0" supports-color: "npm:^7.1.0" - checksum: 10/06b37952e9cb0fe419a37c7f3d74612a098167a9eb0e5264228036e78b42ca5226501e8130738b5306d94bae2ea068ca674080d4af959992523d84aacff67728 + checksum: 10/86a83421ca1cf2109a9f6d193c06c31ef04a45e72a74579b11060b1e7bb9b6337a4e6f04abfb8857e2d569c271273c65e855ee429376a0d7c91ad91db42accd1 languageName: node linkType: hard "istanbul-lib-source-maps@npm:^4.0.0": - version: 4.0.0 - resolution: "istanbul-lib-source-maps@npm:4.0.0" + version: 4.0.1 + resolution: "istanbul-lib-source-maps@npm:4.0.1" dependencies: debug: "npm:^4.1.1" istanbul-lib-coverage: "npm:^3.0.0" source-map: "npm:^0.6.1" - checksum: 10/765252abc6b5c9d29905fc97ce04b92da87d198f2c0161e62fe0aac8bb74fb7bd472a5e1d90fe3e78723d8cad43913f08d8eefa0339536fcc33b3a1922cf5fc3 + checksum: 10/5526983462799aced011d776af166e350191b816821ea7bcf71cab3e5272657b062c47dc30697a22a43656e3ced78893a42de677f9ccf276a28c913190953b82 languageName: node linkType: hard -"istanbul-reports@npm:^3.0.0": - version: 3.0.0 - resolution: "istanbul-reports@npm:3.0.0" +"istanbul-reports@npm:^3.0.2": + version: 3.1.7 + resolution: "istanbul-reports@npm:3.1.7" dependencies: html-escaper: "npm:^2.0.0" istanbul-lib-report: "npm:^3.0.0" - checksum: 10/2c29f322fe13b265716be7e4a4de8007931eed91bc8d14f9c0c08c30f6a987c57f752e07d12ba3e4fd03408eb89b0d3c0d6793f87112e3b85f86bfb50432e3d8 + checksum: 10/f1faaa4684efaf57d64087776018d7426312a59aa6eeb4e0e3a777347d23cd286ad18f427e98f0e3dee666103d7404c9d7abc5f240406a912fa16bd6695437fa + languageName: node + linkType: hard + +"jackspeak@npm:^3.1.2": + version: 3.4.3 + resolution: "jackspeak@npm:3.4.3" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10/96f8786eaab98e4bf5b2a5d6d9588ea46c4d06bbc4f2eb861fdd7b6b182b16f71d8a70e79820f335d52653b16d4843b29dd9cdcf38ae80406756db9199497cf3 languageName: node linkType: hard @@ -3341,15 +3756,15 @@ __metadata: linkType: hard "joi@npm:^17.2.1": - version: 17.2.1 - resolution: "joi@npm:17.2.1" + version: 17.13.3 + resolution: "joi@npm:17.13.3" dependencies: - "@hapi/address": "npm:^4.1.0" - "@hapi/formula": "npm:^2.0.0" - "@hapi/hoek": "npm:^9.0.0" - "@hapi/pinpoint": "npm:^2.0.0" - "@hapi/topo": "npm:^5.0.0" - checksum: 10/e8f43a2b660999252b8245ddd883a5d1a9cf50402249d91a9f6396e0ff4d10e49e902141bef3e96170842d40b8b5616d19c740727e4fcc74b825ea5d0f2f6384 + "@hapi/hoek": "npm:^9.3.0" + "@hapi/topo": "npm:^5.1.0" + "@sideway/address": "npm:^4.1.5" + "@sideway/formula": "npm:^3.0.1" + "@sideway/pinpoint": "npm:^2.0.0" + checksum: 10/4c150db0c820c3a52f4a55c82c1fc5e144a5b5f4da9ffebc7339a15469d1a447ebb427ced446efcb9709ab56bd71a06c4c67c9381bc1b9f9ae63fc7c89209bdf languageName: node linkType: hard @@ -3372,12 +3787,19 @@ __metadata: languageName: node linkType: hard -"jsesc@npm:^2.5.1": - version: 2.5.2 - resolution: "jsesc@npm:2.5.2" +"jsbn@npm:1.1.0": + version: 1.1.0 + resolution: "jsbn@npm:1.1.0" + checksum: 10/bebe7ae829bbd586ce8cbe83501dd8cb8c282c8902a8aeeed0a073a89dc37e8103b1244f3c6acd60278bcbfe12d93a3f83c9ac396868a3b3bbc3c5e5e3b648ef + languageName: node + linkType: hard + +"jsesc@npm:^3.0.2": + version: 3.1.0 + resolution: "jsesc@npm:3.1.0" bin: jsesc: bin/jsesc - checksum: 10/d2096abdcdec56969764b40ffc91d4a23408aa2f351b4d1c13f736f25476643238c43fdbaf38a191c26b1b78fd856d965f5d4d0dde7b89459cd94025190cdf13 + checksum: 10/20bd37a142eca5d1794f354db8f1c9aeb54d85e1f5c247b371de05d23a9751ecd7bd3a9c4fc5298ea6fa09a100dafb4190fa5c98c6610b75952c3487f3ce7967 languageName: node linkType: hard @@ -3403,11 +3825,11 @@ __metadata: linkType: hard "json5@npm:^2.2.2": - version: 2.2.2 - resolution: "json5@npm:2.2.2" + version: 2.2.3 + resolution: "json5@npm:2.2.3" bin: json5: lib/cli.js - checksum: 10/b95425711d180dbe2b48d62b581fa2899fe66731bf20f5a7e278567a44b2d5111e82f9ae382c07251a616065c919255ab5ad7851cc446eee6a8bc8e539348086 + checksum: 10/1db67b853ff0de3534085d630691d3247de53a2ed1390ba0ddff681ea43e9b3e30ecbdb65c5e9aab49435e44059c23dbd6fee8ee619419ba37465bb0dd7135da languageName: node linkType: hard @@ -3435,28 +3857,6 @@ __metadata: languageName: node linkType: hard -"load-json-file@npm:^2.0.0": - version: 2.0.0 - resolution: "load-json-file@npm:2.0.0" - dependencies: - graceful-fs: "npm:^4.1.2" - parse-json: "npm:^2.2.0" - pify: "npm:^2.0.0" - strip-bom: "npm:^3.0.0" - checksum: 10/7f212bbf08a8c9aab087ead07aa220d1f43d83ec1c4e475a00a8d9bf3014eb29ebe901db8554627dcfb70184c274d05b7379f1e9678fe8297ae74dc495212049 - languageName: node - linkType: hard - -"locate-path@npm:^2.0.0": - version: 2.0.0 - resolution: "locate-path@npm:2.0.0" - dependencies: - p-locate: "npm:^2.0.0" - path-exists: "npm:^3.0.0" - checksum: 10/02d581edbbbb0fa292e28d96b7de36b5b62c2fa8b5a7e82638ebb33afa74284acf022d3b1e9ae10e3ffb7658fbc49163fcd5e76e7d1baaa7801c3e05a81da755 - languageName: node - linkType: hard - "locate-path@npm:^3.0.0": version: 3.0.0 resolution: "locate-path@npm:3.0.0" @@ -3696,61 +4096,72 @@ __metadata: languageName: node linkType: hard -"loupe@npm:^2.3.1": - version: 2.3.4 - resolution: "loupe@npm:2.3.4" +"loupe@npm:^2.3.6": + version: 2.3.7 + resolution: "loupe@npm:2.3.7" dependencies: - get-func-name: "npm:^2.0.0" - checksum: 10/29b3b3c2aa849a97395e30e927da075175ec60bf40e088a09c955c943a98f23a73785c66958de038bbf8cf05d08dff8221e2d402f8b65cd6280556e84556600f + get-func-name: "npm:^2.0.1" + checksum: 10/635c8f0914c2ce7ecfe4e239fbaf0ce1d2c00e4246fafcc4ed000bfdb1b8f89d05db1a220054175cca631ebf3894872a26fffba0124477fcb562f78762848fb1 languageName: node linkType: hard -"lru-cache@npm:^6.0.0": - version: 6.0.0 - resolution: "lru-cache@npm:6.0.0" - dependencies: - yallist: "npm:^4.0.0" - checksum: 10/fc1fe2ee205f7c8855fa0f34c1ab0bcf14b6229e35579ec1fd1079f31d6fc8ef8eb6fd17f2f4d99788d7e339f50e047555551ebd5e434dda503696e7c6591825 +"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": + version: 10.4.3 + resolution: "lru-cache@npm:10.4.3" + checksum: 10/e6e90267360476720fa8e83cc168aa2bf0311f3f2eea20a6ba78b90a885ae72071d9db132f40fda4129c803e7dcec3a6b6a6fbb44ca90b081630b810b5d6a41a languageName: node linkType: hard -"lru-cache@npm:^7.7.1": - version: 7.14.1 - resolution: "lru-cache@npm:7.14.1" - checksum: 10/f29a86e9eb3fac3dd2f41c218f6e5b1668786a9ab12d095525994cf1072ad66d0850a41957b6b5da1aea6209c691a1b2bc14e5111467e97112bbf2323d680df2 +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: "npm:^3.0.2" + checksum: 10/951d2673dcc64a7fb888bf3d13bc2fdf923faca97d89cdb405ba3dfff77e2b26e5798d405e78fcd7094c9e7b8b4dab2ddc5a4f8a11928af24a207b7c738ca3f8 languageName: node linkType: hard "make-dir@npm:^3.0.0, make-dir@npm:^3.0.2": - version: 3.0.2 - resolution: "make-dir@npm:3.0.2" + version: 3.1.0 + resolution: "make-dir@npm:3.1.0" dependencies: semver: "npm:^6.0.0" - checksum: 10/b7ba1b53455c54fb867589e08e1303faceec23bec1303ccc77eba5c8350aa02fe30a1a13e009cc712f55efa7b2101262f9373c1c887af4de3025e23977cbdd34 + checksum: 10/484200020ab5a1fdf12f393fe5f385fc8e4378824c940fba1729dcd198ae4ff24867bc7a5646331e50cead8abff5d9270c456314386e629acec6dff4b8016b78 + languageName: node + linkType: hard + +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 10/bf0731a2dd3aab4db6f3de1585cea0b746bb73eb5a02e3d8d72757e376e64e6ada190b1eddcde5b2f24a81b688a9897efd5018737d05e02e2a671dda9cff8a8a languageName: node linkType: hard -"make-fetch-happen@npm:^10.0.3": - version: 10.2.1 - resolution: "make-fetch-happen@npm:10.2.1" +"make-fetch-happen@npm:^14.0.3": + version: 14.0.3 + resolution: "make-fetch-happen@npm:14.0.3" dependencies: - agentkeepalive: "npm:^4.2.1" - cacache: "npm:^16.1.0" - http-cache-semantics: "npm:^4.1.0" - http-proxy-agent: "npm:^5.0.0" - https-proxy-agent: "npm:^5.0.0" - is-lambda: "npm:^1.0.1" - lru-cache: "npm:^7.7.1" - minipass: "npm:^3.1.6" - minipass-collect: "npm:^1.0.2" - minipass-fetch: "npm:^2.0.3" + "@npmcli/agent": "npm:^3.0.0" + cacache: "npm:^19.0.1" + http-cache-semantics: "npm:^4.1.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^4.0.0" minipass-flush: "npm:^1.0.5" minipass-pipeline: "npm:^1.2.4" - negotiator: "npm:^0.6.3" + negotiator: "npm:^1.0.0" + proc-log: "npm:^5.0.0" promise-retry: "npm:^2.0.1" - socks-proxy-agent: "npm:^7.0.0" - ssri: "npm:^9.0.0" - checksum: 10/fef5acb865a46f25ad0b5ad7d979799125db5dbb24ea811ffa850fbb804bc8e495df2237a8ec3a4fc6250e73c2f95549cca6d6d36a73b1faa61224504eb1188f + ssri: "npm:^12.0.0" + checksum: 10/fce0385840b6d86b735053dfe941edc2dd6468fda80fe74da1eeff10cbd82a75760f406194f2bc2fa85b99545b2bc1f84c08ddf994b21830775ba2d1a87e8bdf + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10/11df2eda46d092a6035479632e1ec865b8134bdfc4bd9e571a656f4191525404f13a283a515938c3a8de934dbfd9c09674d9da9fa831e6eb7e22b50b197d2edd languageName: node linkType: hard @@ -3782,20 +4193,13 @@ __metadata: languageName: node linkType: hard -"methods@npm:^1.1.1, methods@npm:^1.1.2": +"methods@npm:^1.1.2": version: 1.1.2 resolution: "methods@npm:1.1.2" checksum: 10/a385dd974faa34b5dd021b2bbf78c722881bf6f003bfe6d391d7da3ea1ed625d1ff10ddd13c57531f628b3e785be38d3eed10ad03cebd90b76932413df9a1820 languageName: node linkType: hard -"mime-db@npm:1.43.0": - version: 1.43.0 - resolution: "mime-db@npm:1.43.0" - checksum: 10/a582d9c60a31c51591773f92ccfb6da1c05b730ba844d11c986bf9728030082010b9bc80f9e34243fcdfdc71de5aa5a9e2a67cfd193c8cd7e00e0a484a4f3ccb - languageName: node - linkType: hard - "mime-db@npm:1.52.0": version: 1.52.0 resolution: "mime-db@npm:1.52.0" @@ -3810,12 +4214,12 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.1.12, mime-types@npm:~2.1.24": - version: 2.1.26 - resolution: "mime-types@npm:2.1.26" +"mime-types@npm:^2.1.12, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" dependencies: - mime-db: "npm:1.43.0" - checksum: 10/464e9037c99286d314402cb929490a78c4d001afd51e26c9125ece0a80f7c3554324f54917d5d0a0d13b280c1505b4f4d0404609671fcdaf9e1bb0fea4ea643b + mime-db: "npm:1.52.0" + checksum: 10/89aa9651b67644035de2784a6e665fc685d79aba61857e02b9c8758da874a754aed4a9aced9265f5ed1171fd934331e5516b84a7f0218031b6fa0270eca1e51a languageName: node linkType: hard @@ -3828,16 +4232,16 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:~2.1.34": - version: 2.1.35 - resolution: "mime-types@npm:2.1.35" - dependencies: - mime-db: "npm:1.52.0" - checksum: 10/89aa9651b67644035de2784a6e665fc685d79aba61857e02b9c8758da874a754aed4a9aced9265f5ed1171fd934331e5516b84a7f0218031b6fa0270eca1e51a +"mime@npm:2.6.0": + version: 2.6.0 + resolution: "mime@npm:2.6.0" + bin: + mime: cli.js + checksum: 10/7da117808b5cd0203bb1b5e33445c330fe213f4d8ee2402a84d62adbde9716ca4fb90dd6d9ab4e77a4128c6c5c24a9c4c9f6a4d720b095b1b342132d02dba58d languageName: node linkType: hard -"mime@npm:^1.3.4, mime@npm:^1.4.1": +"mime@npm:^1.3.4": version: 1.6.0 resolution: "mime@npm:1.6.0" bin: @@ -3846,15 +4250,6 @@ __metadata: languageName: node linkType: hard -"mime@npm:^2.5.0": - version: 2.6.0 - resolution: "mime@npm:2.6.0" - bin: - mime: cli.js - checksum: 10/7da117808b5cd0203bb1b5e33445c330fe213f4d8ee2402a84d62adbde9716ca4fb90dd6d9ab4e77a4128c6c5c24a9c4c9f6a4d720b095b1b342132d02dba58d - languageName: node - linkType: hard - "mimic-fn@npm:^2.1.0": version: 2.1.0 resolution: "mimic-fn@npm:2.1.0" @@ -3872,33 +4267,33 @@ __metadata: linkType: hard "minimist@npm:^1.2.6": - version: 1.2.6 - resolution: "minimist@npm:1.2.6" - checksum: 10/b956a7d48669c5007f0afce100a92d3af18e77939a25b5b4f62e9ea07c2777033608327e14c2af85684d5cd504f623f2a04d30a4a43379d21dd3c6dcf12b8ab8 + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 10/908491b6cc15a6c440ba5b22780a0ba89b9810e1aea684e253e43c4e3b8d56ec1dcdd7ea96dde119c29df59c936cde16062159eae4225c691e19c70b432b6e6f languageName: node linkType: hard -"minipass-collect@npm:^1.0.2": - version: 1.0.2 - resolution: "minipass-collect@npm:1.0.2" +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" dependencies: - minipass: "npm:^3.0.0" - checksum: 10/14df761028f3e47293aee72888f2657695ec66bd7d09cae7ad558da30415fdc4752bbfee66287dcc6fd5e6a2fa3466d6c484dc1cbd986525d9393b9523d97f10 + minipass: "npm:^7.0.3" + checksum: 10/b251bceea62090f67a6cced7a446a36f4cd61ee2d5cea9aee7fff79ba8030e416327a1c5aa2908dc22629d06214b46d88fdab8c51ac76bacbf5703851b5ad342 languageName: node linkType: hard -"minipass-fetch@npm:^2.0.3": - version: 2.1.2 - resolution: "minipass-fetch@npm:2.1.2" +"minipass-fetch@npm:^4.0.0": + version: 4.0.1 + resolution: "minipass-fetch@npm:4.0.1" dependencies: encoding: "npm:^0.1.13" - minipass: "npm:^3.1.6" + minipass: "npm:^7.0.3" minipass-sized: "npm:^1.0.3" - minizlib: "npm:^2.1.2" + minizlib: "npm:^3.0.1" dependenciesMeta: encoding: optional: true - checksum: 10/8cfc589563ae2a11eebbf79121ef9a526fd078fca949ed3f1e4a51472ca4a4aad89fcea1738982ce9d7d833116ecc9c6ae9ebbd844832a94e3f4a3d4d1b9d3b9 + checksum: 10/7ddfebdbb87d9866e7b5f7eead5a9e3d9d507992af932a11d275551f60006cf7d9178e66d586dbb910894f3e3458d27c0ddf93c76e94d49d0a54a541ddc1263d languageName: node linkType: hard @@ -3929,7 +4324,7 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^3.0.0, minipass@npm:^3.1.1, minipass@npm:^3.1.6": +"minipass@npm:^3.0.0": version: 3.3.6 resolution: "minipass@npm:3.3.6" dependencies: @@ -3938,31 +4333,30 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^4.0.0": - version: 4.0.3 - resolution: "minipass@npm:4.0.3" - checksum: 10/773654ca62765d66d7d9a949026e461b91e7bd913f11f513c5b24aa318b7d57b9599e94394e96ec5c9820ed712fb786207ba2d31ea006e16e9b1ef1997d47bd7 +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 10/c25f0ee8196d8e6036661104bacd743785b2599a21de5c516b32b3fa2b83113ac89a2358465bc04956baab37ffb956ae43be679b2262bf7be15fce467ccd7950 languageName: node linkType: hard -"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": - version: 2.1.2 - resolution: "minizlib@npm:2.1.2" +"minizlib@npm:^3.0.1": + version: 3.0.2 + resolution: "minizlib@npm:3.0.2" dependencies: - minipass: "npm:^3.0.0" - yallist: "npm:^4.0.0" - checksum: 10/ae0f45436fb51344dcb87938446a32fbebb540d0e191d63b35e1c773d47512e17307bf54aa88326cc6d176594d00e4423563a091f7266c2f9a6872cdc1e234d1 + minipass: "npm:^7.1.2" + checksum: 10/c075bed1594f68dcc8c35122333520112daefd4d070e5d0a228bd4cf5580e9eed3981b96c0ae1d62488e204e80fd27b2b9d0068ca9a5ef3993e9565faf63ca41 languageName: node linkType: hard -"mkdirp@npm:0.5.1": - version: 0.5.1 - resolution: "mkdirp@npm:0.5.1" +"mkdirp@npm:0.5.5": + version: 0.5.5 + resolution: "mkdirp@npm:0.5.5" dependencies: - minimist: "npm:0.0.8" + minimist: "npm:^1.2.5" bin: mkdirp: bin/cmd.js - checksum: 10/8651af2facdfa53f39e68fd93cf1653c11f7c1d49c6d1b4e53bcedc52e669cc64f1b5e95c49cfde7e99dbbcad26d3e61f4f2b4812f18c871c6455d9592f02806 + checksum: 10/3bce20ea525f9477befe458ab85284b0b66c8dc3812f94155af07c827175948cdd8114852ac6c6d82009b13c1048c37f6d98743eb019651ee25c39acc8aabe7d languageName: node linkType: hard @@ -3977,12 +4371,12 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": - version: 1.0.4 - resolution: "mkdirp@npm:1.0.4" +"mkdirp@npm:^3.0.1": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" bin: - mkdirp: bin/cmd.js - checksum: 10/d71b8dcd4b5af2fe13ecf3bd24070263489404fe216488c5ba7e38ece1f54daf219e72a833a3a2dc404331e870e9f44963a33399589490956bff003a3404d3b2 + mkdirp: dist/cjs/src/bin.js + checksum: 10/16fd79c28645759505914561e249b9a1f5fe3362279ad95487a4501e4467abeb714fd35b95307326b8fd03f3c7719065ef11a6f97b7285d7888306d1bd2232ba languageName: node linkType: hard @@ -3994,8 +4388,8 @@ __metadata: linkType: hard "mocha@npm:7": - version: 7.1.0 - resolution: "mocha@npm:7.1.0" + version: 7.2.0 + resolution: "mocha@npm:7.2.0" dependencies: ansi-colors: "npm:3.2.3" browser-stdout: "npm:1.3.1" @@ -4010,7 +4404,7 @@ __metadata: js-yaml: "npm:3.13.1" log-symbols: "npm:3.0.0" minimatch: "npm:3.0.4" - mkdirp: "npm:0.5.1" + mkdirp: "npm:0.5.5" ms: "npm:2.1.1" node-environment-flags: "npm:1.0.6" object.assign: "npm:4.1.0" @@ -4018,13 +4412,13 @@ __metadata: supports-color: "npm:6.0.0" which: "npm:1.3.1" wide-align: "npm:1.1.3" - yargs: "npm:13.3.0" - yargs-parser: "npm:13.1.1" + yargs: "npm:13.3.2" + yargs-parser: "npm:13.1.2" yargs-unparser: "npm:1.6.0" bin: _mocha: bin/_mocha mocha: bin/mocha - checksum: 10/3d754e4106ccddb577f5ff2b47b5aeca1314891430b127158b60bf1ff53e848347ab8ab2ae30593a918c0fb27fb162e35dff55f59f16e35435e6ae10efd34eca + checksum: 10/3f7630fc5aecd1497a13ffa8ac98a5db6d91a9f0232d12f5d258c17da187ab1ec53192e4947443d96174785256036b711e0d3cd6f99fd5766b29c801836fe6c1 languageName: node linkType: hard @@ -4053,22 +4447,22 @@ __metadata: linkType: hard "moment@npm:^2.29.4": - version: 2.29.4 - resolution: "moment@npm:2.29.4" - checksum: 10/157c5af5a0ba8196e577bc67feb583303191d21ba1f7f2af30b3b40d4c63a64d505ba402be2a1454832082fac6be69db1e0d186c3279dae191e6634b0c33705c + version: 2.30.1 + resolution: "moment@npm:2.30.1" + checksum: 10/ae42d876d4ec831ef66110bdc302c0657c664991e45cf2afffc4b0f6cd6d251dde11375c982a5c0564ccc0fa593fc564576ddceb8c8845e87c15f58aa6baca69 languageName: node linkType: hard "morgan@npm:^1.9.1": - version: 1.9.1 - resolution: "morgan@npm:1.9.1" + version: 1.10.1 + resolution: "morgan@npm:1.10.1" dependencies: - basic-auth: "npm:~2.0.0" + basic-auth: "npm:~2.0.1" debug: "npm:2.6.9" - depd: "npm:~1.1.2" + depd: "npm:~2.0.0" on-finished: "npm:~2.3.0" - on-headers: "npm:~1.0.1" - checksum: 10/0334e4ccbd1e6b458c438d23db5b4feb74f4c2474917a8ea2ad8c1db2614df7fb3e8d19bac7f787df033ce88734103e5515a43dcb92d1bff60bbd642849c0962 + on-headers: "npm:~1.1.0" + checksum: 10/f6a611bdcb9bebe8283381c49efedee81f50b75f6cbc52430cda1743ec35443c92d5e5d4384ce38b102d8c102162c92da563471def3cf840b4980160f278f8ba languageName: node linkType: hard @@ -4086,14 +4480,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.2": - version: 2.1.2 - resolution: "ms@npm:2.1.2" - checksum: 10/673cdb2c3133eb050c745908d8ce632ed2c02d85640e2edb3ace856a2266a813b30c613569bf3354fdf4ea7d1a1494add3bfa95e2713baa27d0c2c71fc44f58f - languageName: node - linkType: hard - -"ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": +"ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10/aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d @@ -4114,14 +4501,7 @@ __metadata: languageName: node linkType: hard -"negotiator@npm:0.6.2": - version: 0.6.2 - resolution: "negotiator@npm:0.6.2" - checksum: 10/eaf267fedd6503c98beee76e1a0388a04c185d9acb70c1ad206f212849392ad63d6beccea5813f0ac1ace79c16b113d2b89734af28554a0bece9a274b5a02628 - languageName: node - linkType: hard - -"negotiator@npm:0.6.3, negotiator@npm:^0.6.3": +"negotiator@npm:0.6.3": version: 0.6.3 resolution: "negotiator@npm:0.6.3" checksum: 10/2723fb822a17ad55c93a588a4bc44d53b22855bf4be5499916ca0cab1e7165409d0b288ba2577d7b029f10ce18cf2ed8e703e5af31c984e1e2304277ef979837 @@ -4168,11 +4548,11 @@ __metadata: linkType: hard "node-cache@npm:^5.1.0": - version: 5.1.0 - resolution: "node-cache@npm:5.1.0" + version: 5.1.2 + resolution: "node-cache@npm:5.1.2" dependencies: clone: "npm:2.x" - checksum: 10/744846e8c770aa1e8caaa7624e48696acab7e98d3331f19e2bbfc128ec1b661af09eb86627367ce3bd84fd25c6c006a174572437bb504ff62965a6f6e7c0fbea + checksum: 10/6ac71a9e65fdd8940883c3c188de4888ff592f5bf52e4d42436c49e2a575d635e7327acea490c49fa7c01d5fa81f7b6e060fd35cf6f6ec401fbd5f77a3ebeecf languageName: node linkType: hard @@ -4194,8 +4574,8 @@ __metadata: linkType: hard "node-fetch@npm:^2.6.7": - version: 2.6.9 - resolution: "node-fetch@npm:2.6.9" + version: 2.7.0 + resolution: "node-fetch@npm:2.7.0" dependencies: whatwg-url: "npm:^5.0.0" peerDependencies: @@ -4203,35 +4583,36 @@ __metadata: peerDependenciesMeta: encoding: optional: true - checksum: 10/4d04273c97e3829b3fb070b9b2c14c9f6ecff9afd1d3d8043fb39d1d2440b23e2ddbdbab1b2f879bf71fa23275bf5711e777256e5784d1852333965a6cea38ab + checksum: 10/b24f8a3dc937f388192e59bcf9d0857d7b6940a2496f328381641cb616efccc9866e89ec43f2ec956bbd6c3d3ee05524ce77fe7b29ccd34692b3a16f237d6676 languageName: node linkType: hard "node-gyp@npm:latest": - version: 9.3.1 - resolution: "node-gyp@npm:9.3.1" + version: 11.3.0 + resolution: "node-gyp@npm:11.3.0" dependencies: env-paths: "npm:^2.2.0" - glob: "npm:^7.1.4" + exponential-backoff: "npm:^3.1.1" graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^10.0.3" - nopt: "npm:^6.0.0" - npmlog: "npm:^6.0.0" - rimraf: "npm:^3.0.2" + make-fetch-happen: "npm:^14.0.3" + nopt: "npm:^8.0.0" + proc-log: "npm:^5.0.0" semver: "npm:^7.3.5" - tar: "npm:^6.1.2" - which: "npm:^2.0.2" + tar: "npm:^7.4.3" + tinyglobby: "npm:^0.2.12" + which: "npm:^5.0.0" bin: node-gyp: bin/node-gyp.js - checksum: 10/e9345b22be0a3256af87a16ba9604362cd8e4db304e67e71dd83bb8e573f3fdbaf69e359b5af572a14a98730cc3e1813679444ee029093d2a2f38ba3cac4ed7e + checksum: 10/e7fb17ba72172d743ee791ea470cba1a579d1c37bcaa67a45d221d07df055baf1367d0684b3c7ee2b9b61f260cea77b2016aaac47027dbc0f43030c90b21527d languageName: node linkType: hard "node-mocks-http@npm:^1.7.0": - version: 1.8.1 - resolution: "node-mocks-http@npm:1.8.1" + version: 1.17.2 + resolution: "node-mocks-http@npm:1.17.2" dependencies: accepts: "npm:^1.3.7" + content-disposition: "npm:^0.5.3" depd: "npm:^1.1.0" fresh: "npm:^0.5.2" merge-descriptors: "npm:^1.0.1" @@ -4240,11 +4621,19 @@ __metadata: parseurl: "npm:^1.3.3" range-parser: "npm:^1.2.0" type-is: "npm:^1.6.18" - checksum: 10/79e0161c6a0640b14738499c0a68d408caf1a2bbb086323d869027d8f239df890a7773c1dccaec713ed2536a2b54dc32505a138e5f6a05f2111b1c0fbfedbf98 + peerDependencies: + "@types/express": ^4.17.21 || ^5.0.0 + "@types/node": "*" + peerDependenciesMeta: + "@types/express": + optional: true + "@types/node": + optional: true + checksum: 10/3ab97b5a7b2dba0032fc5090983e4ca4be0597f1c645b34dff99d465feb17ef1ee7ea4829a9b4303495399c1ac0f12f8964b878e0dda43cda798bb97254ad571 languageName: node linkType: hard -"node-preload@npm:^0.2.0": +"node-preload@npm:^0.2.1": version: 0.2.1 resolution: "node-preload@npm:0.2.1" dependencies: @@ -4253,26 +4642,21 @@ __metadata: languageName: node linkType: hard -"nopt@npm:^6.0.0": - version: 6.0.0 - resolution: "nopt@npm:6.0.0" - dependencies: - abbrev: "npm:^1.0.0" - bin: - nopt: bin/nopt.js - checksum: 10/3c1128e07cd0241ae66d6e6a472170baa9f3e84dd4203950ba8df5bafac4efa2166ce917a57ef02b01ba7c40d18b2cc64b29b225fd3640791fe07b24f0b33a32 +"node-releases@npm:^2.0.19": + version: 2.0.19 + resolution: "node-releases@npm:2.0.19" + checksum: 10/c2b33b4f0c40445aee56141f13ca692fa6805db88510e5bbb3baadb2da13e1293b738e638e15e4a8eb668bb9e97debb08e7a35409b477b5cc18f171d35a83045 languageName: node linkType: hard -"normalize-package-data@npm:^2.3.2": - version: 2.5.0 - resolution: "normalize-package-data@npm:2.5.0" +"nopt@npm:^8.0.0": + version: 8.1.0 + resolution: "nopt@npm:8.1.0" dependencies: - hosted-git-info: "npm:^2.1.4" - resolve: "npm:^1.10.0" - semver: "npm:2 || 3 || 4 || 5" - validate-npm-package-license: "npm:^3.0.1" - checksum: 10/644f830a8bb9b7cc9bf2f6150618727659ee27cdd0840d1c1f97e8e6cab0803a098a2c19f31c6247ad9d3a0792e61521a13a6e8cd87cc6bb676e3150612c03d4 + abbrev: "npm:^3.0.0" + bin: + nopt: bin/nopt.js + checksum: 10/26ab456c51a96f02a9e5aa8d1b80ef3219f2070f3f3528a040e32fb735b1e651e17bdf0f1476988d3a46d498f35c65ed662d122f340d38ce4a7e71dd7b20c4bc languageName: node linkType: hard @@ -4292,21 +4676,9 @@ __metadata: languageName: node linkType: hard -"npmlog@npm:^6.0.0": - version: 6.0.2 - resolution: "npmlog@npm:6.0.2" - dependencies: - are-we-there-yet: "npm:^3.0.0" - console-control-strings: "npm:^1.1.0" - gauge: "npm:^4.0.3" - set-blocking: "npm:^2.0.0" - checksum: 10/82b123677e62deb9e7472e27b92386c09e6e254ee6c8bcd720b3011013e4168bc7088e984f4fbd53cb6e12f8b4690e23e4fa6132689313e0d0dc4feea45489bb - languageName: node - linkType: hard - "nyc@npm:^15.0.0": - version: 15.0.0 - resolution: "nyc@npm:15.0.0" + version: 15.1.0 + resolution: "nyc@npm:15.1.0" dependencies: "@istanbuljs/load-nyc-config": "npm:^1.0.0" "@istanbuljs/schema": "npm:^0.1.2" @@ -4316,6 +4688,7 @@ __metadata: find-cache-dir: "npm:^3.2.0" find-up: "npm:^4.1.0" foreground-child: "npm:^2.0.0" + get-package-type: "npm:^0.1.0" glob: "npm:^7.1.6" istanbul-lib-coverage: "npm:^3.0.0" istanbul-lib-hook: "npm:^3.0.0" @@ -4323,10 +4696,9 @@ __metadata: istanbul-lib-processinfo: "npm:^2.0.2" istanbul-lib-report: "npm:^3.0.0" istanbul-lib-source-maps: "npm:^4.0.0" - istanbul-reports: "npm:^3.0.0" - js-yaml: "npm:^3.13.1" + istanbul-reports: "npm:^3.0.2" make-dir: "npm:^3.0.0" - node-preload: "npm:^0.2.0" + node-preload: "npm:^0.2.1" p-map: "npm:^3.0.0" process-on-spawn: "npm:^1.0.0" resolve-from: "npm:^5.0.0" @@ -4334,11 +4706,10 @@ __metadata: signal-exit: "npm:^3.0.2" spawn-wrap: "npm:^2.0.0" test-exclude: "npm:^6.0.0" - uuid: "npm:^3.3.3" yargs: "npm:^15.0.2" bin: nyc: bin/nyc.js - checksum: 10/07a41110603bb6191c73f691b08b53f87baa47782d8152b888a578140c169132501ebc4b97df1a2cbb384e81839beae91d2cad6963015b0b48a866dcb9a16e6e + checksum: 10/c987f04f4192dfd94e9e69869c76a54220b3ed555016751f380a413a378cceff8ec346df579e9126035b6acbc60ab893cc65e67729cc427c0171361bcb481e66 languageName: node linkType: hard @@ -4349,28 +4720,21 @@ __metadata: languageName: node linkType: hard -"object-inspect@npm:^1.7.0": - version: 1.7.0 - resolution: "object-inspect@npm:1.7.0" - checksum: 10/5fef751f95c3256827a4cc1806dcecf3ec46d2dfe54014a5ca286d1c2525642dea4ab87062bca04283701a505d7c1f4b357f410dcfb74bd60376afdb0718ef9f - languageName: node - linkType: hard - -"object-inspect@npm:^1.9.0": - version: 1.12.2 - resolution: "object-inspect@npm:1.12.2" - checksum: 10/aa11100d45fa919b36448347d4f7c8a78b0247886881db56a2026b512c4042a9749e64894519b00a4db8c6e2b713a965b5ceaa3b59324aeb3da007c54a33bc58 +"object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10/aa13b1190ad3e366f6c83ad8a16ed37a19ed57d267385aa4bfdccda833d7b90465c057ff6c55d035a6b2e52c1a2295582b294217a0a3a1ae7abdd6877ef781fb languageName: node linkType: hard -"object-keys@npm:^1.0.11, object-keys@npm:^1.0.12, object-keys@npm:^1.1.1": +"object-keys@npm:^1.0.11, object-keys@npm:^1.1.1": version: 1.1.1 resolution: "object-keys@npm:1.1.1" checksum: 10/3d81d02674115973df0b7117628ea4110d56042e5326413e4b4313f0bcdf7dd78d4a3acef2c831463fa3796a66762c49daef306f4a0ea1af44877d7086d73bde languageName: node linkType: hard -"object.assign@npm:4.1.0, object.assign@npm:^4.1.0": +"object.assign@npm:4.1.0": version: 4.1.0 resolution: "object.assign@npm:4.1.0" dependencies: @@ -4382,41 +4746,83 @@ __metadata: languageName: node linkType: hard -"object.entries@npm:^1.1.0": - version: 1.1.1 - resolution: "object.entries@npm:1.1.1" +"object.assign@npm:^4.1.2, object.assign@npm:^4.1.7": + version: 4.1.7 + resolution: "object.assign@npm:4.1.7" dependencies: - define-properties: "npm:^1.1.3" - es-abstract: "npm:^1.17.0-next.1" - function-bind: "npm:^1.1.1" - has: "npm:^1.0.3" - checksum: 10/087789285d8d1829f9852c534a39e4bfdcc5a384152b4de7b10281db0dde71de5d27790e7202845dd3798ae80984189d79bd4db0133a63623422e17ac4f5b51b + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + has-symbols: "npm:^1.1.0" + object-keys: "npm:^1.1.1" + checksum: 10/3fe28cdd779f2a728a9a66bd688679ba231a2b16646cd1e46b528fe7c947494387dda4bc189eff3417f3717ef4f0a8f2439347cf9a9aa3cef722fbfd9f615587 + languageName: node + linkType: hard + +"object.entries@npm:^1.1.2": + version: 1.1.9 + resolution: "object.entries@npm:1.1.9" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.1.1" + checksum: 10/24163ab1e1e013796693fc5f5d349e8b3ac0b6a34a7edb6c17d3dd45c6a8854145780c57d302a82512c1582f63720f4b4779d6c1cfba12cbb1420b978802d8a3 + languageName: node + linkType: hard + +"object.fromentries@npm:^2.0.8": + version: 2.0.8 + resolution: "object.fromentries@npm:2.0.8" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-object-atoms: "npm:^1.0.0" + checksum: 10/5b2e80f7af1778b885e3d06aeb335dcc86965e39464671adb7167ab06ac3b0f5dd2e637a90d8ebd7426d69c6f135a4753ba3dd7d0fe2a7030cf718dcb910fd92 languageName: node linkType: hard "object.getownpropertydescriptors@npm:^2.0.3": - version: 2.1.0 - resolution: "object.getownpropertydescriptors@npm:2.1.0" + version: 2.1.8 + resolution: "object.getownpropertydescriptors@npm:2.1.8" + dependencies: + array.prototype.reduce: "npm:^1.0.6" + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-object-atoms: "npm:^1.0.0" + gopd: "npm:^1.0.1" + safe-array-concat: "npm:^1.1.2" + checksum: 10/8c50f52e0d702d30836f3d2772ba02807ca25a5381be6f9470c6d143ee0bad01bce3fff0fedea2bdbc0c9297e4eb7785ffee5739f6a3a7c60fcd622b42f8a9fb + languageName: node + linkType: hard + +"object.groupby@npm:^1.0.3": + version: 1.0.3 + resolution: "object.groupby@npm:1.0.3" dependencies: - define-properties: "npm:^1.1.3" - es-abstract: "npm:^1.17.0-next.1" - checksum: 10/ec6b4b3d0c21397aabbb02222fa134aaa6a56978933c80d5b3f3fdd7e2e567350be3b3a0805b1056f97a5dea55b3100a121f3262d1c13ddba5da4351c117ae4b + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + checksum: 10/44cb86dd2c660434be65f7585c54b62f0425b0c96b5c948d2756be253ef06737da7e68d7106e35506ce4a44d16aa85a413d11c5034eb7ce5579ec28752eb42d0 languageName: node linkType: hard -"object.values@npm:^1.1.0": - version: 1.1.1 - resolution: "object.values@npm:1.1.1" +"object.values@npm:^1.2.1": + version: 1.2.1 + resolution: "object.values@npm:1.2.1" dependencies: - define-properties: "npm:^1.1.3" - es-abstract: "npm:^1.17.0-next.1" - function-bind: "npm:^1.1.1" - has: "npm:^1.0.3" - checksum: 10/bbe9b2b98390987a514bf7e41e28388784ed4a6fed3526b86dd4e14569f7cd6483446d77cbeeefe3cff70d8206f1d450c2d485088a841c13048cfd238aa234ca + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10/f5ec9eccdefeaaa834b089c525663436812a65ff13de7964a1c3a9110f32054f2d58aa476a645bb14f75a79f3fe1154fb3e7bfdae7ac1e80affe171b2ef74bce languageName: node linkType: hard -"on-finished@npm:2.4.1, on-finished@npm:^2.4.1": +"on-finished@npm:2.4.1, on-finished@npm:^2.3.0, on-finished@npm:^2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" dependencies: @@ -4425,7 +4831,7 @@ __metadata: languageName: node linkType: hard -"on-finished@npm:^2.3.0, on-finished@npm:~2.3.0": +"on-finished@npm:~2.3.0": version: 2.3.0 resolution: "on-finished@npm:2.3.0" dependencies: @@ -4434,10 +4840,10 @@ __metadata: languageName: node linkType: hard -"on-headers@npm:~1.0.1": - version: 1.0.2 - resolution: "on-headers@npm:1.0.2" - checksum: 10/870766c16345855e2012e9422ba1ab110c7e44ad5891a67790f84610bd70a72b67fdd71baf497295f1d1bf38dd4c92248f825d48729c53c0eae5262fb69fa171 +"on-headers@npm:~1.1.0": + version: 1.1.0 + resolution: "on-headers@npm:1.1.0" + checksum: 10/98aa64629f986fb8cc4517dd8bede73c980e31208cba97f4442c330959f60ced3dc6214b83420491f5111fc7c4f4343abe2ea62c85f505cf041d67850f238776 languageName: node linkType: hard @@ -4451,11 +4857,11 @@ __metadata: linkType: hard "onetime@npm:^5.1.0": - version: 5.1.0 - resolution: "onetime@npm:5.1.0" + version: 5.1.2 + resolution: "onetime@npm:5.1.2" dependencies: mimic-fn: "npm:^2.1.0" - checksum: 10/a2beeef8b927797b8d549cff01faf3458c963215bfeb579964acb43b06d3f20ee2d5ef571dda5009df5beecb62f8f9e509ed1724977fed115915ad30df796b13 + checksum: 10/e9fd0695a01cf226652f0385bf16b7a24153dbbb2039f764c8ba6d2306a8506b0e4ce570de6ad99c7a6eb49520743afdb66edd95ee979c1a342554ed49a9aadd languageName: node linkType: hard @@ -4487,30 +4893,23 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^1.1.0": - version: 1.3.0 - resolution: "p-limit@npm:1.3.0" +"own-keys@npm:^1.0.1": + version: 1.0.1 + resolution: "own-keys@npm:1.0.1" dependencies: - p-try: "npm:^1.0.0" - checksum: 10/eb9d9bc378d48ab1998d2a2b2962a99eddd3e3726c82d3258ecc1a475f22907968edea4fec2736586d100366a001c6bb449a2abe6cd65e252e9597394f01e789 + get-intrinsic: "npm:^1.2.6" + object-keys: "npm:^1.1.1" + safe-push-apply: "npm:^1.0.0" + checksum: 10/ab4bb3b8636908554fc19bf899e225444195092864cb61503a0d048fdaf662b04be2605b636a4ffeaf6e8811f6fcfa8cbb210ec964c0eb1a41eb853e1d5d2f41 languageName: node linkType: hard "p-limit@npm:^2.0.0, p-limit@npm:^2.2.0": - version: 2.2.2 - resolution: "p-limit@npm:2.2.2" + version: 2.3.0 + resolution: "p-limit@npm:2.3.0" dependencies: p-try: "npm:^2.0.0" - checksum: 10/20c395084f4bbcb6330c21ed5408a482c7e03bb68fbf9d4667fb0f49de944a7474f788b214ca087090868dfce3f3fff4a4830b0951c8a979469786635b172fb2 - languageName: node - linkType: hard - -"p-locate@npm:^2.0.0": - version: 2.0.0 - resolution: "p-locate@npm:2.0.0" - dependencies: - p-limit: "npm:^1.1.0" - checksum: 10/e2dceb9b49b96d5513d90f715780f6f4972f46987dc32a0e18bc6c3fc74a1a5d73ec5f81b1398af5e58b99ea1ad03fd41e9181c01fa81b4af2833958696e3081 + checksum: 10/84ff17f1a38126c3314e91ecfe56aecbf36430940e2873dadaa773ffe072dc23b7af8e46d4b6485d302a11673fe94c6b67ca2cfbb60c989848b02100d0594ac1 languageName: node linkType: hard @@ -4541,19 +4940,10 @@ __metadata: languageName: node linkType: hard -"p-map@npm:^4.0.0": - version: 4.0.0 - resolution: "p-map@npm:4.0.0" - dependencies: - aggregate-error: "npm:^3.0.0" - checksum: 10/7ba4a2b1e24c05e1fc14bbaea0fc6d85cf005ae7e9c9425d4575550f37e2e584b1af97bcde78eacd7559208f20995988d52881334db16cf77bc1bcf68e48ed7c - languageName: node - linkType: hard - -"p-try@npm:^1.0.0": - version: 1.0.0 - resolution: "p-try@npm:1.0.0" - checksum: 10/20d9735f57258158df50249f172c77fe800d31e80f11a3413ac9e68ccbe6b11798acb3f48f2df8cea7ba2b56b753ce695a4fe2a2987c3c7691c44226b6d82b6f +"p-map@npm:^7.0.2": + version: 7.0.3 + resolution: "p-map@npm:7.0.3" + checksum: 10/2ef48ccfc6dd387253d71bf502604f7893ed62090b2c9d73387f10006c342606b05233da0e4f29388227b61eb5aeface6197e166520c465c234552eeab2fe633 languageName: node linkType: hard @@ -4576,6 +4966,13 @@ __metadata: languageName: node linkType: hard +"package-json-from-dist@npm:^1.0.0": + version: 1.0.1 + resolution: "package-json-from-dist@npm:1.0.1" + checksum: 10/58ee9538f2f762988433da00e26acc788036914d57c71c246bf0be1b60cdbd77dd60b6a3e1a30465f0b248aeb80079e0b34cb6050b1dfa18c06953bb1cbc7602 + languageName: node + linkType: hard + "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -4585,15 +4982,6 @@ __metadata: languageName: node linkType: hard -"parse-json@npm:^2.2.0": - version: 2.2.0 - resolution: "parse-json@npm:2.2.0" - dependencies: - error-ex: "npm:^1.2.0" - checksum: 10/39924c0ddbf6f2544ab92acea61d91a0fb0ac959b0d19d273468cf8aa977522f8076e8fbb29cdab75c1440ebc2e172389988274890373d95fe308837074cc7e0 - languageName: node - linkType: hard - "parseurl@npm:^1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" @@ -4636,10 +5024,20 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: "npm:^10.2.0" + minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" + checksum: 10/5e8845c159261adda6f09814d7725683257fcc85a18f329880ab4d7cc1d12830967eae5d5894e453f341710d5484b8fdbbd4d75181b4d6e1eb2f4dc7aeadc434 + languageName: node + linkType: hard + "path-to-regexp@npm:^6.2.1": - version: 6.2.2 - resolution: "path-to-regexp@npm:6.2.2" - checksum: 10/f7d11c1a9e02576ce0294f4efdc523c11b73894947afdf7b23a0d0f7c6465d7a7772166e770ddf1495a8017cc0ee99e3e8a15ed7302b6b948b89a6dd4eea895e + version: 6.3.0 + resolution: "path-to-regexp@npm:6.3.0" + checksum: 10/6822f686f01556d99538b350722ef761541ec0ce95ca40ce4c29e20a5b492fe8361961f57993c71b2418de12e604478dcf7c430de34b2c31a688363a7a944d9c languageName: node linkType: hard @@ -4650,15 +5048,6 @@ __metadata: languageName: node linkType: hard -"path-type@npm:^2.0.0": - version: 2.0.0 - resolution: "path-type@npm:2.0.0" - dependencies: - pify: "npm:^2.0.0" - checksum: 10/749dc0c32d4ebe409da155a0022f9be3d08e6fd276adb3dfa27cb2486519ab2aa277d1453b3fde050831e0787e07b0885a75653fefcc82d883753c5b91121b1c - languageName: node - linkType: hard - "path@npm:^0.12.7": version: 0.12.7 resolution: "path@npm:0.12.7" @@ -4676,26 +5065,24 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^2.0.4": - version: 2.2.1 - resolution: "picomatch@npm:2.2.1" - checksum: 10/886eafac759bb447e83ee7e3e93219ed19836574d4a03d90cd1b0f3b12d8cd042b5ef32c0b97bc8967d1f85e0b3b845768dd4a34579fcb79a011aa7451c8b158 +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10/e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 languageName: node linkType: hard -"pify@npm:^2.0.0": - version: 2.3.0 - resolution: "pify@npm:2.3.0" - checksum: 10/9503aaeaf4577acc58642ad1d25c45c6d90288596238fb68f82811c08104c800e5a7870398e9f015d82b44ecbcbef3dc3d4251a1cbb582f6e5959fe09884b2ba +"picomatch@npm:^2.0.4": + version: 2.3.1 + resolution: "picomatch@npm:2.3.1" + checksum: 10/60c2595003b05e4535394d1da94850f5372c9427ca4413b71210f437f7b2ca091dbd611c45e8b37d10036fa8eade25c1b8951654f9d3973bfa66a2ff4d3b08bc languageName: node linkType: hard -"pkg-dir@npm:^2.0.0": - version: 2.0.0 - resolution: "pkg-dir@npm:2.0.0" - dependencies: - find-up: "npm:^2.1.0" - checksum: 10/8c72b712305b51e1108f0ffda5ec1525a8307e54a5855db8fb1dcf77561a5ae98e2ba3b4814c9806a679f76b2f7e5dd98bde18d07e594ddd9fdd25e9cf242ea1 +"picomatch@npm:^4.0.2": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10/57b99055f40b16798f2802916d9c17e9744e620a0db136554af01d19598b96e45e2f00014c91d1b8b13874b80caa8c295b3d589a3f72373ec4aaf54baa5962d5 languageName: node linkType: hard @@ -4708,6 +5095,13 @@ __metadata: languageName: node linkType: hard +"possible-typed-array-names@npm:^1.0.0": + version: 1.1.0 + resolution: "possible-typed-array-names@npm:1.1.0" + checksum: 10/2f44137b8d3dd35f4a7ba7469eec1cd9cfbb46ec164b93a5bc1f4c3d68599c9910ee3b91da1d28b4560e9cc8414c3cd56fedc07259c67e52cc774476270d3302 + languageName: node + linkType: hard + "prelude-ls@npm:~1.1.2": version: 1.1.2 resolution: "prelude-ls@npm:1.1.2" @@ -4715,19 +5109,19 @@ __metadata: languageName: node linkType: hard -"process-nextick-args@npm:~2.0.0": - version: 2.0.1 - resolution: "process-nextick-args@npm:2.0.1" - checksum: 10/1d38588e520dab7cea67cbbe2efdd86a10cc7a074c09657635e34f035277b59fbb57d09d8638346bf7090f8e8ebc070c96fa5fd183b777fff4f5edff5e9466cf +"proc-log@npm:^5.0.0": + version: 5.0.0 + resolution: "proc-log@npm:5.0.0" + checksum: 10/35610bdb0177d3ab5d35f8827a429fb1dc2518d9e639f2151ac9007f01a061c30e0c635a970c9b00c39102216160f6ec54b62377c92fac3b7bfc2ad4b98d195c languageName: node linkType: hard "process-on-spawn@npm:^1.0.0": - version: 1.0.0 - resolution: "process-on-spawn@npm:1.0.0" + version: 1.1.0 + resolution: "process-on-spawn@npm:1.1.0" dependencies: fromentries: "npm:^1.2.0" - checksum: 10/8795d71742798e5a059e13da2a9c13988aa7c673a3a57f276c1ff6ed942ba9b7636139121c6a409eaa2ea6a8fda7af4be19c3dc576320515bb3f354e3544106e + checksum: 10/4cc56df51bf54d7629c1857e472c9440984d230c4a4dfdfc2de25abcee57b3d8f4bdfb0b9ad65fe7eea11a7a10f03474c3e8c5eb554454d32c86444e635c85f8 languageName: node linkType: hard @@ -4739,16 +5133,9 @@ __metadata: linkType: hard "progress@npm:^2.0.0": - version: 2.0.0 - resolution: "progress@npm:2.0.0" - checksum: 10/858d086e3b8eaf75209194c59a9354650b52fb6a2a4dd79dc5f0b0edd53b5af2002a600faa715514f3d35a1bba2f46fec1df4755d502e86e2d340e6e7d08bf3b - languageName: node - linkType: hard - -"promise-inflight@npm:^1.0.1": - version: 1.0.1 - resolution: "promise-inflight@npm:1.0.1" - checksum: 10/1560d413ea20c5a74f3631d39ba8cbd1972b9228072a755d01e1f5ca5110382d9af76a1582d889445adc6e75bb5ac4886b56dc4b6eae51b30145d7bb1ac7505b + version: 2.0.3 + resolution: "progress@npm:2.0.3" + checksum: 10/e6f0bcb71f716eee9dfac0fe8a2606e3704d6a64dd93baaf49fbadbc8499989a610fe14cf1bc6f61b6d6653c49408d94f4a94e124538084efd8e4cf525e0293d languageName: node linkType: hard @@ -4791,18 +5178,18 @@ __metadata: linkType: hard "punycode@npm:^2.1.0": - version: 2.1.1 - resolution: "punycode@npm:2.1.1" - checksum: 10/939daa010c2cacebdb060c40ecb52fef0a739324a66f7fffe0f94353a1ee83e3b455e9032054c4a0c4977b0a28e27086f2171c392832b59a01bd948fd8e20914 + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10/febdc4362bead22f9e2608ff0171713230b57aff9dddc1c273aa2a651fbd366f94b7d6a71d78342a7c0819906750351ca7f2edd26ea41b626d87d6a13d1bd059 languageName: node linkType: hard "qs@npm:^6.11.0": - version: 6.11.0 - resolution: "qs@npm:6.11.0" + version: 6.14.0 + resolution: "qs@npm:6.14.0" dependencies: - side-channel: "npm:^1.0.4" - checksum: 10/5a3bfea3e2f359ede1bfa5d2f0dbe54001aa55e40e27dc3e60fab814362d83a9b30758db057c2011b6f53a2d4e4e5150194b5bac45372652aecb3e3c0d4b256e + side-channel: "npm:^1.1.0" + checksum: 10/a60e49bbd51c935a8a4759e7505677b122e23bf392d6535b8fc31c1e447acba2c901235ecb192764013cd2781723dc1f61978b5fdd93cc31d7043d31cdc01974 languageName: node linkType: hard @@ -4837,53 +5224,6 @@ __metadata: languageName: node linkType: hard -"read-pkg-up@npm:^2.0.0": - version: 2.0.0 - resolution: "read-pkg-up@npm:2.0.0" - dependencies: - find-up: "npm:^2.0.0" - read-pkg: "npm:^2.0.0" - checksum: 10/22f9026fb72219ecd165f94f589461c70a88461dc7ea0d439a310ef2a5271ff176a4df4e5edfad087d8ac89b8553945eb209476b671e8ed081c990f30fc40b27 - languageName: node - linkType: hard - -"read-pkg@npm:^2.0.0": - version: 2.0.0 - resolution: "read-pkg@npm:2.0.0" - dependencies: - load-json-file: "npm:^2.0.0" - normalize-package-data: "npm:^2.3.2" - path-type: "npm:^2.0.0" - checksum: 10/85c5bf35f2d96acdd756151ba83251831bb2b1040b7d96adce70b2cb119b5320417f34876de0929f2d06c67f3df33ef4636427df3533913876f9ef2487a6f48f - languageName: node - linkType: hard - -"readable-stream@npm:^2.3.5": - version: 2.3.7 - resolution: "readable-stream@npm:2.3.7" - dependencies: - core-util-is: "npm:~1.0.0" - inherits: "npm:~2.0.3" - isarray: "npm:~1.0.0" - process-nextick-args: "npm:~2.0.0" - safe-buffer: "npm:~5.1.1" - string_decoder: "npm:~1.1.1" - util-deprecate: "npm:~1.0.1" - checksum: 10/d04c677c1705e3fc6283d45859a23f4c05243d0c0f1fc08cb8f995b4d69f0eb7f38ec0ec102f0ee20535c5d999ee27449f40aa2edf6bf30c24d0cc8f8efeb6d7 - languageName: node - linkType: hard - -"readable-stream@npm:^3.6.0": - version: 3.6.0 - resolution: "readable-stream@npm:3.6.0" - dependencies: - inherits: "npm:^2.0.3" - string_decoder: "npm:^1.1.1" - util-deprecate: "npm:^1.0.1" - checksum: 10/b80b3e6a7fafb1c79de7db541de357f4a5ee73bd70c21672f5a7c840d27bb27bdb0151e7ba2fd82c4a888df22ce0c501b0d9f3e4dfe51688876701c437d59536 - languageName: node - linkType: hard - "readdirp@npm:~3.2.0": version: 3.2.0 resolution: "readdirp@npm:3.2.0" @@ -4894,9 +5234,9 @@ __metadata: linkType: hard "redis-commands@npm:^1.2.0": - version: 1.3.5 - resolution: "redis-commands@npm:1.3.5" - checksum: 10/bcb25f1095621777c6699acb0c2700ea442f39f1af9687d28a437780de646b0253ec2adafca89d9b55a67ce9e277c1195b1d9e5deb025e1beb23b6dcd230829c + version: 1.7.0 + resolution: "redis-commands@npm:1.7.0" + checksum: 10/c3c86ecefb7552d4333024dba8e0f1f6516568c2a74fd41643768781fb909524c7a581027d75e2456be1f0b7f08505c4c2252c6234abe044626455ef645c9459 languageName: node linkType: hard @@ -4907,6 +5247,36 @@ __metadata: languageName: node linkType: hard +"reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.9": + version: 1.0.10 + resolution: "reflect.getprototypeof@npm:1.0.10" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.9" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.7" + get-proto: "npm:^1.0.1" + which-builtin-type: "npm:^1.2.1" + checksum: 10/80a4e2be716f4fe46a89a08ccad0863b47e8ce0f49616cab2d65dab0fbd53c6fdba0f52935fd41d37a2e4e22355c272004f920d63070de849f66eea7aeb4a081 + languageName: node + linkType: hard + +"regexp.prototype.flags@npm:^1.5.4": + version: 1.5.4 + resolution: "regexp.prototype.flags@npm:1.5.4" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-errors: "npm:^1.3.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + set-function-name: "npm:^2.0.2" + checksum: 10/8ab897ca445968e0b96f6237641510f3243e59c180ee2ee8d83889c52ff735dd1bf3657fcd36db053e35e1d823dd53f2565d0b8021ea282c9fe62401c6c3bd6d + languageName: node + linkType: hard + "regexpp@npm:^2.0.1": version: 2.0.1 resolution: "regexpp@npm:2.0.1" @@ -4956,12 +5326,12 @@ __metadata: linkType: hard "requirejs@npm:~2.3.6": - version: 2.3.6 - resolution: "requirejs@npm:2.3.6" + version: 2.3.7 + resolution: "requirejs@npm:2.3.7" bin: - r.js: ./bin/r.js - r_js: ./bin/r.js - checksum: 10/808540b0a2374cf19bf00d13036a90f94aac92984a9be0f1fa642266d0ee467db0b1aa9d85d567e0da71b54294f5feb92e13ac62bae7f85a0b3ac3ab393b05d4 + r.js: bin/r.js + r_js: bin/r.js + checksum: 10/7f42af10d8af210a2769501004b34f9dad4c39716f008f91faeef36c33b60a5f372ca1025c8f40b262f2522fd1ea0f36fd11e9587cba14311fac0df51a8571b0 languageName: node linkType: hard @@ -4979,16 +5349,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.10.0, resolve@npm:^1.12.0, resolve@npm:^1.13.1, resolve@npm:^1.3.2": - version: 1.15.1 - resolution: "resolve@npm:1.15.1" - dependencies: - path-parse: "npm:^1.0.6" - checksum: 10/0e30fc3240b45ef4111daf5def7ab61867833600b920ded23f80e52c9f55b9979f04d9e42610e294da1e09e96c021983e813ad030418912a397c4fde91bb3921 - languageName: node - linkType: hard - -"resolve@npm:^1.11.1, resolve@npm:^1.22.8": +"resolve@npm:^1.11.1, resolve@npm:^1.22.4, resolve@npm:^1.22.8": version: 1.22.10 resolution: "resolve@npm:1.22.10" dependencies: @@ -5001,16 +5362,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.12.0#optional!builtin, resolve@patch:resolve@npm%3A^1.13.1#optional!builtin, resolve@patch:resolve@npm%3A^1.3.2#optional!builtin": - version: 1.15.1 - resolution: "resolve@patch:resolve@npm%3A1.15.1#optional!builtin::version=1.15.1&hash=c3c19d" - dependencies: - path-parse: "npm:^1.0.6" - checksum: 10/ce61fdbeb623e245a2e8188a0157b13084d76fef759b7850ae21849e44945ce1b7624a6e2989cbc01190fdb6d18f62d64795066d5e86ad9917864a09dfad34b6 - languageName: node - linkType: hard - -"resolve@patch:resolve@npm%3A^1.11.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": +"resolve@patch:resolve@npm%3A^1.11.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": version: 1.22.10 resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" dependencies: @@ -5051,7 +5403,7 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": +"rimraf@npm:^3.0.0": version: 3.0.2 resolution: "rimraf@npm:3.0.2" dependencies: @@ -5083,27 +5435,38 @@ __metadata: linkType: hard "run-async@npm:^2.4.0": - version: 2.4.0 - resolution: "run-async@npm:2.4.0" - dependencies: - is-promise: "npm:^2.1.0" - checksum: 10/94ce9f7c58e3d69dac09917d9375cd05af5b8d20af8a722193fb5f66cf3234bb97ab15504b1f3aa7f7987da62679577b650a61f80cfb6b66de77d7446196f41d + version: 2.4.1 + resolution: "run-async@npm:2.4.1" + checksum: 10/c79551224dafa26ecc281cb1efad3510c82c79116aaf681f8a931ce70fdf4ca880d58f97d3b930a38992c7aad7955a08e065b32ec194e1dd49d7790c874ece50 languageName: node linkType: hard -"rxjs@npm:^6.5.3": - version: 6.5.4 - resolution: "rxjs@npm:6.5.4" +"rxjs@npm:^6.6.0": + version: 6.6.7 + resolution: "rxjs@npm:6.6.7" dependencies: tslib: "npm:^1.9.0" - checksum: 10/4b6f1cd018a6b41ac1feee26e18e8d62c583d9f3a8aa3e79ec5d39b16691ec015f2fe22f9646c589824af94316a2e278a3da1c91f051508ac4733f8922bea28f + checksum: 10/c8263ebb20da80dd7a91c452b9e96a178331f402344bbb40bc772b56340fcd48d13d1f545a1e3d8e464893008c5e306cc42a1552afe0d562b1a6d4e1e6262b03 languageName: node linkType: hard -"safe-buffer@npm:5.1.1": - version: 5.1.1 - resolution: "safe-buffer@npm:5.1.1" - checksum: 10/e8acac337b7d7e108fcfe2b8b2cb20952abb1ed11dc60968b7adffb19b9477893d44136987a420f90ff4d7a0a1a932f147b3a222f73001f59fb4822097a1616d +"safe-array-concat@npm:^1.1.2, safe-array-concat@npm:^1.1.3": + version: 1.1.3 + resolution: "safe-array-concat@npm:1.1.3" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + get-intrinsic: "npm:^1.2.6" + has-symbols: "npm:^1.1.0" + isarray: "npm:^2.0.5" + checksum: 10/fac4f40f20a3f7da024b54792fcc61059e814566dcbb04586bfefef4d3b942b2408933f25b7b3dd024affd3f2a6bbc916bef04807855e4f192413941369db864 + languageName: node + linkType: hard + +"safe-buffer@npm:5.1.2": + version: 5.1.2 + resolution: "safe-buffer@npm:5.1.2" + checksum: 10/7eb5b48f2ed9a594a4795677d5a150faa7eb54483b2318b568dc0c4fc94092a6cce5be02c7288a0500a156282f5276d5688bce7259299568d1053b2150ef374a languageName: node linkType: hard @@ -5114,17 +5477,24 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": - version: 5.1.2 - resolution: "safe-buffer@npm:5.1.2" - checksum: 10/7eb5b48f2ed9a594a4795677d5a150faa7eb54483b2318b568dc0c4fc94092a6cce5be02c7288a0500a156282f5276d5688bce7259299568d1053b2150ef374a +"safe-push-apply@npm:^1.0.0": + version: 1.0.0 + resolution: "safe-push-apply@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + isarray: "npm:^2.0.5" + checksum: 10/2bd4e53b6694f7134b9cf93631480e7fafc8637165f0ee91d5a4af5e7f33d37de9562d1af5021178dd4217d0230cde8d6530fa28cfa1ebff9a431bf8fff124b4 languageName: node linkType: hard -"safe-buffer@npm:~5.2.0": - version: 5.2.0 - resolution: "safe-buffer@npm:5.2.0" - checksum: 10/2e8f7b7936104df9b72d6d2ef10d6a07bc0af874ec0f331ada58f302eb2f10ea91df01eca59cf858de9510d8407ea7293afec23752dbb2317ca8bf737ca00110 +"safe-regex-test@npm:^1.1.0": + version: 1.1.0 + resolution: "safe-regex-test@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-regex: "npm:^1.2.1" + checksum: 10/ebdb61f305bf4756a5b023ad86067df5a11b26898573afe9e52a548a63c3bd594825d9b0e2dde2eb3c94e57e0e04ac9929d4107c394f7b8e56a4613bed46c69a languageName: node linkType: hard @@ -5182,10 +5552,47 @@ __metadata: languageName: node linkType: hard -"setprototypeof@npm:1.0.3": - version: 1.0.3 - resolution: "setprototypeof@npm:1.0.3" - checksum: 10/ae520af039feeb3364be586c48c82c6b9eb5bd6834e440b42bfd54f714fcdabab123ff850a3142c00ce1f37eb4c395f06ed4a5ce33b2c3cbf52b36b8297b8cc2 +"set-function-length@npm:^1.2.2": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + gopd: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.2" + checksum: 10/505d62b8e088468917ca4e3f8f39d0e29f9a563b97dbebf92f4bd2c3172ccfb3c5b8e4566d5fcd00784a00433900e7cb8fbc404e2dbd8c3818ba05bb9d4a8a6d + languageName: node + linkType: hard + +"set-function-name@npm:^2.0.2": + version: 2.0.2 + resolution: "set-function-name@npm:2.0.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + functions-have-names: "npm:^1.2.3" + has-property-descriptors: "npm:^1.0.2" + checksum: 10/c7614154a53ebf8c0428a6c40a3b0b47dac30587c1a19703d1b75f003803f73cdfa6a93474a9ba678fa565ef5fbddc2fae79bca03b7d22ab5fd5163dbe571a74 + languageName: node + linkType: hard + +"set-proto@npm:^1.0.0": + version: 1.0.0 + resolution: "set-proto@npm:1.0.0" + dependencies: + dunder-proto: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + checksum: 10/b87f8187bca595ddc3c0721ece4635015fd9d7cb294e6dd2e394ce5186a71bbfa4dc8a35010958c65e43ad83cde09642660e61a952883c24fd6b45ead15f045c + languageName: node + linkType: hard + +"setprototypeof@npm:1.1.0": + version: 1.1.0 + resolution: "setprototypeof@npm:1.1.0" + checksum: 10/02d2564e02a260551bab3ec95358dcfde775fe61272b1b7c488de3676a4bb79f280b5668a324aebe0ec73f0d8ba408bc2d816a609ee5d93b1a7936b9d4ba1208 languageName: node linkType: hard @@ -5219,38 +5626,75 @@ __metadata: languageName: node linkType: hard -"side-channel@npm:^1.0.4": - version: 1.0.4 - resolution: "side-channel@npm:1.0.4" +"side-channel-list@npm:^1.0.0": + version: 1.0.0 + resolution: "side-channel-list@npm:1.0.0" dependencies: - call-bind: "npm:^1.0.0" - get-intrinsic: "npm:^1.0.2" - object-inspect: "npm:^1.9.0" - checksum: 10/c4998d9fc530b0e75a7fd791ad868fdc42846f072734f9080ff55cc8dc7d3899abcda24fd896aa6648c3ab7021b4bb478073eb4f44dfd55bce9714bc1a7c5d45 + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + checksum: 10/603b928997abd21c5a5f02ae6b9cc36b72e3176ad6827fab0417ead74580cc4fb4d5c7d0a8a2ff4ead34d0f9e35701ed7a41853dac8a6d1a664fcce1a044f86f languageName: node linkType: hard -"signal-exit@npm:^3.0.2": - version: 3.0.2 - resolution: "signal-exit@npm:3.0.2" - checksum: 10/ccc08b9ad53644154d274ed147bb5e6cd5fd09c81bc6480a93bbe581f9030a599882907f78b305b81214ea725be7c09ed9182b58c675a148a1fe48cd50e43b2b +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 10/5771861f77feefe44f6195ed077a9e4f389acc188f895f570d56445e251b861754b547ea9ef73ecee4e01fdada6568bfe9020d2ec2dfc5571e9fa1bbc4a10615 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 10/a815c89bc78c5723c714ea1a77c938377ea710af20d4fb886d362b0d1f8ac73a17816a5f6640f354017d7e292a43da9c5e876c22145bac00b76cfb3468001736 + languageName: node + linkType: hard + +"side-channel@npm:^1.1.0": + version: 1.1.0 + resolution: "side-channel@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + side-channel-list: "npm:^1.0.0" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: 10/7d53b9db292c6262f326b6ff3bc1611db84ece36c2c7dc0e937954c13c73185b0406c56589e2bb8d071d6fee468e14c39fb5d203ee39be66b7b8174f179afaba languageName: node linkType: hard -"signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.2": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: 10/a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 languageName: node linkType: hard +"signal-exit@npm:^4.0.1": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 10/c9fa63bbbd7431066174a48ba2dd9986dfd930c3a8b59de9c29d7b6854ec1c12a80d15310869ea5166d413b99f041bfa3dd80a7947bcd44ea8e6eb3ffeabfa1f + languageName: node + linkType: hard + "sinon-chai@npm:^3.5.0": - version: 3.5.0 - resolution: "sinon-chai@npm:3.5.0" + version: 3.7.0 + resolution: "sinon-chai@npm:3.7.0" peerDependencies: chai: ^4.0.0 - sinon: ">=4.0.0 <10.0.0" - checksum: 10/e160c5f535dc504d22a161198930b9138727bb7058b1a703a35ee65cc2a0e18580e5f983bd89960ded55d3737d9b6cb02c3803280b2b2eaa9e11acc39933ff58 + sinon: ">=4.0.0" + checksum: 10/028853eb8a545ca613c6863014a40f07d1e6b81467e20939fefcd13f170206d24165b91099fb297aeb4d137745e321da25daa8e2d665cc0a78f90d5b877e8bbe languageName: node linkType: hard @@ -5296,21 +5740,22 @@ __metadata: linkType: hard "socket.io-adapter@npm:~2.5.2": - version: 2.5.2 - resolution: "socket.io-adapter@npm:2.5.2" + version: 2.5.5 + resolution: "socket.io-adapter@npm:2.5.5" dependencies: - ws: "npm:~8.11.0" - checksum: 10/08b052d6b487399cdf753ef5cf6941c6da2b8927994580b65dac0918a3a3ab6a6b7906871adc09d53837beb13244e8897bfa670f558c7231ac87ebe995dbc55e + debug: "npm:~4.3.4" + ws: "npm:~8.17.1" + checksum: 10/e364733a4c34ff1d4a02219e409bd48074fd614b7f5b0568ccfa30dd553252a5b9a41056931306a276891d13ea76a19e2c6f2128a4675c37323f642896874d80 languageName: node linkType: hard -"socket.io-parser@npm:~4.2.1": - version: 4.2.2 - resolution: "socket.io-parser@npm:4.2.2" +"socket.io-parser@npm:~4.2.4": + version: 4.2.4 + resolution: "socket.io-parser@npm:4.2.4" dependencies: "@socket.io/component-emitter": "npm:~3.1.0" debug: "npm:~4.3.1" - checksum: 10/6ab2df5bfe070475b7134334018cb5fd3962905e6512fe666e13d1316e8f081aff84c2e67f1f775151180e29d9f29ef57ab8ee75d225fc77261e00f9d5efd04d + checksum: 10/4be500a9ff7e79c50ec25af11048a3ed34b4c003a9500d656786a1e5bceae68421a8394cf3eb0aa9041f85f36c1a9a737617f4aee91a42ab4ce16ffb2aa0c89c languageName: node linkType: hard @@ -5323,38 +5768,39 @@ __metadata: languageName: node linkType: hard -"socket.io@npm:^4.1.2": - version: 4.6.1 - resolution: "socket.io@npm:4.6.1" +"socket.io@npm:^4.8.1": + version: 4.8.1 + resolution: "socket.io@npm:4.8.1" dependencies: accepts: "npm:~1.3.4" base64id: "npm:~2.0.0" + cors: "npm:~2.8.5" debug: "npm:~4.3.2" - engine.io: "npm:~6.4.1" + engine.io: "npm:~6.6.0" socket.io-adapter: "npm:~2.5.2" - socket.io-parser: "npm:~4.2.1" - checksum: 10/5388ee122528d19f9adbfc8eb91b92785bd70e5a09b3b605d7e9cc7aa061abd9453724e9a4380e293a1c35f0b8dc1febc5012905f3ec311f9bf219aefd2a7e16 + socket.io-parser: "npm:~4.2.4" + checksum: 10/b9b362b7f63fc7ebb58482b8a3ade6c971da7783b7611dfeebaa8b02be23cb948137ec218491ccda8be57e434e97d65b64edf1e9811e5245b23a888d41636f4a languageName: node linkType: hard -"socks-proxy-agent@npm:^7.0.0": - version: 7.0.0 - resolution: "socks-proxy-agent@npm:7.0.0" +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" dependencies: - agent-base: "npm:^6.0.2" - debug: "npm:^4.3.3" - socks: "npm:^2.6.2" - checksum: 10/26c75d9c62a9ed3fd494df60e65e88da442f78e0d4bc19bfd85ac37bd2c67470d6d4bba5202e804561cda6674db52864c9e2a2266775f879bc8d89c1445a5f4c + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10/ee99e1dacab0985b52cbe5a75640be6e604135e9489ebdc3048635d186012fbaecc20fbbe04b177dee434c319ba20f09b3e7dfefb7d932466c0d707744eac05c languageName: node linkType: hard -"socks@npm:^2.6.2": - version: 2.7.1 - resolution: "socks@npm:2.7.1" +"socks@npm:^2.8.3": + version: 2.8.6 + resolution: "socks@npm:2.8.6" dependencies: - ip: "npm:^2.0.0" + ip-address: "npm:^9.0.5" smart-buffer: "npm:^4.2.0" - checksum: 10/5074f7d6a13b3155fa655191df1c7e7a48ce3234b8ccf99afa2ccb56591c195e75e8bb78486f8e9ea8168e95a29573cbaad55b2b5e195160ae4d2ea6811ba833 + checksum: 10/7aef197dee914a01a39d5f250a59f0c9fa0a9fd10f8135ee2cff6c42576993ae1c817503a12eb424f1bb69f1e234f8dbaf8cc48bfcfa10c51a12af8f0ea97698 languageName: node linkType: hard @@ -5367,13 +5813,6 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.5.0": - version: 0.5.7 - resolution: "source-map@npm:0.5.7" - checksum: 10/9b4ac749ec5b5831cad1f8cc4c19c4298ebc7474b24a0acf293e2f040f03f8eeccb3d01f12aa0f90cf46d555c887e03912b83a042c627f419bda5152d89c5269 - languageName: node - linkType: hard - "source-map@npm:^0.6.1": version: 0.6.1 resolution: "source-map@npm:0.6.1" @@ -5395,37 +5834,10 @@ __metadata: languageName: node linkType: hard -"spdx-correct@npm:^3.0.0": - version: 3.1.0 - resolution: "spdx-correct@npm:3.1.0" - dependencies: - spdx-expression-parse: "npm:^3.0.0" - spdx-license-ids: "npm:^3.0.0" - checksum: 10/7638519f17cfed6b170ea8922eed354ebd580d23ebb7cd47bf1d2f44ae3ffd5d9e87abed3bb8b2c3441ebb236b779cdcaa16cd3b043106ce2b447dfaadfbdf55 - languageName: node - linkType: hard - -"spdx-exceptions@npm:^2.1.0": - version: 2.2.0 - resolution: "spdx-exceptions@npm:2.2.0" - checksum: 10/29189de3f60ac6d74d84fa85cfc49ca6a838f710242db99d9414461c2c1717ca3f4aae59b2ce57a99cf6427adc62bdcc4c198fb7ae17383497e5e85cc851f8d7 - languageName: node - linkType: hard - -"spdx-expression-parse@npm:^3.0.0": - version: 3.0.0 - resolution: "spdx-expression-parse@npm:3.0.0" - dependencies: - spdx-exceptions: "npm:^2.1.0" - spdx-license-ids: "npm:^3.0.0" - checksum: 10/308c8c4925f3a584d5740e2d13615aa90e800fc16f9f794195723c9a3f56030096bf5cf34f68b2b05aedac292edd48fe7d51bac13e77e6f94abf921044e40248 - languageName: node - linkType: hard - -"spdx-license-ids@npm:^3.0.0": - version: 3.0.5 - resolution: "spdx-license-ids@npm:3.0.5" - checksum: 10/a5b78b6765826db9a98c890588e474fadb06dfaecc7b579b545ccc11f0a9739f097d11d741c1e0302562884f5d6e3ce0008ca68fa0036646831e0e61331b85b0 +"sprintf-js@npm:^1.1.3": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: 10/e7587128c423f7e43cc625fe2f87e6affdf5ca51c1cc468e910d8aaca46bb44a7fbcfa552f787b1d3987f7043aeb4527d1b99559e6621e01b42b3f45e5a24cbb languageName: node linkType: hard @@ -5436,12 +5848,12 @@ __metadata: languageName: node linkType: hard -"ssri@npm:^9.0.0": - version: 9.0.1 - resolution: "ssri@npm:9.0.1" +"ssri@npm:^12.0.0": + version: 12.0.0 + resolution: "ssri@npm:12.0.0" dependencies: - minipass: "npm:^3.1.1" - checksum: 10/7638a61e91432510718e9265d48d0438a17d53065e5184f1336f234ef6aa3479663942e41e97df56cda06bb24d9d0b5ef342c10685add3cac7267a82d7fa6718 + minipass: "npm:^7.0.3" + checksum: 10/7024c1a6e39b3f18aa8f1c8290e884fe91b0f9ca5a6c6d410544daad54de0ba664db879afe16412e187c6c292fd60b937f047ee44292e5c2af2dcc6d8e1a9b48 languageName: node linkType: hard @@ -5466,7 +5878,7 @@ __metadata: languageName: node linkType: hard -"statuses@npm:>= 1.3.1 < 2": +"statuses@npm:>= 1.4.0 < 2": version: 1.5.0 resolution: "statuses@npm:1.5.0" checksum: 10/c469b9519de16a4bb19600205cffb39ee471a5f17b82589757ca7bd40a8d92ebb6ed9f98b5a540c5d302ccbc78f15dc03cc0280dd6e00df1335568a5d5758a5c @@ -5480,17 +5892,17 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^1.0.2 || 2": - version: 2.1.1 - resolution: "string-width@npm:2.1.1" +"stop-iteration-iterator@npm:^1.1.0": + version: 1.1.0 + resolution: "stop-iteration-iterator@npm:1.1.0" dependencies: - is-fullwidth-code-point: "npm:^2.0.0" - strip-ansi: "npm:^4.0.0" - checksum: 10/d6173abe088c615c8dffaf3861dc5d5906ed3dc2d6fd67ff2bd2e2b5dce7fd683c5240699cf0b1b8aa679a3b3bd6b28b5053c824cb89b813d7f6541d8f89064a + es-errors: "npm:^1.3.0" + internal-slot: "npm:^1.1.0" + checksum: 10/ff36c4db171ee76c936ccfe9541946b77017f12703d4c446652017356816862d3aa029a64e7d4c4ceb484e00ed4a81789333896390d808458638f3a216aa1f41 languageName: node linkType: hard -"string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -5501,6 +5913,16 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^1.0.2 || 2": + version: 2.1.1 + resolution: "string-width@npm:2.1.1" + dependencies: + is-fullwidth-code-point: "npm:^2.0.0" + strip-ansi: "npm:^4.0.0" + checksum: 10/d6173abe088c615c8dffaf3861dc5d5906ed3dc2d6fd67ff2bd2e2b5dce7fd683c5240699cf0b1b8aa679a3b3bd6b28b5053c824cb89b813d7f6541d8f89064a + languageName: node + linkType: hard + "string-width@npm:^3.0.0, string-width@npm:^3.1.0": version: 3.1.0 resolution: "string-width@npm:3.1.0" @@ -5512,52 +5934,61 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^4.1.0, string-width@npm:^4.2.0": - version: 4.2.0 - resolution: "string-width@npm:4.2.0" +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" dependencies: - emoji-regex: "npm:^8.0.0" - is-fullwidth-code-point: "npm:^3.0.0" - strip-ansi: "npm:^6.0.0" - checksum: 10/ee2c68df9a3ce4256565d2bdc8490f5706f195f88e799d3d425889264d3eff3d7984fe8b38dfc983dac948e03d8cdc737294b1c81f1528c37c9935d86b67593d + eastasianwidth: "npm:^0.2.0" + emoji-regex: "npm:^9.2.2" + strip-ansi: "npm:^7.0.1" + checksum: 10/7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193 languageName: node linkType: hard -"string.prototype.trimleft@npm:^2.1.1": - version: 2.1.1 - resolution: "string.prototype.trimleft@npm:2.1.1" +"string.prototype.trim@npm:^1.2.10": + version: 1.2.10 + resolution: "string.prototype.trim@npm:1.2.10" dependencies: - define-properties: "npm:^1.1.3" - function-bind: "npm:^1.1.1" - checksum: 10/16a9086f21e5dbebaa3ad2b9c7f8d8cf9c0f0dc8a7af00ef2c6a39902e0c2e7c0245668ab13d30c21be7674bb74621b5bf31d21d5c08a1e302e8630c68ebe00d + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + define-data-property: "npm:^1.1.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-object-atoms: "npm:^1.0.0" + has-property-descriptors: "npm:^1.0.2" + checksum: 10/47bb63cd2470a64bc5e2da1e570d369c016ccaa85c918c3a8bb4ab5965120f35e66d1f85ea544496fac84b9207a6b722adf007e6c548acd0813e5f8a82f9712a languageName: node linkType: hard -"string.prototype.trimright@npm:^2.1.1": - version: 2.1.1 - resolution: "string.prototype.trimright@npm:2.1.1" +"string.prototype.trimend@npm:^1.0.9": + version: 1.0.9 + resolution: "string.prototype.trimend@npm:1.0.9" dependencies: - define-properties: "npm:^1.1.3" - function-bind: "npm:^1.1.1" - checksum: 10/8ac0f2145fe04a90bd4854ba08954b8e4939eead45dcfa95e0505e3e18385c3897196969e981b426a004f3b1c8b3e50a21fd5aced9107cefb404b3ec5fc78271 + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10/140c73899b6747de9e499c7c2e7a83d549c47a26fa06045b69492be9cfb9e2a95187499a373983a08a115ecff8bc3bd7b0fb09b8ff72fb2172abe766849272ef languageName: node linkType: hard -"string_decoder@npm:^1.1.1": - version: 1.3.0 - resolution: "string_decoder@npm:1.3.0" +"string.prototype.trimstart@npm:^1.0.8": + version: 1.0.8 + resolution: "string.prototype.trimstart@npm:1.0.8" dependencies: - safe-buffer: "npm:~5.2.0" - checksum: 10/54d23f4a6acae0e93f999a585e673be9e561b65cd4cca37714af1e893ab8cd8dfa52a9e4f58f48f87b4a44918d3a9254326cb80ed194bf2e4c226e2b21767e56 + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10/160167dfbd68e6f7cb9f51a16074eebfce1571656fc31d40c3738ca9e30e35496f2c046fe57b6ad49f65f238a152be8c86fd9a2dd58682b5eba39dad995b3674 languageName: node linkType: hard -"string_decoder@npm:~1.1.1": - version: 1.1.1 - resolution: "string_decoder@npm:1.1.1" +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" dependencies: - safe-buffer: "npm:~5.1.0" - checksum: 10/7c41c17ed4dea105231f6df208002ebddd732e8e9e2d619d133cecd8e0087ddfd9587d2feb3c8caf3213cbd841ada6d057f5142cae68a4e62d3540778d9819b4 + ansi-regex: "npm:^5.0.1" + checksum: 10/ae3b5436d34fadeb6096367626ce987057713c566e1e7768818797e00ac5d62023d0f198c4e681eae9e20701721980b26a64a8f5b91238869592a9c6800719a2 languageName: node linkType: hard @@ -5579,21 +6010,12 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^6.0.0": - version: 6.0.0 - resolution: "strip-ansi@npm:6.0.0" - dependencies: - ansi-regex: "npm:^5.0.0" - checksum: 10/fb33042c065e35dd33f82daf780252c855c520250bf2cc9257718e2868efbfd93f0712e0efc5e90750a0f806ad73971c1ac67785b532563df18aad4fddfde74d - languageName: node - linkType: hard - -"strip-ansi@npm:^6.0.1": - version: 6.0.1 - resolution: "strip-ansi@npm:6.0.1" +"strip-ansi@npm:^7.0.1": + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" dependencies: - ansi-regex: "npm:^5.0.1" - checksum: 10/ae3b5436d34fadeb6096367626ce987057713c566e1e7768818797e00ac5d62023d0f198c4e681eae9e20701721980b26a64a8f5b91238869592a9c6800719a2 + ansi-regex: "npm:^6.0.1" + checksum: 10/475f53e9c44375d6e72807284024ac5d668ee1d06010740dec0b9744f2ddf47de8d7151f80e5f6190fc8f384e802fdf9504b76a7e9020c9faee7103623338be2 languageName: node linkType: hard @@ -5619,56 +6041,54 @@ __metadata: linkType: hard "strip-json-comments@npm:^3.0.1": - version: 3.0.1 - resolution: "strip-json-comments@npm:3.0.1" - checksum: 10/2b860124c04b9b4ac09ec63c17fea142c789ea99b30569240f63c91917c3a8fdc250fc799280bc80dbbad1cccbcfc5f662636f960f80ce660e230f770c3f3a95 + version: 3.1.1 + resolution: "strip-json-comments@npm:3.1.1" + checksum: 10/492f73e27268f9b1c122733f28ecb0e7e8d8a531a6662efbd08e22cccb3f9475e90a1b82cab06a392f6afae6d2de636f977e231296400d0ec5304ba70f166443 languageName: node linkType: hard -"superagent@npm:7": - version: 7.1.5 - resolution: "superagent@npm:7.1.5" +"superagent@npm:^10.2.2, superagent@npm:^10.2.3": + version: 10.2.3 + resolution: "superagent@npm:10.2.3" dependencies: - component-emitter: "npm:^1.3.0" - cookiejar: "npm:^2.1.3" - debug: "npm:^4.3.4" + component-emitter: "npm:^1.3.1" + cookiejar: "npm:^2.1.4" + debug: "npm:^4.3.7" fast-safe-stringify: "npm:^2.1.1" - form-data: "npm:^4.0.0" - formidable: "npm:^2.0.1" + form-data: "npm:^4.0.4" + formidable: "npm:^3.5.4" methods: "npm:^1.1.2" - mime: "npm:^2.5.0" - qs: "npm:^6.10.3" - readable-stream: "npm:^3.6.0" - semver: "npm:^7.3.7" - checksum: 10/30415cd6b773359c3b70023aff941ddb3d40673ff6e55916b21fbd9c8700ad01849f871a65092c518e80ed4e3439a05505a321243d25d57b915ea725ef01b501 + mime: "npm:2.6.0" + qs: "npm:^6.11.2" + checksum: 10/377bf938e68927dd772169c5285be27872bf6e84fac01c52bcd9396bc5b348c9ded8f8be54649510ec09a67bc5096055847b37cb01b3bca0eb06ff1856170e35 languageName: node linkType: hard -"superagent@npm:^3.7.0, superagent@npm:^3.8.3": - version: 3.8.3 - resolution: "superagent@npm:3.8.3" +"superagent@npm:^8.0.9": + version: 8.1.2 + resolution: "superagent@npm:8.1.2" dependencies: - component-emitter: "npm:^1.2.0" - cookiejar: "npm:^2.1.0" - debug: "npm:^3.1.0" - extend: "npm:^3.0.0" - form-data: "npm:^2.3.1" - formidable: "npm:^1.2.0" - methods: "npm:^1.1.1" - mime: "npm:^1.4.1" - qs: "npm:^6.5.1" - readable-stream: "npm:^2.3.5" - checksum: 10/7226a514ebff5aab000d4589367e28ff8e9768a522d84a8da81201a56dfa0d15e713b9508d369a4b6e5aaf814a45ca7b230fffdb6955fd34ba061c4002d61ef8 + component-emitter: "npm:^1.3.0" + cookiejar: "npm:^2.1.4" + debug: "npm:^4.3.4" + fast-safe-stringify: "npm:^2.1.1" + form-data: "npm:^4.0.0" + formidable: "npm:^2.1.2" + methods: "npm:^1.1.2" + mime: "npm:2.6.0" + qs: "npm:^6.11.0" + semver: "npm:^7.3.8" + checksum: 10/33d0072e051baf91c7d68131c70682a0650dd1bd0b8dfb6f88e5bdfcb02e18cc2b42a66e44b32fd405ac6bcf5fd57c6e267bf80e2a8ce57a18166a9d3a78f57d languageName: node linkType: hard -"supertest@npm:^3.0.0": - version: 3.4.2 - resolution: "supertest@npm:3.4.2" +"supertest@npm:^7.1.4": + version: 7.1.4 + resolution: "supertest@npm:7.1.4" dependencies: methods: "npm:^1.1.2" - superagent: "npm:^3.8.3" - checksum: 10/7fa22c06f3367aceb8f713a0fefda0ca244247cef143f05fb3fde8f097df654d1c3b98db5090cb97c5b87c4f9f6b2bfa77d358caf71d1e3521516770fc77a0b4 + superagent: "npm:^10.2.3" + checksum: 10/ecb5d41f2b62b257dbdcabac245c32b8e8fb264fe2636dd85c2c883569d23dc14adc0a471abb84187cbdb49bc36ad870ad355b4a0b85973f510fd57fc229e6cc languageName: node linkType: hard @@ -5682,15 +6102,15 @@ __metadata: linkType: hard "supports-color@npm:^5.3.0": - version: 5.3.0 - resolution: "supports-color@npm:5.3.0" + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" dependencies: has-flag: "npm:^3.0.0" - checksum: 10/033d59dab3c45b2b90b01a0e3cf31fbad6c953c012197e1a57d8a62805f08a7cc5d9d03d9c991bcd13da050853798f1ebd1952a7e6158d3a754c96ea4f7d55bd + checksum: 10/5f505c6fa3c6e05873b43af096ddeb22159831597649881aeb8572d6fe3b81e798cc10840d0c9735e0026b250368851b7f77b65e84f4e4daa820a4f69947f55b languageName: node linkType: hard -"supports-color@npm:^7": +"supports-color@npm:^7, supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" dependencies: @@ -5699,15 +6119,6 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^7.1.0": - version: 7.1.0 - resolution: "supports-color@npm:7.1.0" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10/f5b2df5336c825ac31ea155180d88b5b5aacaaa7037c5b15d73412b84f1187c205b289e41a303ae6919a261f6642ceea350281e047885b499d2c3a551056f70a - languageName: node - linkType: hard - "supports-preserve-symlinks-flag@npm:^1.0.0": version: 1.0.0 resolution: "supports-preserve-symlinks-flag@npm:1.0.0" @@ -5727,17 +6138,17 @@ __metadata: languageName: node linkType: hard -"tar@npm:^6.1.11, tar@npm:^6.1.2": - version: 6.1.13 - resolution: "tar@npm:6.1.13" +"tar@npm:^7.4.3": + version: 7.4.3 + resolution: "tar@npm:7.4.3" dependencies: - chownr: "npm:^2.0.0" - fs-minipass: "npm:^2.0.0" - minipass: "npm:^4.0.0" - minizlib: "npm:^2.1.1" - mkdirp: "npm:^1.0.3" - yallist: "npm:^4.0.0" - checksum: 10/add2c3c6d0d71192186ec118d265b92d94be5cd57a0b8fdf0d29ee46dc846574925a5fc57170eefffd78201eda4c45d7604070b5a4b0648e4d6e1d65918b5a82 + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.0.1" + mkdirp: "npm:^3.0.1" + yallist: "npm:^5.0.0" + checksum: 10/12a2a4fc6dee23e07cc47f1aeb3a14a1afd3f16397e1350036a8f4cdfee8dcac7ef5978337a4e7b2ac2c27a9a6d46388fc2088ea7c80cb6878c814b1425f8ecf languageName: node linkType: hard @@ -5766,6 +6177,16 @@ __metadata: languageName: node linkType: hard +"tinyglobby@npm:^0.2.12": + version: 0.2.14 + resolution: "tinyglobby@npm:0.2.14" + dependencies: + fdir: "npm:^6.4.4" + picomatch: "npm:^4.0.2" + checksum: 10/3d306d319718b7cc9d79fb3f29d8655237aa6a1f280860a217f93417039d0614891aee6fc47c5db315f4fcc6ac8d55eb8e23e2de73b2c51a431b42456d9e5764 + languageName: node + linkType: hard + "tmp@npm:^0.0.33": version: 0.0.33 resolution: "tmp@npm:0.0.33" @@ -5775,13 +6196,6 @@ __metadata: languageName: node linkType: hard -"to-fast-properties@npm:^2.0.0": - version: 2.0.0 - resolution: "to-fast-properties@npm:2.0.0" - checksum: 10/be2de62fe58ead94e3e592680052683b1ec986c72d589e7b21e5697f8744cdbf48c266fa72f6c15932894c10187b5f54573a3bcf7da0bfd964d5caf23d436168 - languageName: node - linkType: hard - "to-regex-range@npm:^5.0.1": version: 5.0.1 resolution: "to-regex-range@npm:5.0.1" @@ -5805,10 +6219,22 @@ __metadata: languageName: node linkType: hard +"tsconfig-paths@npm:^3.15.0": + version: 3.15.0 + resolution: "tsconfig-paths@npm:3.15.0" + dependencies: + "@types/json5": "npm:^0.0.29" + json5: "npm:^1.0.2" + minimist: "npm:^1.2.6" + strip-bom: "npm:^3.0.0" + checksum: 10/2041beaedc6c271fc3bedd12e0da0cc553e65d030d4ff26044b771fac5752d0460944c0b5e680f670c2868c95c664a256cec960ae528888db6ded83524e33a14 + languageName: node + linkType: hard + "tslib@npm:^1.9.0": - version: 1.11.1 - resolution: "tslib@npm:1.11.1" - checksum: 10/d635ca62724e01c7c10513f3fc8eabf481725ac96c46430ea667a144542b73be415ce7fb69252dd7b5185dcb16b99c42737b61b3f78b66761c4f5a19872b6acb + version: 1.14.1 + resolution: "tslib@npm:1.14.1" + checksum: 10/7dbf34e6f55c6492637adb81b555af5e3b4f9cc6b998fb440dac82d3b42bdc91560a35a5fb75e20e24a076c651438234da6743d139e4feabf0783f3cdfe1dddb languageName: node linkType: hard @@ -5828,24 +6254,24 @@ __metadata: languageName: node linkType: hard -"type-detect@npm:4.0.8, type-detect@npm:^4.0.5": +"type-detect@npm:4.0.8": version: 4.0.8 resolution: "type-detect@npm:4.0.8" checksum: 10/5179e3b8ebc51fce1b13efb75fdea4595484433f9683bbc2dca6d99789dba4e602ab7922d2656f2ce8383987467f7770131d4a7f06a26287db0615d2f4c4ce7d languageName: node linkType: hard -"type-detect@npm:^4.0.0, type-detect@npm:^4.0.8": +"type-detect@npm:^4.0.0, type-detect@npm:^4.0.8, type-detect@npm:^4.1.0": version: 4.1.0 resolution: "type-detect@npm:4.1.0" checksum: 10/e363bf0352427a79301f26a7795a27718624c49c576965076624eb5495d87515030b207217845f7018093adcbe169b2d119bb9b7f1a31a92bfbb1ab9639ca8dd languageName: node linkType: hard -"type-fest@npm:^0.11.0": - version: 0.11.0 - resolution: "type-fest@npm:0.11.0" - checksum: 10/2d60de8588b876719396abdce0fcf282a0b6290259300f6334f655e99229398ea165e6cabd118961201da8ce4b87d7f50fd5628fb466c346fdc00f68f3548fec +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: 10/f4254070d9c3d83a6e573bcb95173008d73474ceadbbf620dd32d273940ca18734dff39c2b2480282df9afe5d1675ebed5499a00d791758748ea81f61a38961f languageName: node linkType: hard @@ -5877,6 +6303,59 @@ __metadata: languageName: node linkType: hard +"typed-array-buffer@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-buffer@npm:1.0.3" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-typed-array: "npm:^1.1.14" + checksum: 10/3fb91f0735fb413b2bbaaca9fabe7b8fc14a3fa5a5a7546bab8a57e755be0e3788d893195ad9c2b842620592de0e68d4c077d4c2c41f04ec25b8b5bb82fa9a80 + languageName: node + linkType: hard + +"typed-array-byte-length@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-byte-length@npm:1.0.3" + dependencies: + call-bind: "npm:^1.0.8" + for-each: "npm:^0.3.3" + gopd: "npm:^1.2.0" + has-proto: "npm:^1.2.0" + is-typed-array: "npm:^1.1.14" + checksum: 10/269dad101dda73e3110117a9b84db86f0b5c07dad3a9418116fd38d580cab7fc628a4fc167e29b6d7c39da2f53374b78e7cb578b3c5ec7a556689d985d193519 + languageName: node + linkType: hard + +"typed-array-byte-offset@npm:^1.0.4": + version: 1.0.4 + resolution: "typed-array-byte-offset@npm:1.0.4" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + for-each: "npm:^0.3.3" + gopd: "npm:^1.2.0" + has-proto: "npm:^1.2.0" + is-typed-array: "npm:^1.1.15" + reflect.getprototypeof: "npm:^1.0.9" + checksum: 10/c2869aa584cdae24ecfd282f20a0f556b13a49a9d5bca1713370bb3c89dff0ccbc5ceb45cb5b784c98f4579e5e3e2a07e438c3a5b8294583e2bd4abbd5104fb5 + languageName: node + linkType: hard + +"typed-array-length@npm:^1.0.7": + version: 1.0.7 + resolution: "typed-array-length@npm:1.0.7" + dependencies: + call-bind: "npm:^1.0.7" + for-each: "npm:^0.3.3" + gopd: "npm:^1.0.1" + is-typed-array: "npm:^1.1.13" + possible-typed-array-names: "npm:^1.0.0" + reflect.getprototypeof: "npm:^1.0.6" + checksum: 10/d6b2f0e81161682d2726eb92b1dc2b0890890f9930f33f9bcf6fc7272895ce66bc368066d273e6677776de167608adc53fcf81f1be39a146d64b630edbf2081c + languageName: node + linkType: hard + "typedarray-to-buffer@npm:^3.1.5": version: 3.1.5 resolution: "typedarray-to-buffer@npm:3.1.5" @@ -5886,28 +6365,47 @@ __metadata: languageName: node linkType: hard +"unbox-primitive@npm:^1.1.0": + version: 1.1.0 + resolution: "unbox-primitive@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + has-bigints: "npm:^1.0.2" + has-symbols: "npm:^1.1.0" + which-boxed-primitive: "npm:^1.1.1" + checksum: 10/fadb347020f66b2c8aeacf8b9a79826fa34cc5e5457af4eb0bbc4e79bd87fed0fa795949825df534320f7c13f199259516ad30abc55a6e7b91d8d996ca069e50 + languageName: node + linkType: hard + "underscore@npm:^1.13.1": - version: 1.13.1 - resolution: "underscore@npm:1.13.1" - checksum: 10/71bec536cb8068edc94f55560e0512e7b5d892631eabc9078925fb0685dc1b183c1783d0936a9ba327a82c83380b1a1d53f385a91f739c3ee63ffedb8e5e40e2 + version: 1.13.7 + resolution: "underscore@npm:1.13.7" + checksum: 10/1ce3368dbe73d1e99678fa5d341a9682bd27316032ad2de7883901918f0f5d50e80320ccc543f53c1862ab057a818abc560462b5f83578afe2dd8dd7f779766c languageName: node linkType: hard -"unique-filename@npm:^2.0.0": - version: 2.0.1 - resolution: "unique-filename@npm:2.0.1" +"undici-types@npm:~7.8.0": + version: 7.8.0 + resolution: "undici-types@npm:7.8.0" + checksum: 10/fcff3fbab234f067fbd69e374ee2c198ba74c364ceaf6d93db7ca267e784457b5518cd01d0d2329b075f412574205ea3172a9a675facb49b4c9efb7141cd80b7 + languageName: node + linkType: hard + +"unique-filename@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-filename@npm:4.0.0" dependencies: - unique-slug: "npm:^3.0.0" - checksum: 10/807acf3381aff319086b64dc7125a9a37c09c44af7620bd4f7f3247fcd5565660ac12d8b80534dcbfd067e6fe88a67e621386dd796a8af828d1337a8420a255f + unique-slug: "npm:^5.0.0" + checksum: 10/6a62094fcac286b9ec39edbd1f8f64ff92383baa430af303dfed1ffda5e47a08a6b316408554abfddd9730c78b6106bef4ca4d02c1231a735ddd56ced77573df languageName: node linkType: hard -"unique-slug@npm:^3.0.0": - version: 3.0.0 - resolution: "unique-slug@npm:3.0.0" +"unique-slug@npm:^5.0.0": + version: 5.0.0 + resolution: "unique-slug@npm:5.0.0" dependencies: imurmurhash: "npm:^0.1.4" - checksum: 10/26fc5bc209a875956dd5e84ca39b89bc3be777b112504667c35c861f9547df95afc80439358d836b878b6d91f6ee21fe5ba1a966e9ec2e9f071ddf3fd67d45ee + checksum: 10/beafdf3d6f44990e0a5ce560f8f881b4ee811be70b6ba0db25298c31c8cf525ed963572b48cd03be1c1349084f9e339be4241666d7cf1ebdad20598d3c652b27 languageName: node linkType: hard @@ -5918,19 +6416,26 @@ __metadata: languageName: node linkType: hard -"uri-js@npm:^4.2.2": - version: 4.2.2 - resolution: "uri-js@npm:4.2.2" +"update-browserslist-db@npm:^1.1.3": + version: 1.1.3 + resolution: "update-browserslist-db@npm:1.1.3" dependencies: - punycode: "npm:^2.1.0" - checksum: 10/e9499d30bfa7559acc255ab196bf7be0db9e5e5550cc0dfd8aeaeabbe423c323b18e261b31b996a409465b29f6ad814f8683f0c4f476ee347a57103dba0fb7f7 + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10/87af2776054ffb9194cf95e0201547d041f72ee44ce54b144da110e65ea7ca01379367407ba21de5c9edd52c74d95395366790de67f3eb4cc4afa0fe4424e76f languageName: node linkType: hard -"util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1": - version: 1.0.2 - resolution: "util-deprecate@npm:1.0.2" - checksum: 10/474acf1146cb2701fe3b074892217553dfcf9a031280919ba1b8d651a068c9b15d863b7303cb15bd00a862b498e6cf4ad7b4a08fb134edd5a6f7641681cb54a2 +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: "npm:^2.1.0" + checksum: 10/b271ca7e3d46b7160222e3afa3e531505161c9a4e097febae9664e4b59912f4cbe94861361a4175edac3a03fee99d91e44b6a58c17a634bc5a664b19fc76fbcb languageName: node linkType: hard @@ -5943,29 +6448,19 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^3.3.3": - version: 3.4.0 - resolution: "uuid@npm:3.4.0" +"uuid@npm:^8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" bin: - uuid: ./bin/uuid - checksum: 10/4f2b86432b04cc7c73a0dd1bcf11f1fc18349d65d2e4e32dd0fc658909329a1e0cc9244aa93f34c0cccfdd5ae1af60a149251a5f420ec3ac4223a3dab198fb2e + uuid: dist/bin/uuid + checksum: 10/9a5f7aa1d6f56dd1e8d5f2478f855f25c645e64e26e347a98e98d95781d5ed20062d6cca2eecb58ba7c84bc3910be95c0451ef4161906abaab44f9cb68ffbdd1 languageName: node linkType: hard "v8-compile-cache@npm:^2.0.3": - version: 2.1.0 - resolution: "v8-compile-cache@npm:2.1.0" - checksum: 10/fb0d06fa72dda33accc7f837d1af823dc5e364a3ab7bb34ec284aec61b26021e255e78fff1ed2eadd0589b80465e5a2f88989d951d9b04143a3975f6f3806321 - languageName: node - linkType: hard - -"validate-npm-package-license@npm:^3.0.1": - version: 3.0.4 - resolution: "validate-npm-package-license@npm:3.0.4" - dependencies: - spdx-correct: "npm:^3.0.0" - spdx-expression-parse: "npm:^3.0.0" - checksum: 10/86242519b2538bb8aeb12330edebb61b4eb37fd35ef65220ab0b03a26c0592c1c8a7300d32da3cde5abd08d18d95e8dabfad684b5116336f6de9e6f207eec224 + version: 2.4.0 + resolution: "v8-compile-cache@npm:2.4.0" + checksum: 10/49e726d7b2825ef7bc92187ecd57c59525957badbddb18fa529b0458b9280c59a1607ad3da4abe7808e9f9a00ec99b0fc07e485ffb7358cd5c11b2ef68d2145f languageName: node linkType: hard @@ -6000,10 +6495,71 @@ __metadata: languageName: node linkType: hard +"which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": + version: 1.1.1 + resolution: "which-boxed-primitive@npm:1.1.1" + dependencies: + is-bigint: "npm:^1.1.0" + is-boolean-object: "npm:^1.2.1" + is-number-object: "npm:^1.1.1" + is-string: "npm:^1.1.1" + is-symbol: "npm:^1.1.1" + checksum: 10/a877c0667bc089518c83ad4d845cf8296b03efe3565c1de1940c646e00a2a1ae9ed8a185bcfa27cbf352de7906f0616d83b9d2f19ca500ee02a551fb5cf40740 + languageName: node + linkType: hard + +"which-builtin-type@npm:^1.2.1": + version: 1.2.1 + resolution: "which-builtin-type@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + function.prototype.name: "npm:^1.1.6" + has-tostringtag: "npm:^1.0.2" + is-async-function: "npm:^2.0.0" + is-date-object: "npm:^1.1.0" + is-finalizationregistry: "npm:^1.1.0" + is-generator-function: "npm:^1.0.10" + is-regex: "npm:^1.2.1" + is-weakref: "npm:^1.0.2" + isarray: "npm:^2.0.5" + which-boxed-primitive: "npm:^1.1.0" + which-collection: "npm:^1.0.2" + which-typed-array: "npm:^1.1.16" + checksum: 10/22c81c5cb7a896c5171742cd30c90d992ff13fb1ea7693e6cf80af077791613fb3f89aa9b4b7f890bd47b6ce09c6322c409932359580a2a2a54057f7b52d1cbe + languageName: node + linkType: hard + +"which-collection@npm:^1.0.2": + version: 1.0.2 + resolution: "which-collection@npm:1.0.2" + dependencies: + is-map: "npm:^2.0.3" + is-set: "npm:^2.0.3" + is-weakmap: "npm:^2.0.2" + is-weakset: "npm:^2.0.3" + checksum: 10/674bf659b9bcfe4055f08634b48a8588e879161b9fefed57e9ec4ff5601e4d50a05ccd76cf10f698ef5873784e5df3223336d56c7ce88e13bcf52ebe582fc8d7 + languageName: node + linkType: hard + "which-module@npm:^2.0.0": - version: 2.0.0 - resolution: "which-module@npm:2.0.0" - checksum: 10/e3e46c9c84475bff773b9e5bbf48ffa1749bc45669c56ffc874ae4a520627a259e10f16ca67c1a1338edce7a002af86c40a036dcb13ad45c18246939997fa006 + version: 2.0.1 + resolution: "which-module@npm:2.0.1" + checksum: 10/1967b7ce17a2485544a4fdd9063599f0f773959cca24176dbe8f405e55472d748b7c549cd7920ff6abb8f1ab7db0b0f1b36de1a21c57a8ff741f4f1e792c52be + languageName: node + linkType: hard + +"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19": + version: 1.1.19 + resolution: "which-typed-array@npm:1.1.19" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + for-each: "npm:^0.3.5" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + checksum: 10/12be30fb88567f9863186bee1777f11bea09dd59ed8b3ce4afa7dd5cade75e2f4cc56191a2da165113cc7cf79987ba021dac1e22b5b62aa7e5c56949f2469a68 languageName: node linkType: hard @@ -6018,7 +6574,7 @@ __metadata: languageName: node linkType: hard -"which@npm:^2.0.1, which@npm:^2.0.2": +"which@npm:^2.0.1": version: 2.0.2 resolution: "which@npm:2.0.2" dependencies: @@ -6029,6 +6585,17 @@ __metadata: languageName: node linkType: hard +"which@npm:^5.0.0": + version: 5.0.0 + resolution: "which@npm:5.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10/6ec99e89ba32c7e748b8a3144e64bfc74aa63e2b2eacbb61a0060ad0b961eb1a632b08fb1de067ed59b002cec3e21de18299216ebf2325ef0f78e0f121e14e90 + languageName: node + linkType: hard + "wide-align@npm:1.1.3": version: 1.1.3 resolution: "wide-align@npm:1.1.3" @@ -6038,33 +6605,35 @@ __metadata: languageName: node linkType: hard -"wide-align@npm:^1.1.5": - version: 1.1.5 - resolution: "wide-align@npm:1.1.5" - dependencies: - string-width: "npm:^1.0.2 || 2 || 3 || 4" - checksum: 10/d5f8027b9a8255a493a94e4ec1b74a27bff6679d5ffe29316a3215e4712945c84ef73ca4045c7e20ae7d0c72f5f57f296e04a4928e773d4276a2f1222e4c2e99 - languageName: node - linkType: hard - "winston@npm:^2.4.5": - version: 2.4.6 - resolution: "winston@npm:2.4.6" + version: 2.4.7 + resolution: "winston@npm:2.4.7" dependencies: - async: "npm:^3.2.3" + async: "npm:^2.6.4" colors: "npm:1.0.x" cycle: "npm:1.0.x" eyes: "npm:0.1.x" isstream: "npm:0.1.x" stack-trace: "npm:0.0.x" - checksum: 10/9f653d3f87ab2597bf89a87b8b4c06bbbdba685d3198a234aaf05cba78e910796b8ef5aeb43c2a9ccc3b886f912e772ee0dac2318436e0989ef997b1484ac476 + checksum: 10/44665f99bb1b1f290fc48879cd3bf635d0821ad70e90cdec0911a79b4ffd4f62ae4afa4764b27766e6a4ff60e911611047fd3f10b21d395eb0e3da33086d6c73 languageName: node linkType: hard "word-wrap@npm:~1.2.3": - version: 1.2.3 - resolution: "word-wrap@npm:1.2.3" - checksum: 10/08a677e1578b9cc367a03d52bc51b6869fec06303f68d29439e4ed647257411f857469990c31066c1874678937dac737c9f8f20d3fd59918fb86b7d926a76b15 + version: 1.2.5 + resolution: "word-wrap@npm:1.2.5" + checksum: 10/1ec6f6089f205f83037be10d0c4b34c9183b0b63fca0834a5b3cee55dd321429d73d40bb44c8fc8471b5203d6e8f8275717f49a8ff4b2b0ab41d7e1b563e0854 + languageName: node + linkType: hard + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10/cebdaeca3a6880da410f75209e68cd05428580de5ad24535f22696d7d9cab134d1f8498599f344c3cf0fb37c1715807a183778d8c648d6cc0cb5ff2bb4236540 languageName: node linkType: hard @@ -6090,6 +6659,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" + dependencies: + ansi-styles: "npm:^6.1.0" + string-width: "npm:^5.0.1" + strip-ansi: "npm:^7.0.1" + checksum: 10/7b1e4b35e9bb2312d2ee9ee7dc95b8cb5f8b4b5a89f7dde5543fe66c1e3715663094defa50d75454ac900bd210f702d575f15f3f17fa9ec0291806d2578d1ddf + languageName: node + linkType: hard + "wrappy@npm:1": version: 1.0.2 resolution: "wrappy@npm:1.0.2" @@ -6119,8 +6699,8 @@ __metadata: linkType: hard "ws@npm:^7.4.6": - version: 7.5.9 - resolution: "ws@npm:7.5.9" + version: 7.5.10 + resolution: "ws@npm:7.5.10" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ^5.0.2 @@ -6129,7 +6709,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10/171e35012934bd8788150a7f46f963e50bac43a4dc524ee714c20f258693ac4d3ba2abadb00838fdac42a47af9e958c7ae7e6f4bc56db047ba897b8a2268cf7c + checksum: 10/9c796b84ba80ffc2c2adcdfc9c8e9a219ba99caa435c9a8d45f9ac593bba325563b3f83edc5eb067cc6d21b9a6bf2c930adf76dd40af5f58a5ca6859e81858f0 languageName: node linkType: hard @@ -6140,6 +6720,13 @@ __metadata: languageName: node linkType: hard +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 10/9af0a4329c3c6b779ac4736c69fae4190ac03029fa27c1aef4e6bcc92119b73dea6fe5db5fe881fb0ce2a0e9539a42cdf60c7c21eda04d1a0b8c082e38509efb + languageName: node + linkType: hard + "yallist@npm:^4.0.0": version: 4.0.0 resolution: "yallist@npm:4.0.0" @@ -6147,6 +6734,13 @@ __metadata: languageName: node linkType: hard +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10/1884d272d485845ad04759a255c71775db0fac56308764b4c77ea56a20d56679fad340213054c8c9c9c26fcfd4c4b2a90df993b7e0aaf3cdb73c618d1d1a802a + languageName: node + linkType: hard + "yargs-parser@npm:^18.1.2": version: 18.1.3 resolution: "yargs-parser@npm:18.1.3" @@ -6168,25 +6762,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:13.3.0": - version: 13.3.0 - resolution: "yargs@npm:13.3.0" - dependencies: - cliui: "npm:^5.0.0" - find-up: "npm:^3.0.0" - get-caller-file: "npm:^2.0.1" - require-directory: "npm:^2.1.1" - require-main-filename: "npm:^2.0.0" - set-blocking: "npm:^2.0.0" - string-width: "npm:^3.0.0" - which-module: "npm:^2.0.0" - y18n: "npm:^4.0.0" - yargs-parser: "npm:^13.1.1" - checksum: 10/4dab805e11e36626b3cb791b16a204dd0f6ce5e8eb1117edec18cadac6b65a59e4c68705394e434b2716abe50c6c384d1dac0f262c3378a69ecacfd70d702ffc - languageName: node - linkType: hard - -"yargs@npm:^13.3.0": +"yargs@npm:13.3.2, yargs@npm:^13.3.0": version: 13.3.2 resolution: "yargs@npm:13.3.2" dependencies: @@ -6205,8 +6781,8 @@ __metadata: linkType: hard "yargs@npm:^15.0.2": - version: 15.1.0 - resolution: "yargs@npm:15.1.0" + version: 15.4.1 + resolution: "yargs@npm:15.4.1" dependencies: cliui: "npm:^6.0.0" decamelize: "npm:^1.2.0" @@ -6218,8 +6794,8 @@ __metadata: string-width: "npm:^4.2.0" which-module: "npm:^2.0.0" y18n: "npm:^4.0.0" - yargs-parser: "npm:^16.1.0" - checksum: 10/cb1341fd44cae9642a5c1278bf6c686f13b2e0a4603fa57063b7d1ca7caab14ccff56066879d4f33b139276f3bd2f9dca47566c3df00835667e65baf430406b8 + yargs-parser: "npm:^18.1.2" + checksum: 10/bbcc82222996c0982905b668644ca363eebe6ffd6a572fbb52f0c0e8146661d8ce5af2a7df546968779bb03d1e4186f3ad3d55dfaadd1c4f0d5187c0e3a5ba16 languageName: node linkType: hard From c9c7a18ae3128a2bb9ce54e7cfbd23168c40f07b Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Fri, 19 Sep 2025 17:42:42 +0100 Subject: [PATCH 060/113] yarn conflic fix --- yarn.lock | 524 +++++++++++++++++++++++++++++------------------------- 1 file changed, 285 insertions(+), 239 deletions(-) diff --git a/yarn.lock b/yarn.lock index f1e33a60..20e5b9c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,17 +5,7 @@ __metadata: version: 8 cacheKey: 10 -"@ampproject/remapping@npm:^2.2.0": - version: 2.3.0 - resolution: "@ampproject/remapping@npm:2.3.0" - dependencies: - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10/f3451525379c68a73eb0a1e65247fbf28c0cccd126d93af21c75fceff77773d43c0d4a2d51978fb131aff25b5f2cb41a9fe48cc296e61ae65e679c4f6918b0ab - languageName: node - linkType: hard - -"@azure/abort-controller@npm:^2.0.0": +"@azure/abort-controller@npm:^2.0.0, @azure/abort-controller@npm:^2.1.2": version: 2.1.2 resolution: "@azure/abort-controller@npm:2.1.2" dependencies: @@ -24,7 +14,7 @@ __metadata: languageName: node linkType: hard -"@azure/core-auth@npm:1.7.2, @azure/core-auth@npm:^1.4.0": +"@azure/core-auth@npm:1.7.2": version: 1.7.2 resolution: "@azure/core-auth@npm:1.7.2" dependencies: @@ -35,6 +25,17 @@ __metadata: languageName: node linkType: hard +"@azure/core-auth@npm:^1.4.0": + version: 1.10.1 + resolution: "@azure/core-auth@npm:1.10.1" + dependencies: + "@azure/abort-controller": "npm:^2.1.2" + "@azure/core-util": "npm:^1.13.0" + tslib: "npm:^2.6.2" + checksum: 10/230c1766d4cb3ac7beac45db65bd5e493e1530f6f1d51dc0fd3537f8144e5c9acfed94700fd28c7aee67bab7502e23a1588adc6aa76f918f08fe40b3b007e2a3 + languageName: node + linkType: hard + "@azure/core-rest-pipeline@npm:1.16.3": version: 1.16.3 resolution: "@azure/core-rest-pipeline@npm:1.16.3" @@ -52,46 +53,47 @@ __metadata: linkType: hard "@azure/core-tracing@npm:^1.0.1, @azure/core-tracing@npm:^1.2.0": - version: 1.2.0 - resolution: "@azure/core-tracing@npm:1.2.0" + version: 1.3.1 + resolution: "@azure/core-tracing@npm:1.3.1" dependencies: tslib: "npm:^2.6.2" - checksum: 10/5d63ffc8f6361545b55b108b2898cda2b424db1a533d11a56890d53ba3b385e9be8f50cfd48a21b897351e1f4bbc56ede14d57187ea927d4489637fc93ebe615 + checksum: 10/7ef179e0ceb58c76d99c22bb5c5faade6fceaa62a265dcdaf09456e979be716e0249bb952d8000b9502b2194aeccb01454d60b497d4a18755e933cf7b1df919d languageName: node linkType: hard -"@azure/core-util@npm:^1.1.0, @azure/core-util@npm:^1.9.0": - version: 1.12.0 - resolution: "@azure/core-util@npm:1.12.0" +"@azure/core-util@npm:^1.1.0, @azure/core-util@npm:^1.13.0, @azure/core-util@npm:^1.9.0": + version: 1.13.1 + resolution: "@azure/core-util@npm:1.13.1" dependencies: - "@azure/abort-controller": "npm:^2.0.0" - "@typespec/ts-http-runtime": "npm:^0.2.2" + "@azure/abort-controller": "npm:^2.1.2" + "@typespec/ts-http-runtime": "npm:^0.3.0" tslib: "npm:^2.6.2" - checksum: 10/6a5544451fae579d91f0066807477ee1ffba93b4bf7629e73f53c561560f792770590ebf86d214ff5242ea8a2808c44e48f4188561352d34372734a6affe8e87 + checksum: 10/81ba529bed2fb615836be9425608e012f9bd243881f861c7ac086ea618c5f91129d3088216eee588323ffc3dfc0013706069830c03810f8a0f4591553ef5843b languageName: node linkType: hard "@azure/logger@npm:^1.0.0": - version: 1.2.0 - resolution: "@azure/logger@npm:1.2.0" + version: 1.3.0 + resolution: "@azure/logger@npm:1.3.0" dependencies: - "@typespec/ts-http-runtime": "npm:^0.2.2" + "@typespec/ts-http-runtime": "npm:^0.3.0" tslib: "npm:^2.6.2" - checksum: 10/21347e019c7c66be0707968f824210845ea9aa21c96ae8b1075f7a53b6a23c230e631db53ba63696b0ede577f1ca5665e4fc4a346b61896b376d15f0a01717ee + checksum: 10/7df11bf3b4952207d7355fde3cee223df2e4a64eaafff05a1fcbcb5c870350f1ef726866b771a7520b0e2bb33bfa9c96415b823c4b74e04ad4b755e961634528 languageName: node linkType: hard "@azure/opentelemetry-instrumentation-azure-sdk@npm:^1.0.0-beta.5": - version: 1.0.0-beta.8 - resolution: "@azure/opentelemetry-instrumentation-azure-sdk@npm:1.0.0-beta.8" + version: 1.0.0-beta.9 + resolution: "@azure/opentelemetry-instrumentation-azure-sdk@npm:1.0.0-beta.9" dependencies: "@azure/core-tracing": "npm:^1.2.0" "@azure/logger": "npm:^1.0.0" "@opentelemetry/api": "npm:^1.9.0" - "@opentelemetry/core": "npm:^1.30.1" - "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.200.0" + "@opentelemetry/sdk-trace-web": "npm:^2.0.0" tslib: "npm:^2.7.0" - checksum: 10/8fe7ee9f2e15f4daaa66c58b728560c01122295e6a323d8898373846973e9ad6f8cfc8b71faf0d34c43fefe5e280ade27affd30c157ae6a11c60b8cf33d47d0a + checksum: 10/eb28a6242cf24722287dc3faaa5a91e3d954458d27ff9ec2326a83402e8b4dfcf53c8fc3341eaaa7b9c643279aa552fe99a64e4cc373cfe4b5910d4ebf74214b languageName: node linkType: hard @@ -107,45 +109,45 @@ __metadata: linkType: hard "@babel/compat-data@npm:^7.27.2": - version: 7.28.0 - resolution: "@babel/compat-data@npm:7.28.0" - checksum: 10/1a56a5e48c7259f72cc4329adeca38e72fd650ea09de267ea4aa070e3da91e5c265313b6656823fff77d64a8bab9554f276c66dade9355fdc0d8604deea015aa + version: 7.28.4 + resolution: "@babel/compat-data@npm:7.28.4" + checksum: 10/95b7864e6b210c84c069743966da448c0cb50015a4de5e18dd755776a0b5e53c4653e74f26700aed8de922eaa3b8844fc5fc5b29bc64830249d2abe914aec832 languageName: node linkType: hard "@babel/core@npm:^7.7.5": - version: 7.28.0 - resolution: "@babel/core@npm:7.28.0" + version: 7.28.4 + resolution: "@babel/core@npm:7.28.4" dependencies: - "@ampproject/remapping": "npm:^2.2.0" "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.0" + "@babel/generator": "npm:^7.28.3" "@babel/helper-compilation-targets": "npm:^7.27.2" - "@babel/helper-module-transforms": "npm:^7.27.3" - "@babel/helpers": "npm:^7.27.6" - "@babel/parser": "npm:^7.28.0" + "@babel/helper-module-transforms": "npm:^7.28.3" + "@babel/helpers": "npm:^7.28.4" + "@babel/parser": "npm:^7.28.4" "@babel/template": "npm:^7.27.2" - "@babel/traverse": "npm:^7.28.0" - "@babel/types": "npm:^7.28.0" + "@babel/traverse": "npm:^7.28.4" + "@babel/types": "npm:^7.28.4" + "@jridgewell/remapping": "npm:^2.3.5" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10/1c86eec8d76053f7b1c5f65296d51d7b8ac00f80d169ff76d3cd2e7d85ab222eb100d40cc3314f41b96c8cc06e9abab21c63d246161f0f3f70ef14c958419c33 + checksum: 10/0593295241fac9be567145ef16f3858d34fc91390a9438c6d47476be9823af4cc0488c851c59702dd46b968e9fd46d17ddf0105ea30195ca85f5a66b4044c519 languageName: node linkType: hard -"@babel/generator@npm:^7.28.0": - version: 7.28.0 - resolution: "@babel/generator@npm:7.28.0" +"@babel/generator@npm:^7.28.3": + version: 7.28.3 + resolution: "@babel/generator@npm:7.28.3" dependencies: - "@babel/parser": "npm:^7.28.0" - "@babel/types": "npm:^7.28.0" + "@babel/parser": "npm:^7.28.3" + "@babel/types": "npm:^7.28.2" "@jridgewell/gen-mapping": "npm:^0.3.12" "@jridgewell/trace-mapping": "npm:^0.3.28" jsesc: "npm:^3.0.2" - checksum: 10/064c5ba4c07ecd7600377bd0022d5f6bdb3b35e9ff78d9378f6bd1e656467ca902c091647222ab2f0d2967f6d6c0ca33157d37dd9b1c51926c9b0e1527ab9b92 + checksum: 10/d00d1e6b51059e47594aab7920b88ec6fcef6489954a9172235ab57ad2e91b39c95376963a6e2e4cc7e8b88fa4f931018f71f9ab32bbc9c0bc0de35a0231f26c languageName: node linkType: hard @@ -179,16 +181,16 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.27.3": - version: 7.27.3 - resolution: "@babel/helper-module-transforms@npm:7.27.3" +"@babel/helper-module-transforms@npm:^7.28.3": + version: 7.28.3 + resolution: "@babel/helper-module-transforms@npm:7.28.3" dependencies: "@babel/helper-module-imports": "npm:^7.27.1" "@babel/helper-validator-identifier": "npm:^7.27.1" - "@babel/traverse": "npm:^7.27.3" + "@babel/traverse": "npm:^7.28.3" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/47abc90ceb181b4bdea9bf1717adf536d1b5e5acb6f6d8a7a4524080318b5ca8a99e6d58677268c596bad71077d1d98834d2c3815f2443e6d3f287962300f15d + checksum: 10/598fdd8aa5b91f08542d0ba62a737847d0e752c8b95ae2566bc9d11d371856d6867d93e50db870fb836a6c44cfe481c189d8a2b35ca025a224f070624be9fa87 languageName: node linkType: hard @@ -213,24 +215,24 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.27.6": - version: 7.28.2 - resolution: "@babel/helpers@npm:7.28.2" +"@babel/helpers@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/helpers@npm:7.28.4" dependencies: "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.28.2" - checksum: 10/09fd7965e83d4777a4331a082677a1a2261cec451bf3307cb0fb62b2d32c83d55fb1cac494a5dab5c6ad9da459883b8d4e49142812b10ef3e36b54022b2de3a4 + "@babel/types": "npm:^7.28.4" + checksum: 10/5a70a82e196cf8808f8a449cc4780c34d02edda2bb136d39ce9d26e63b615f18e89a95472230c3ce7695db0d33e7026efeee56f6454ed43480f223007ed205eb languageName: node linkType: hard -"@babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.0": - version: 7.28.0 - resolution: "@babel/parser@npm:7.28.0" +"@babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/parser@npm:7.28.4" dependencies: - "@babel/types": "npm:^7.28.0" + "@babel/types": "npm:^7.28.4" bin: parser: ./bin/babel-parser.js - checksum: 10/2c14a0d2600bae9ab81924df0a85bbd34e427caa099c260743f7c6c12b2042e743e776043a0d1a2573229ae648f7e66a80cfb26fc27e2a9eb59b55932d44c817 + checksum: 10/f54c46213ef180b149f6a17ea765bf40acc1aebe2009f594e2a283aec69a190c6dda1fdf24c61a258dbeb903abb8ffb7a28f1a378f8ab5d333846ce7b7e23bf1 languageName: node linkType: hard @@ -245,28 +247,28 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.27.3, @babel/traverse@npm:^7.28.0": - version: 7.28.0 - resolution: "@babel/traverse@npm:7.28.0" +"@babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/traverse@npm:7.28.4" dependencies: "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.0" + "@babel/generator": "npm:^7.28.3" "@babel/helper-globals": "npm:^7.28.0" - "@babel/parser": "npm:^7.28.0" + "@babel/parser": "npm:^7.28.4" "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.28.0" + "@babel/types": "npm:^7.28.4" debug: "npm:^4.3.1" - checksum: 10/c1c24b12b6cb46241ec5d11ddbd2989d6955c282715cbd8ee91a09fe156b3bdb0b88353ac33329c2992113e3dfb5198f616c834f8805bb3fa85da1f864bec5f3 + checksum: 10/c3099364b7b1c36bcd111099195d4abeef16499e5defb1e56766b754e8b768c252e856ed9041665158aa1b31215fc6682632756803c8fa53405381ec08c4752b languageName: node linkType: hard -"@babel/types@npm:^7.27.1, @babel/types@npm:^7.28.0, @babel/types@npm:^7.28.2": - version: 7.28.2 - resolution: "@babel/types@npm:7.28.2" +"@babel/types@npm:^7.27.1, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/types@npm:7.28.4" dependencies: "@babel/helper-string-parser": "npm:^7.27.1" "@babel/helper-validator-identifier": "npm:^7.27.1" - checksum: 10/a8de404a2e3109651f346d892dc020ce2c82046068f4ce24de7f487738dfbfa7bd716b35f1dcd6d6c32dde96208dc74a56b7f56a2c0bcb5af0ddc56cbee13533 + checksum: 10/db50bf257aafa5d845ad16dae0587f57d596e4be4cbb233ea539976a4c461f9fbcc0bf3d37adae3f8ce5dcb4001462aa608f3558161258b585f6ce6ce21a2e45 languageName: node linkType: hard @@ -363,12 +365,22 @@ __metadata: linkType: hard "@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.5": - version: 0.3.12 - resolution: "@jridgewell/gen-mapping@npm:0.3.12" + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" dependencies: "@jridgewell/sourcemap-codec": "npm:^1.5.0" "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10/151667531566417a940d4dd0a319724979f7a90b9deb9f1617344e1183887d78c835bc1a9209c1ee10fc8a669cdd7ac8120a43a2b6bc8d0d5dd18a173059ff4b + checksum: 10/902f8261dcf450b4af7b93f9656918e02eec80a2169e155000cb2059f90113dd98f3ccf6efc6072cee1dd84cac48cade51da236972d942babc40e4c23da4d62a + languageName: node + linkType: hard + +"@jridgewell/remapping@npm:^2.3.5": + version: 2.3.5 + resolution: "@jridgewell/remapping@npm:2.3.5" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10/c2bb01856e65b506d439455f28aceacf130d6c023d1d4e3b48705e88def3571753e1a887daa04b078b562316c92d26ce36408a60534bceca3f830aec88a339ad languageName: node linkType: hard @@ -380,19 +392,19 @@ __metadata: linkType: hard "@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": - version: 1.5.4 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.4" - checksum: 10/f677787f52224c6c971a7a41b7a074243240a6917fa75eceb9f7a442866f374fb0522b505e0496ee10a650c5936727e76d11bf36a6d0ae9e6c3b726c9e284cc7 + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10/5d9d207b462c11e322d71911e55e21a4e2772f71ffe8d6f1221b8eb5ae6774458c1d242f897fb0814e8714ca9a6b498abfa74dfe4f434493342902b1a48b33a5 languageName: node linkType: hard "@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.28": - version: 0.3.29 - resolution: "@jridgewell/trace-mapping@npm:0.3.29" + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" dependencies: "@jridgewell/resolve-uri": "npm:^3.1.0" "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10/64e1ce0dc3a9e56b0118eaf1b2f50746fd59a36de37516cc6855b5370d5f367aa8229e1237536d738262e252c70ee229619cb04e3f3b822146ee3eb1b7ab297f + checksum: 10/da0283270e691bdb5543806077548532791608e52386cfbbf3b9e8fb00457859d1bd01d512851161c886eb3a2f3ce6fd9bcf25db8edf3bddedd275bd4a88d606 languageName: node linkType: hard @@ -432,12 +444,12 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/api-logs@npm:0.57.2": - version: 0.57.2 - resolution: "@opentelemetry/api-logs@npm:0.57.2" +"@opentelemetry/api-logs@npm:0.200.0": + version: 0.200.0 + resolution: "@opentelemetry/api-logs@npm:0.200.0" dependencies: "@opentelemetry/api": "npm:^1.3.0" - checksum: 10/8e3bac962e8f1fc93bfee6b433121bd2e07e8a8d1b86ef0d9d4a2c54d1759b64c74cf5da400f82f5ab5a4fe0da481726d8635fd1b15d123cf43090fa0adb8ea8 + checksum: 10/91c09934268d43070f9cc39a6e3c337aefddae9ed9533105673f577604bfa07b8d19a0015b4988c61279e7d5715c8df2644f0389308b2941374db7bb8504d35b languageName: node linkType: hard @@ -448,7 +460,7 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/core@npm:1.30.1, @opentelemetry/core@npm:^1.19.0, @opentelemetry/core@npm:^1.30.1": +"@opentelemetry/core@npm:1.30.1, @opentelemetry/core@npm:^1.19.0": version: 1.30.1 resolution: "@opentelemetry/core@npm:1.30.1" dependencies: @@ -459,19 +471,29 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/instrumentation@npm:^0.57.1": - version: 0.57.2 - resolution: "@opentelemetry/instrumentation@npm:0.57.2" +"@opentelemetry/core@npm:2.1.0, @opentelemetry/core@npm:^2.0.0": + version: 2.1.0 + resolution: "@opentelemetry/core@npm:2.1.0" + dependencies: + "@opentelemetry/semantic-conventions": "npm:^1.29.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/735bd1fe8a099c3aa3d7640875a5b30ab304f7e3b13efd622daabe47ff5470e9c453ad9fc119a5a0c0458ec2c7753f9a066afa32a269745b06b429ba7db90516 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation@npm:^0.200.0": + version: 0.200.0 + resolution: "@opentelemetry/instrumentation@npm:0.200.0" dependencies: - "@opentelemetry/api-logs": "npm:0.57.2" + "@opentelemetry/api-logs": "npm:0.200.0" "@types/shimmer": "npm:^1.2.0" import-in-the-middle: "npm:^1.8.1" require-in-the-middle: "npm:^7.1.1" - semver: "npm:^7.5.2" shimmer: "npm:^1.2.1" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/b66b840e87976a5edf551a7011a395df8df5985571ac0506412943d07b4309fcc78fe71d3f55217a00f44384fbf61f59f1e54d544ab12f5490f6a7a56b71e02a + checksum: 10/8e168487b630596accb02e26d27384593fe006daeb34f2943bcf61450a9d663225052c4940a5335be52647ce3436965719560e0ab8aaf5380fa1f80c9aaca148 languageName: node linkType: hard @@ -487,6 +509,31 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/resources@npm:2.1.0": + version: 2.1.0 + resolution: "@opentelemetry/resources@npm:2.1.0" + dependencies: + "@opentelemetry/core": "npm:2.1.0" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.10.0" + checksum: 10/8e2255443184fb889c54ed07fbcadb3b1595ce45219a39b5ac93e09648bf0b293098f84e2e47aec795deaf2d9d3db20302863f41e8e24195b2a19680eb9234fc + languageName: node + linkType: hard + +"@opentelemetry/sdk-trace-base@npm:2.1.0": + version: 2.1.0 + resolution: "@opentelemetry/sdk-trace-base@npm:2.1.0" + dependencies: + "@opentelemetry/core": "npm:2.1.0" + "@opentelemetry/resources": "npm:2.1.0" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.10.0" + checksum: 10/bae338ed2f518e7873d17d28a756c1eed65990a5cc36930fba0f88a2e1f794ffa2eb2394f3eff1fd61420796536358534bce3be005c83812fc1a7f0ed9c61700 + languageName: node + linkType: hard + "@opentelemetry/sdk-trace-base@npm:^1.19.0": version: 1.30.1 resolution: "@opentelemetry/sdk-trace-base@npm:1.30.1" @@ -500,6 +547,18 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/sdk-trace-web@npm:^2.0.0": + version: 2.1.0 + resolution: "@opentelemetry/sdk-trace-web@npm:2.1.0" + dependencies: + "@opentelemetry/core": "npm:2.1.0" + "@opentelemetry/sdk-trace-base": "npm:2.1.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/e639fa01101b4d824cf6137a439de318be722364b8e180106d5c563c0d307ead5cee1a9b9a8e1ce3896496df6a7068377ae39db190b9afa77cf6231e374caf49 + languageName: node + linkType: hard + "@opentelemetry/semantic-conventions@npm:1.28.0": version: 1.28.0 resolution: "@opentelemetry/semantic-conventions@npm:1.28.0" @@ -507,10 +566,10 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/semantic-conventions@npm:^1.19.0": - version: 1.36.0 - resolution: "@opentelemetry/semantic-conventions@npm:1.36.0" - checksum: 10/f1939066c30147348b326840d67cc48e73072b762f2e2af5c3ea894268d64c62fc4e73fad49a72ed4a52a543b2fa0824c969a676e658ae727f75182f52104007 +"@opentelemetry/semantic-conventions@npm:^1.19.0, @opentelemetry/semantic-conventions@npm:^1.29.0": + version: 1.37.0 + resolution: "@opentelemetry/semantic-conventions@npm:1.37.0" + checksum: 10/919951c2ddbe5509dad26afc7b09d9a5e3c169de187013d1836d2771a7e840e85ea500725128b798d7659d89aff480c07fd039cf77858df3f3573a15063db7c3 languageName: node linkType: hard @@ -560,15 +619,6 @@ __metadata: languageName: node linkType: hard -"@sinonjs/commons@npm:^2.0.0": - version: 2.0.0 - resolution: "@sinonjs/commons@npm:2.0.0" - dependencies: - type-detect: "npm:4.0.8" - checksum: 10/bd6b44957077cd99067dcf401e80ed5ea03ba930cba2066edbbfe302d5fc973a108db25c0ae4930ee53852716929e4c94fa3b8a1510a51ac6869443a139d1e3d - languageName: node - linkType: hard - "@sinonjs/commons@npm:^3.0.0, @sinonjs/commons@npm:^3.0.1": version: 3.0.1 resolution: "@sinonjs/commons@npm:3.0.1" @@ -578,7 +628,7 @@ __metadata: languageName: node linkType: hard -"@sinonjs/fake-timers@npm:11.2.2, @sinonjs/fake-timers@npm:^11.2.2": +"@sinonjs/fake-timers@npm:11.2.2": version: 11.2.2 resolution: "@sinonjs/fake-timers@npm:11.2.2" dependencies: @@ -587,21 +637,29 @@ __metadata: languageName: node linkType: hard +"@sinonjs/fake-timers@npm:^13.0.1": + version: 13.0.5 + resolution: "@sinonjs/fake-timers@npm:13.0.5" + dependencies: + "@sinonjs/commons": "npm:^3.0.1" + checksum: 10/11ee417968fc4dce1896ab332ac13f353866075a9d2a88ed1f6258f17cc4f7d93e66031b51fcddb8c203aa4d53fd980b0ae18aba06269f4682164878a992ec3f + languageName: node + linkType: hard + "@sinonjs/samsam@npm:^8.0.0": - version: 8.0.0 - resolution: "@sinonjs/samsam@npm:8.0.0" + version: 8.0.3 + resolution: "@sinonjs/samsam@npm:8.0.3" dependencies: - "@sinonjs/commons": "npm:^2.0.0" - lodash.get: "npm:^4.4.2" - type-detect: "npm:^4.0.8" - checksum: 10/0c9928a7d16a2428ba561e410d9d637c08014d549cac4979c63a6580c56b69378dba80ea01b17e8e163f2ca5dd331376dae92eae8364857ef827ae59dbcfe0ce + "@sinonjs/commons": "npm:^3.0.1" + type-detect: "npm:^4.1.0" + checksum: 10/af95fba2bcc4502ec2fd60cca568b89f0a4c21ef78c256fdf38bc2ce248f28f9001c63ce6e0aacdfcac8c6239ca8714b050560878a491af7b4836dce2451ff81 languageName: node linkType: hard -"@sinonjs/text-encoding@npm:^0.7.2": - version: 0.7.2 - resolution: "@sinonjs/text-encoding@npm:0.7.2" - checksum: 10/ec713fb44888c852d84ca54f6abf9c14d036c11a5d5bfab7825b8b9d2b22127dbe53412c68f4dbb0c05ea5ed61c64679bd2845c177d81462db41e0d3d7eca499 +"@sinonjs/text-encoding@npm:^0.7.3": + version: 0.7.3 + resolution: "@sinonjs/text-encoding@npm:0.7.3" + checksum: 10/f0cc89bae36e7ce159187dece7800b78831288f1913e9ae8cf8a878da5388232d2049740f6f4a43ec4b43b8ad1beb55f919f45eb9a577adb4a2a6eacb27b25fc languageName: node linkType: hard @@ -643,11 +701,11 @@ __metadata: linkType: hard "@types/node@npm:*, @types/node@npm:>=10.0.0": - version: 24.1.0 - resolution: "@types/node@npm:24.1.0" + version: 24.5.2 + resolution: "@types/node@npm:24.5.2" dependencies: - undici-types: "npm:~7.8.0" - checksum: 10/02c3d91e1407a93a2363f97a245475fa0f6209d3f3e6ba9fdaabe65389e3e078f648da81b8738125dec6b6bf98c50fb928f36f4e72b8b015817bc21479a868c2 + undici-types: "npm:~7.12.0" + checksum: 10/a497aea88a12131b03382d933690b71c131ee890232596b8d5b73f0a20c90874001800b2bfc267bd37df8285bef911729b4773426be7d2dc13ef4c760904e47d languageName: node linkType: hard @@ -668,14 +726,14 @@ __metadata: languageName: node linkType: hard -"@typespec/ts-http-runtime@npm:^0.2.2": - version: 0.2.2 - resolution: "@typespec/ts-http-runtime@npm:0.2.2" +"@typespec/ts-http-runtime@npm:^0.3.0": + version: 0.3.1 + resolution: "@typespec/ts-http-runtime@npm:0.3.1" dependencies: http-proxy-agent: "npm:^7.0.0" https-proxy-agent: "npm:^7.0.0" tslib: "npm:^2.6.2" - checksum: 10/71aa7cea8fee84d3b3f076ab41b2b1b6a4761c2b08e05e475d20a00eeab89739e5d95b96aa633c6ebb33093c2eaeaf9b331c956ff21d3a204adfb1b607a7ba96 + checksum: 10/30cdf5f371e0e75a17f33a148942671079abe0bbb8acd8304cc2863a33ae60515a06018d3cdf1a8cc03885fef7d566d45e332434cedab2dce6cf6f0dccee0acc languageName: node linkType: hard @@ -734,18 +792,18 @@ __metadata: linkType: hard "acorn@npm:^8.14.0": - version: 8.14.1 - resolution: "acorn@npm:8.14.1" + version: 8.15.0 + resolution: "acorn@npm:8.15.0" bin: acorn: bin/acorn - checksum: 10/d1379bbee224e8d44c3c3946e6ba6973e999fbdd4e22e41c3455d7f9b6f72f7ce18d3dc218002e1e48eea789539cf1cb6d1430c81838c6744799c712fb557d92 + checksum: 10/77f2de5051a631cf1729c090e5759148459cdb76b5f5c70f890503d629cf5052357b0ce783c0f976dd8a93c5150f59f6d18df1def3f502396a20f81282482fa4 languageName: node linkType: hard "agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": - version: 7.1.3 - resolution: "agent-base@npm:7.1.3" - checksum: 10/3db6d8d4651f2aa1a9e4af35b96ab11a7607af57a24f3bc721a387eaa3b5f674e901f0a648b0caefd48f3fd117c7761b79a3b55854e2aebaa96c3f32cf76af84 + version: 7.1.4 + resolution: "agent-base@npm:7.1.4" + checksum: 10/79bef167247789f955aaba113bae74bf64aa1e1acca4b1d6bb444bdf91d82c3e07e9451ef6a6e2e35e8f71a6f97ce33e3d855a5328eb9fad1bc3cc4cfd031ed8 languageName: node linkType: hard @@ -813,9 +871,9 @@ __metadata: linkType: hard "ansi-styles@npm:^6.1.0": - version: 6.2.1 - resolution: "ansi-styles@npm:6.2.1" - checksum: 10/70fdf883b704d17a5dfc9cde206e698c16bcd74e7f196ab821511651aee4f9f76c9514bdfa6ca3a27b5e49138b89cb222a28caf3afe4567570139577f991df32 + version: 6.2.3 + resolution: "ansi-styles@npm:6.2.3" + checksum: 10/c49dad7639f3e48859bd51824c93b9eb0db628afc243c51c3dd2410c4a15ede1a83881c6c7341aa2b159c4f90c11befb38f2ba848c07c66c9f9de4bcd7cb9f30 languageName: node linkType: hard @@ -1061,6 +1119,15 @@ __metadata: languageName: node linkType: hard +"baseline-browser-mapping@npm:^2.8.3": + version: 2.8.6 + resolution: "baseline-browser-mapping@npm:2.8.6" + bin: + baseline-browser-mapping: dist/cli.js + checksum: 10/05c89fb1aa864a2a3b5fc9b7f3a4ed3e102ae4d6fa9ccf96a2b8f57fd0c995fb8b4e9ea3152b34c5661533a198026b713e1be415e96473322705b2fbd8dddc48 + languageName: node + linkType: hard + "basic-auth@npm:~2.0.1": version: 2.0.1 resolution: "basic-auth@npm:2.0.1" @@ -1148,16 +1215,17 @@ __metadata: linkType: hard "browserslist@npm:^4.24.0": - version: 4.25.1 - resolution: "browserslist@npm:4.25.1" + version: 4.26.2 + resolution: "browserslist@npm:4.26.2" dependencies: - caniuse-lite: "npm:^1.0.30001726" - electron-to-chromium: "npm:^1.5.173" - node-releases: "npm:^2.0.19" + baseline-browser-mapping: "npm:^2.8.3" + caniuse-lite: "npm:^1.0.30001741" + electron-to-chromium: "npm:^1.5.218" + node-releases: "npm:^2.0.21" update-browserslist-db: "npm:^1.1.3" bin: browserslist: cli.js - checksum: 10/bfb5511b425886279bbe2ea44d10e340c8aea85866c9d45083c13491d049b6362e254018c0afbf56d41ceeb64f994957ea8ae98dbba74ef1e54ef901c8732987 + checksum: 10/7f732f1a9c18c510aa146270d704b7b1acab52c9922147d453eecd70c926f21d97c7ac10f5303668d444fa60bd3b8778a63a797be249b0d348af4c3a644fa530 languageName: node linkType: hard @@ -1246,10 +1314,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001726": - version: 1.0.30001731 - resolution: "caniuse-lite@npm:1.0.30001731" - checksum: 10/ad6771127c0cca13a711ca363bb0165a687f9a5c76f057b2c8c9746608a6bae2f15c7b6955063eefcd4ca0e5733d429f25292cc093a40a189d662f3a8647a020 +"caniuse-lite@npm:^1.0.30001741": + version: 1.0.30001743 + resolution: "caniuse-lite@npm:1.0.30001743" + checksum: 10/e55b13b4a547c9f610a68d5f5668a3239ada4a4aef5d198860397757ab7c03a5b0590675b7e82c5d3d57316b40499e1da52decb08a97ef3066a338871bbb5c37 languageName: node linkType: hard @@ -1757,14 +1825,14 @@ __metadata: linkType: hard "debug@npm:4, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.7, debug@npm:^4.4.0": - version: 4.4.1 - resolution: "debug@npm:4.4.1" + version: 4.4.3 + resolution: "debug@npm:4.4.3" dependencies: ms: "npm:^2.1.3" peerDependenciesMeta: supports-color: optional: true - checksum: 10/8e2709b2144f03c7950f8804d01ccb3786373df01e406a0f66928e47001cf2d336cbed9ee137261d4f90d68d8679468c755e3548ed83ddacdc82b194d2468afe + checksum: 10/9ada3434ea2993800bd9a1e320bd4aa7af69659fb51cca685d390949434bc0a8873c21ed7c9b852af6f2455a55c6d050aa3937d52b3c69f796dab666f762acad languageName: node linkType: hard @@ -1963,10 +2031,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.5.173": - version: 1.5.192 - resolution: "electron-to-chromium@npm:1.5.192" - checksum: 10/99f5dc94b077738a8dadba553e673fc8da33407df58efe870d7c4872895207862e044049bda0d41e2743a97918b2028d1c7e8ebb6a6c7786ec179a5a47bbe0b2 +"electron-to-chromium@npm:^1.5.218": + version: 1.5.222 + resolution: "electron-to-chromium@npm:1.5.222" + checksum: 10/f1f7b21598ddf77a8e44f8e288bc0fb5c82c110ae1df7174a188ea7d2f81851d8e693d46ad2916e2ae0014b7368d331f8c5bee5175e572d7180f91153b251f8d languageName: node linkType: hard @@ -2500,15 +2568,15 @@ __metadata: languageName: node linkType: hard -"fdir@npm:^6.4.4": - version: 6.4.6 - resolution: "fdir@npm:6.4.6" +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: picomatch: optional: true - checksum: 10/c186ba387e7b75ccf874a098d9bc5fe0af0e9c52fc56f8eac8e80aa4edb65532684bf2bf769894ff90f53bf221d6136692052d31f07a9952807acae6cbe7ee50 + checksum: 10/14ca1c9f0a0e8f4f2e9bf4e8551065a164a09545dae548c12a18d238b72e51e5a7b39bd8e5494b56463a0877672d0a6c1ef62c6fa0677db1b0c847773be939b1 languageName: node linkType: hard @@ -3142,7 +3210,16 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": +"iconv-lite@npm:0.7.0": + version: 0.7.0 + resolution: "iconv-lite@npm:0.7.0" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10/5bfc897fedfb7e29991ae5ef1c061ed4f864005f8c6d61ef34aba6a3885c04bd207b278c0642b041383aeac2d11645b4319d0ca7b863b0be4be0cde1c9238ca7 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" dependencies: @@ -3169,14 +3246,14 @@ __metadata: linkType: hard "import-in-the-middle@npm:^1.8.1": - version: 1.14.0 - resolution: "import-in-the-middle@npm:1.14.0" + version: 1.14.2 + resolution: "import-in-the-middle@npm:1.14.2" dependencies: acorn: "npm:^8.14.0" acorn-import-attributes: "npm:^1.9.5" cjs-module-lexer: "npm:^1.2.2" module-details-from-path: "npm:^1.0.3" - checksum: 10/c0b73a5637c0c7ec0ab19d5687a571dc864d90e0603dd45e28939624707def244a5dae402d7dccc1f5fc5b54ffcf18313a071d13048c390a495696d6b9f290fc + checksum: 10/45934b366d7f344e1cbfb6141ed93d3c2ced7021d2dd49b0e2474bab4f571e11f7f377c0f510f03e2d4ba61074d64b9f04677497d3f14106c8cc6f44c749f068 languageName: node linkType: hard @@ -3281,13 +3358,10 @@ __metadata: languageName: node linkType: hard -"ip-address@npm:^9.0.5": - version: 9.0.5 - resolution: "ip-address@npm:9.0.5" - dependencies: - jsbn: "npm:1.1.0" - sprintf-js: "npm:^1.1.3" - checksum: 10/1ed81e06721af012306329b31f532b5e24e00cb537be18ddc905a84f19fe8f83a09a1699862bf3a1ec4b9dea93c55a3fa5faf8b5ea380431469df540f38b092c +"ip-address@npm:^10.0.1": + version: 10.0.1 + resolution: "ip-address@npm:10.0.1" + checksum: 10/09731acda32cd8e14c46830c137e7e5940f47b36d63ffb87c737331270287d631cf25aa95570907a67d3f919fdb25f4470c404eda21e62f22e0a55927f4dd0fb languageName: node linkType: hard @@ -3697,12 +3771,12 @@ __metadata: linkType: hard "istanbul-reports@npm:^3.0.2": - version: 3.1.7 - resolution: "istanbul-reports@npm:3.1.7" + version: 3.2.0 + resolution: "istanbul-reports@npm:3.2.0" dependencies: html-escaper: "npm:^2.0.0" istanbul-lib-report: "npm:^3.0.0" - checksum: 10/f1faaa4684efaf57d64087776018d7426312a59aa6eeb4e0e3a777347d23cd286ad18f427e98f0e3dee666103d7404c9d7abc5f240406a912fa16bd6695437fa + checksum: 10/6773a1d5c7d47eeec75b317144fe2a3b1da84a44b6282bebdc856e09667865e58c9b025b75b3d87f5bc62939126cbba4c871ee84254537d934ba5da5d4c4ec4e languageName: node linkType: hard @@ -3787,13 +3861,6 @@ __metadata: languageName: node linkType: hard -"jsbn@npm:1.1.0": - version: 1.1.0 - resolution: "jsbn@npm:1.1.0" - checksum: 10/bebe7ae829bbd586ce8cbe83501dd8cb8c282c8902a8aeeed0a073a89dc37e8103b1244f3c6acd60278bcbfe12d93a3f83c9ac396868a3b3bbc3c5e5e3b648ef - languageName: node - linkType: hard - "jsesc@npm:^3.0.2": version: 3.1.0 resolution: "jsesc@npm:3.1.0" @@ -3980,13 +4047,6 @@ __metadata: languageName: node linkType: hard -"lodash.get@npm:^4.4.2": - version: 4.4.2 - resolution: "lodash.get@npm:4.4.2" - checksum: 10/2a4925f6e89bc2c010a77a802d1ba357e17ed1ea03c2ddf6a146429f2856a216663e694a6aa3549a318cbbba3fd8b7decb392db457e6ac0b83dc745ed0a17380 - languageName: node - linkType: hard - "lodash.isarguments@npm:^3.0.0": version: 3.1.0 resolution: "lodash.isarguments@npm:3.1.0" @@ -4516,15 +4576,15 @@ __metadata: linkType: hard "nise@npm:^6.0.0": - version: 6.0.0 - resolution: "nise@npm:6.0.0" + version: 6.1.1 + resolution: "nise@npm:6.1.1" dependencies: - "@sinonjs/commons": "npm:^3.0.0" - "@sinonjs/fake-timers": "npm:^11.2.2" - "@sinonjs/text-encoding": "npm:^0.7.2" + "@sinonjs/commons": "npm:^3.0.1" + "@sinonjs/fake-timers": "npm:^13.0.1" + "@sinonjs/text-encoding": "npm:^0.7.3" just-extend: "npm:^6.2.0" - path-to-regexp: "npm:^6.2.1" - checksum: 10/a11be5fd21ece95c80fda14a2cf80350404acc895467fc5104dc9ea9c0630614fcc83e10591ead96796b31aa2f3ccb7dc9198ed940d0f3e91e760bf5104d41a8 + path-to-regexp: "npm:^8.1.0" + checksum: 10/2d3175587cf0a351e2c91eb643fdc59d266de39f394a3ac0bace38571749d1e7f25341d763899245139b8f0d2ee048b2d3387d75ecf94c4897e947d5fc881eea languageName: node linkType: hard @@ -4588,8 +4648,8 @@ __metadata: linkType: hard "node-gyp@npm:latest": - version: 11.3.0 - resolution: "node-gyp@npm:11.3.0" + version: 11.4.2 + resolution: "node-gyp@npm:11.4.2" dependencies: env-paths: "npm:^2.2.0" exponential-backoff: "npm:^3.1.1" @@ -4603,7 +4663,7 @@ __metadata: which: "npm:^5.0.0" bin: node-gyp: bin/node-gyp.js - checksum: 10/e7fb17ba72172d743ee791ea470cba1a579d1c37bcaa67a45d221d07df055baf1367d0684b3c7ee2b9b61f260cea77b2016aaac47027dbc0f43030c90b21527d + checksum: 10/de0fdd1a23d27976974f2480b1c5a2954180050f4d7d682b2fcd36a7c996100981fc37ba0c893d02471ccf1730240f73c3073a6a9397c5eb3bb7578ca82808ed languageName: node linkType: hard @@ -4642,10 +4702,10 @@ __metadata: languageName: node linkType: hard -"node-releases@npm:^2.0.19": - version: 2.0.19 - resolution: "node-releases@npm:2.0.19" - checksum: 10/c2b33b4f0c40445aee56141f13ca692fa6805db88510e5bbb3baadb2da13e1293b738e638e15e4a8eb668bb9e97debb08e7a35409b477b5cc18f171d35a83045 +"node-releases@npm:^2.0.21": + version: 2.0.21 + resolution: "node-releases@npm:2.0.21" + checksum: 10/5344d634b39d20f47c0d85a1c64567fdb9cf46f7b27ed3d141f752642faab47dae326835c2109636f823758afb16ffbed7b0c0fe6f800ef91cec9f2beb4f2b4a languageName: node linkType: hard @@ -5034,17 +5094,10 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:^6.2.1": - version: 6.3.0 - resolution: "path-to-regexp@npm:6.3.0" - checksum: 10/6822f686f01556d99538b350722ef761541ec0ce95ca40ce4c29e20a5b492fe8361961f57993c71b2418de12e604478dcf7c430de34b2c31a688363a7a944d9c - languageName: node - linkType: hard - -"path-to-regexp@npm:^8.0.0": - version: 8.2.0 - resolution: "path-to-regexp@npm:8.2.0" - checksum: 10/23378276a172b8ba5f5fb824475d1818ca5ccee7bbdb4674701616470f23a14e536c1db11da9c9e6d82b82c556a817bbf4eee6e41b9ed20090ef9427cbb38e13 +"path-to-regexp@npm:^8.0.0, path-to-regexp@npm:^8.1.0": + version: 8.3.0 + resolution: "path-to-regexp@npm:8.3.0" + checksum: 10/568f148fc64f5fd1ecebf44d531383b28df924214eabf5f2570dce9587a228e36c37882805ff02d71c6209b080ea3ee6a4d2b712b5df09741b67f1f3cf91e55a languageName: node linkType: hard @@ -5079,7 +5132,7 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^4.0.2": +"picomatch@npm:^4.0.3": version: 4.0.3 resolution: "picomatch@npm:4.0.3" checksum: 10/57b99055f40b16798f2802916d9c17e9744e620a0db136554af01d19598b96e45e2f00014c91d1b8b13874b80caa8c295b3d589a3f72373ec4aaf54baa5962d5 @@ -5213,14 +5266,14 @@ __metadata: linkType: hard "raw-body@npm:^3.0.0": - version: 3.0.0 - resolution: "raw-body@npm:3.0.0" + version: 3.0.1 + resolution: "raw-body@npm:3.0.1" dependencies: bytes: "npm:3.1.2" http-errors: "npm:2.0.0" - iconv-lite: "npm:0.6.3" + iconv-lite: "npm:0.7.0" unpipe: "npm:1.0.0" - checksum: 10/2443429bbb2f9ae5c50d3d2a6c342533dfbde6b3173740b70fa0302b30914ff400c6d31a46b3ceacbe7d0925dc07d4413928278b494b04a65736fc17ca33e30c + checksum: 10/3cc63e154147d15200ebf4fe3fb806682b268b8c6256ef3296f60025b07b67a028c1c92b3985b4ec1c7af08b7365ef91b0d0597b957c1c6ac40241b5f6b7d38b languageName: node linkType: hard @@ -5795,12 +5848,12 @@ __metadata: linkType: hard "socks@npm:^2.8.3": - version: 2.8.6 - resolution: "socks@npm:2.8.6" + version: 2.8.7 + resolution: "socks@npm:2.8.7" dependencies: - ip-address: "npm:^9.0.5" + ip-address: "npm:^10.0.1" smart-buffer: "npm:^4.2.0" - checksum: 10/7aef197dee914a01a39d5f250a59f0c9fa0a9fd10f8135ee2cff6c42576993ae1c817503a12eb424f1bb69f1e234f8dbaf8cc48bfcfa10c51a12af8f0ea97698 + checksum: 10/d19366c95908c19db154f329bbe94c2317d315dc933a7c2b5101e73f32a555c84fb199b62174e1490082a593a4933d8d5a9b297bde7d1419c14a11a965f51356 languageName: node linkType: hard @@ -5834,13 +5887,6 @@ __metadata: languageName: node linkType: hard -"sprintf-js@npm:^1.1.3": - version: 1.1.3 - resolution: "sprintf-js@npm:1.1.3" - checksum: 10/e7587128c423f7e43cc625fe2f87e6affdf5ca51c1cc468e910d8aaca46bb44a7fbcfa552f787b1d3987f7043aeb4527d1b99559e6621e01b42b3f45e5a24cbb - languageName: node - linkType: hard - "sprintf-js@npm:~1.0.2": version: 1.0.3 resolution: "sprintf-js@npm:1.0.3" @@ -6011,11 +6057,11 @@ __metadata: linkType: hard "strip-ansi@npm:^7.0.1": - version: 7.1.0 - resolution: "strip-ansi@npm:7.1.0" + version: 7.1.2 + resolution: "strip-ansi@npm:7.1.2" dependencies: ansi-regex: "npm:^6.0.1" - checksum: 10/475f53e9c44375d6e72807284024ac5d668ee1d06010740dec0b9744f2ddf47de8d7151f80e5f6190fc8f384e802fdf9504b76a7e9020c9faee7103623338be2 + checksum: 10/db0e3f9654e519c8a33c50fc9304d07df5649388e7da06d3aabf66d29e5ad65d5e6315d8519d409c15b32fa82c1df7e11ed6f8cd50b0e4404463f0c9d77c8d0b languageName: node linkType: hard @@ -6178,12 +6224,12 @@ __metadata: linkType: hard "tinyglobby@npm:^0.2.12": - version: 0.2.14 - resolution: "tinyglobby@npm:0.2.14" + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" dependencies: - fdir: "npm:^6.4.4" - picomatch: "npm:^4.0.2" - checksum: 10/3d306d319718b7cc9d79fb3f29d8655237aa6a1f280860a217f93417039d0614891aee6fc47c5db315f4fcc6ac8d55eb8e23e2de73b2c51a431b42456d9e5764 + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.3" + checksum: 10/d72bd826a8b0fa5fa3929e7fe5ba48fceb2ae495df3a231b6c5408cd7d8c00b58ab5a9c2a76ba56a62ee9b5e083626f1f33599734bed1ffc4b792406408f0ca2 languageName: node linkType: hard @@ -6261,7 +6307,7 @@ __metadata: languageName: node linkType: hard -"type-detect@npm:^4.0.0, type-detect@npm:^4.0.8, type-detect@npm:^4.1.0": +"type-detect@npm:^4.0.0, type-detect@npm:^4.1.0": version: 4.1.0 resolution: "type-detect@npm:4.1.0" checksum: 10/e363bf0352427a79301f26a7795a27718624c49c576965076624eb5495d87515030b207217845f7018093adcbe169b2d119bb9b7f1a31a92bfbb1ab9639ca8dd @@ -6384,10 +6430,10 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~7.8.0": - version: 7.8.0 - resolution: "undici-types@npm:7.8.0" - checksum: 10/fcff3fbab234f067fbd69e374ee2c198ba74c364ceaf6d93db7ca267e784457b5518cd01d0d2329b075f412574205ea3172a9a675facb49b4c9efb7141cd80b7 +"undici-types@npm:~7.12.0": + version: 7.12.0 + resolution: "undici-types@npm:7.12.0" + checksum: 10/4a0f927c98828f76fb0d64f356e36e5ac6e074ae4c7bec08d6de8bc36b7cf08ae27a3518fa8eb703f51c1a675241e2d07359bbce63f5575299148a270cea7e43 languageName: node linkType: hard From 0c00236a590238b1348afd79832a4b6bdf2878b0 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Fri, 3 Oct 2025 11:11:44 +0100 Subject: [PATCH 061/113] EUI-2976 activity tracker changes --- app/socket/service/activity-service.js | 36 ++++++++++++++++++++++++++ app/socket/service/handlers.js | 33 +++++++++++++++++++++-- app/socket/utils/get.js | 4 +++ 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/app/socket/service/activity-service.js b/app/socket/service/activity-service.js index 5ecf6c03..2124c360 100644 --- a/app/socket/service/activity-service.js +++ b/app/socket/service/activity-service.js @@ -26,6 +26,8 @@ module.exports = (config, redis) => { // Get hold of the details. const details = await redis.pipeline(utils.get.users(userIds)).exec(); + // console.log('user details for userIds ', userIds, ' => ', details); + // Now turn them into a map. return details.reduce((obj, item) => { if (item[1]) { @@ -41,6 +43,7 @@ module.exports = (config, redis) => { const doRemoveSocketActivity = async (socketId) => { // First make sure we actually have some activity to remove. const activity = await getSocketActivity(socketId); + console.log('Removing activity for socketId ', socketId, ' activity ', activity); if (activity) { await redis.pipeline([ utils.remove.userActivity(activity), @@ -61,6 +64,7 @@ module.exports = (config, redis) => { const doAddActivity = async (caseId, user, socketId, activity) => { // Now store this activity. const activityKey = keys.case[activity](caseId); + //console.log(`storing activity '${activity}' for caseId '${caseId}' and user`, user, `on socket '${socketId}'`); return redis.pipeline([ utils.store.userActivity(activityKey, user.uid, utils.score(ttl.activity)), utils.store.socketActivity(socketId, activityKey, caseId, user.uid, ttl.user), @@ -69,11 +73,18 @@ module.exports = (config, redis) => { }; const addActivity = async (caseId, user, socketId, activity) => { + console.log(`adding activity for caseId '${caseId}', user`, user, `on socket '${socketId}' with activity '${activity}'`); if (caseId && user && socketId && activity) { // First, clear out any existing activity on this socket. const removedCaseId = await doRemoveSocketActivity(socketId); + let cs = await getActivityForCases([caseId]); + console.log('notifying case activity: addActivity method ', JSON.stringify(cs, null, 2)); + + console.log('removedCaseId => ', removedCaseId); + // Now store this activity. + // console.log('about to doAddActivity'); await doAddActivity(caseId, user, socketId, activity); if (removedCaseId !== caseId) { notifyChange(removedCaseId); @@ -87,17 +98,30 @@ module.exports = (config, redis) => { if (!Array.isArray(caseIds) || caseIds.length === 0) { return []; } + // console.log('calling getActivityForCases with caseIds:', caseIds); let uniqueUserIds = []; let caseViewers = []; let caseEditors = []; const now = Date.now(); const getPromise = async (activity, failureMessage, cb) => { + + // console.log('utils.get.caseActivities(caseIds, activity, now) => ', utils.get.caseActivities(caseIds, activity, now)); + + // console.log('Redis Pipeline => ', await redis.pipeline( + // utils.get.caseActivities(caseIds, activity, now))); + const result = await redis.pipeline( utils.get.caseActivities(caseIds, activity, now) ).exec(); + + console.log(`raw result for activity '${activity}' =>`, result); + redis.logPipelineFailures(result, failureMessage); cb(result); + // console.log(`result for activity '${activity}' =>`, result); + // console.log(`uniqueUserIds before extracting for activity '${activity}' =>`, uniqueUserIds); uniqueUserIds = utils.extractUniqueUserIds(result, uniqueUserIds); + // console.log(`uniqueUserIds after extracting for activity '${activity}' =>`, uniqueUserIds); }; // Set up the promises fore view and edit. @@ -112,13 +136,25 @@ module.exports = (config, redis) => { await Promise.all([caseViewersPromise, caseEditorsPromise]); // Get all the user details for both viewers and editors. + console.log('uniqueUserIds => ', uniqueUserIds); + const userDetails = await getUserDetails(uniqueUserIds); + console.log('case viewers => ', caseViewers); + console.log('case editors => ', caseEditors); + + + console.log('case ids => ', caseIds); + // console.log('case viewers =>', caseViewers); + // console.log('case editors =>', caseEditors); + console.log('user details => ', userDetails); + // Now produce a response for every case requested. return caseIds.map((caseId, index) => { const cv = caseViewers[index][1]; const ce = caseEditors[index][1]; const viewers = cv ? cv.map((v) => userDetails[v]) : []; + // console.log('viewers for caseId ', caseId, ' => ', viewers); const editors = ce ? ce.map((e) => userDetails[e]) : []; return { caseId, diff --git a/app/socket/service/handlers.js b/app/socket/service/handlers.js index a41fda84..b3fb8967 100644 --- a/app/socket/service/handlers.js +++ b/app/socket/service/handlers.js @@ -10,11 +10,15 @@ module.exports = (activityService, socketServer) => { * @param {*} activity Whether they're viewing or editing. */ async function addActivity(socket, caseId, user, activity) { + // Update what's being watched. utils.watch.update(socket, [caseId]); + console.log('Adding activity for caseId ', caseId, ' user ', user, ' activity ', activity); + // Then add this new activity to redis, which will also clear out the old activity. - await activityService.addActivity(caseId, utils.toUser(user), socket.id, activity); + await activityService.addActivity(caseId, user, socket.id, activity); + } /** @@ -23,7 +27,29 @@ module.exports = (activityService, socketServer) => { * notified about. */ async function notify(caseId) { - const cs = await activityService.getActivityForCases([caseId]); + // console.log('notifying change for caseId: ', caseId); + let cs = await activityService.getActivityForCases([caseId]); + console.log('notifying case activity: ', JSON.stringify(cs, null, 2)); + // Temp hack to get around lack of user details in redis. + // cs = [ + // { + // caseId: '1712141847061149', + // viewers: [ + // { + // id: '1712141847061149', + // forename: 'SSC', + // surname: 'Super User' + // } + // ], + // unknownViewers: 0, + // editors: [ { + // id: '1712141847061149', + // forename: 'SSC', + // surname: 'Super User' + // }], + // unknownEditors: 0 + // } + // ]; socketServer.to(keys.case.base(caseId)).emit('activity', cs); } @@ -65,3 +91,6 @@ module.exports = (activityService, socketServer) => { watch }; }; + + +// ws://localhost:3000/socket.io/?user=%7B%22given_name%22%3A%22SSCS%22%2C%22email%22%3A%22sscs.superuserhmc%40justice.gov.uk%22%2C%22family_name%22%3A%22superuser%22%2C%22name%22%3A%22SSCS%20superuser%22%2C%22ssoProvider%22%3A%22testing-support%22%2C%22uid%22%3A%2241033a79-b9c1-4a36-b0ff-113451f736ba%22%2C%22identity%22%3A%22id%3D41033a79-b9c1-4a36-b0ff-113451f736ba%2Cou%3Duser%2Co%3Dhmcts%2Cou%3Dservices%2Cou%3Dam-config%22%2C%22roles%22%3A%5B%22caseworker%22%2C%22caseworker-sscs%22%2C%22caseworker-sscs-superuser%22%2C%22caseworker-sscs-systemupdate%22%2C%22cwd-user%22%2C%22staff-admin%22%2C%22hmcts-legal-operations%22%2C%22hearing-viewer%22%2C%22hearing-manager%22%2C%22tribunal-caseworker%22%2C%22sscs-tribunal-caseworker%22%5D%2C%22sub%22%3A%22sscs.superuserhmc%40justice.gov.uk%22%2C%22subname%22%3A%22sscs.superuserhmc%40justice.gov.uk%22%2C%22iss%22%3A%22https%3A%2F%2Fforgerock-am.service.core-compute-idam-aat2.internal%3A8443%2Fopenam%2Foauth2%2Frealms%2Froot%2Frealms%2Fhmcts%22%2C%22roleCategory%22%3A%22LEGAL_OPERATIONS%22%7D&EIO=3&transport=websocket \ No newline at end of file diff --git a/app/socket/utils/get.js b/app/socket/utils/get.js index 91f9d728..034ac4eb 100644 --- a/app/socket/utils/get.js +++ b/app/socket/utils/get.js @@ -2,14 +2,18 @@ const keys = require('../redis/keys'); const get = { caseActivities: (caseIds, activity, now) => { + console.log(`getting case activities for activity '${activity}' and caseIds: `, caseIds); + if (Array.isArray(caseIds) && ['view', 'edit'].indexOf(activity) > -1) { return caseIds.filter((id) => !!id).map((id) => { return ['zrangebyscore', keys.case[activity](id), now, '+inf']; + // return ['zrangebyscore', keys.case[activity](id), '-inf', '+inf']; }); } return []; }, users: (userIds) => { + console.log('getting user details for userIds: ', userIds); if (Array.isArray(userIds)) { return userIds.filter((id) => !!id).map((id) => ['get', keys.user(id)]); } From faf3e6458e2114b84cb8800b235522710685b05f Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Fri, 3 Oct 2025 11:16:38 +0100 Subject: [PATCH 062/113] EUI-2976 activity tracker changes --- app/socket/service/activity-service.js | 15 ++++----------- app/socket/service/handlers.js | 15 ++++++--------- app/socket/utils/get.js | 2 +- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/app/socket/service/activity-service.js b/app/socket/service/activity-service.js index 2124c360..366e611a 100644 --- a/app/socket/service/activity-service.js +++ b/app/socket/service/activity-service.js @@ -64,7 +64,6 @@ module.exports = (config, redis) => { const doAddActivity = async (caseId, user, socketId, activity) => { // Now store this activity. const activityKey = keys.case[activity](caseId); - //console.log(`storing activity '${activity}' for caseId '${caseId}' and user`, user, `on socket '${socketId}'`); return redis.pipeline([ utils.store.userActivity(activityKey, user.uid, utils.score(ttl.activity)), utils.store.socketActivity(socketId, activityKey, caseId, user.uid, ttl.user), @@ -78,13 +77,12 @@ module.exports = (config, redis) => { // First, clear out any existing activity on this socket. const removedCaseId = await doRemoveSocketActivity(socketId); - let cs = await getActivityForCases([caseId]); - console.log('notifying case activity: addActivity method ', JSON.stringify(cs, null, 2)); + // const cs = await getActivityForCases([caseId]); + // console.log('notifying case activity: addActivity method ', JSON.stringify(cs, null, 2)); console.log('removedCaseId => ', removedCaseId); // Now store this activity. - // console.log('about to doAddActivity'); await doAddActivity(caseId, user, socketId, activity); if (removedCaseId !== caseId) { notifyChange(removedCaseId); @@ -98,15 +96,12 @@ module.exports = (config, redis) => { if (!Array.isArray(caseIds) || caseIds.length === 0) { return []; } - // console.log('calling getActivityForCases with caseIds:', caseIds); + // console.log('calling getActivityForCases with caseIds:', caseIds); let uniqueUserIds = []; let caseViewers = []; let caseEditors = []; const now = Date.now(); const getPromise = async (activity, failureMessage, cb) => { - - // console.log('utils.get.caseActivities(caseIds, activity, now) => ', utils.get.caseActivities(caseIds, activity, now)); - // console.log('Redis Pipeline => ', await redis.pipeline( // utils.get.caseActivities(caseIds, activity, now))); @@ -119,7 +114,6 @@ module.exports = (config, redis) => { redis.logPipelineFailures(result, failureMessage); cb(result); // console.log(`result for activity '${activity}' =>`, result); - // console.log(`uniqueUserIds before extracting for activity '${activity}' =>`, uniqueUserIds); uniqueUserIds = utils.extractUniqueUserIds(result, uniqueUserIds); // console.log(`uniqueUserIds after extracting for activity '${activity}' =>`, uniqueUserIds); }; @@ -143,7 +137,6 @@ module.exports = (config, redis) => { console.log('case viewers => ', caseViewers); console.log('case editors => ', caseEditors); - console.log('case ids => ', caseIds); // console.log('case viewers =>', caseViewers); // console.log('case editors =>', caseEditors); @@ -154,7 +147,7 @@ module.exports = (config, redis) => { const cv = caseViewers[index][1]; const ce = caseEditors[index][1]; const viewers = cv ? cv.map((v) => userDetails[v]) : []; - // console.log('viewers for caseId ', caseId, ' => ', viewers); + // console.log('viewers for caseId ', caseId, ' => ', viewers); const editors = ce ? ce.map((e) => userDetails[e]) : []; return { caseId, diff --git a/app/socket/service/handlers.js b/app/socket/service/handlers.js index b3fb8967..74b2f95c 100644 --- a/app/socket/service/handlers.js +++ b/app/socket/service/handlers.js @@ -10,15 +10,13 @@ module.exports = (activityService, socketServer) => { * @param {*} activity Whether they're viewing or editing. */ async function addActivity(socket, caseId, user, activity) { - // Update what's being watched. utils.watch.update(socket, [caseId]); console.log('Adding activity for caseId ', caseId, ' user ', user, ' activity ', activity); // Then add this new activity to redis, which will also clear out the old activity. - await activityService.addActivity(caseId, user, socket.id, activity); - + await activityService.addActivity(caseId, user, socket.id, activity); } /** @@ -27,22 +25,22 @@ module.exports = (activityService, socketServer) => { * notified about. */ async function notify(caseId) { - // console.log('notifying change for caseId: ', caseId); - let cs = await activityService.getActivityForCases([caseId]); + // console.log('notifying change for caseId: ', caseId); + const cs = await activityService.getActivityForCases([caseId]); console.log('notifying case activity: ', JSON.stringify(cs, null, 2)); // Temp hack to get around lack of user details in redis. // cs = [ // { // caseId: '1712141847061149', // viewers: [ - // { + // { // id: '1712141847061149', // forename: 'SSC', // surname: 'Super User' // } // ], // unknownViewers: 0, - // editors: [ { + // editors: [ { // id: '1712141847061149', // forename: 'SSC', // surname: 'Super User' @@ -92,5 +90,4 @@ module.exports = (activityService, socketServer) => { }; }; - -// ws://localhost:3000/socket.io/?user=%7B%22given_name%22%3A%22SSCS%22%2C%22email%22%3A%22sscs.superuserhmc%40justice.gov.uk%22%2C%22family_name%22%3A%22superuser%22%2C%22name%22%3A%22SSCS%20superuser%22%2C%22ssoProvider%22%3A%22testing-support%22%2C%22uid%22%3A%2241033a79-b9c1-4a36-b0ff-113451f736ba%22%2C%22identity%22%3A%22id%3D41033a79-b9c1-4a36-b0ff-113451f736ba%2Cou%3Duser%2Co%3Dhmcts%2Cou%3Dservices%2Cou%3Dam-config%22%2C%22roles%22%3A%5B%22caseworker%22%2C%22caseworker-sscs%22%2C%22caseworker-sscs-superuser%22%2C%22caseworker-sscs-systemupdate%22%2C%22cwd-user%22%2C%22staff-admin%22%2C%22hmcts-legal-operations%22%2C%22hearing-viewer%22%2C%22hearing-manager%22%2C%22tribunal-caseworker%22%2C%22sscs-tribunal-caseworker%22%5D%2C%22sub%22%3A%22sscs.superuserhmc%40justice.gov.uk%22%2C%22subname%22%3A%22sscs.superuserhmc%40justice.gov.uk%22%2C%22iss%22%3A%22https%3A%2F%2Fforgerock-am.service.core-compute-idam-aat2.internal%3A8443%2Fopenam%2Foauth2%2Frealms%2Froot%2Frealms%2Fhmcts%22%2C%22roleCategory%22%3A%22LEGAL_OPERATIONS%22%7D&EIO=3&transport=websocket \ No newline at end of file +// ws://localhost:3000/socket.io/?user=%7B%22given_name%22%3A%22SSCS%22%2C%22email%22%3A%22sscs.superuserhmc%40justice.gov.uk%22%2C%22family_name%22%3A%22superuser%22%2C%22name%22%3A%22SSCS%20superuser%22%2C%22ssoProvider%22%3A%22testing-support%22%2C%22uid%22%3A%2241033a79-b9c1-4a36-b0ff-113451f736ba%22%2C%22identity%22%3A%22id%3D41033a79-b9c1-4a36-b0ff-113451f736ba%2Cou%3Duser%2Co%3Dhmcts%2Cou%3Dservices%2Cou%3Dam-config%22%2C%22roles%22%3A%5B%22caseworker%22%2C%22caseworker-sscs%22%2C%22caseworker-sscs-superuser%22%2C%22caseworker-sscs-systemupdate%22%2C%22cwd-user%22%2C%22staff-admin%22%2C%22hmcts-legal-operations%22%2C%22hearing-viewer%22%2C%22hearing-manager%22%2C%22tribunal-caseworker%22%2C%22sscs-tribunal-caseworker%22%5D%2C%22sub%22%3A%22sscs.superuserhmc%40justice.gov.uk%22%2C%22subname%22%3A%22sscs.superuserhmc%40justice.gov.uk%22%2C%22iss%22%3A%22https%3A%2F%2Fforgerock-am.service.core-compute-idam-aat2.internal%3A8443%2Fopenam%2Foauth2%2Frealms%2Froot%2Frealms%2Fhmcts%22%2C%22roleCategory%22%3A%22LEGAL_OPERATIONS%22%7D&EIO=3&transport=websocket diff --git a/app/socket/utils/get.js b/app/socket/utils/get.js index 034ac4eb..01d6f90d 100644 --- a/app/socket/utils/get.js +++ b/app/socket/utils/get.js @@ -3,7 +3,7 @@ const keys = require('../redis/keys'); const get = { caseActivities: (caseIds, activity, now) => { console.log(`getting case activities for activity '${activity}' and caseIds: `, caseIds); - + if (Array.isArray(caseIds) && ['view', 'edit'].indexOf(activity) > -1) { return caseIds.filter((id) => !!id).map((id) => { return ['zrangebyscore', keys.case[activity](id), now, '+inf']; From 805b16ff3f840bd0feef6c3acbece4a0a7709fc0 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Fri, 3 Oct 2025 12:50:48 +0100 Subject: [PATCH 063/113] EUI-2976 activity tracker changes --- test/spec/app/socket/service/handlers.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spec/app/socket/service/handlers.spec.js b/test/spec/app/socket/service/handlers.spec.js index ef9644e6..8ac3e221 100644 --- a/test/spec/app/socket/service/handlers.spec.js +++ b/test/spec/app/socket/service/handlers.spec.js @@ -81,7 +81,7 @@ describe('socket.service.handlers', () => { describe('addActivity', () => { it('should update what the socket is watching and add activity for the specified case', async () => { const CASE_ID = '0987654321'; - const USER = { id: 'a', name: 'John Smith' }; + const USER = { uid: 'a', name: 'John Smith', given_name: 'John', family_name: 'Smith' }; const ACTIVITY = 'view'; // Pretend the socket is watching a bunch of additional rooms. @@ -106,7 +106,7 @@ describe('socket.service.handlers', () => { expect(MOCK_ACTIVITY_SERVICE.calls[0].params.socketId).to.equal(MOCK_SOCKET.id); expect(MOCK_ACTIVITY_SERVICE.calls[0].params.activity).to.equal(ACTIVITY); // The user parameter should have been transformed appropriatel. - expect(MOCK_ACTIVITY_SERVICE.calls[0].params.user.uid).to.equal(USER.id); + expect(MOCK_ACTIVITY_SERVICE.calls[0].params.user.uid).to.equal(USER.uid); expect(MOCK_ACTIVITY_SERVICE.calls[0].params.user.name).to.equal(USER.name); expect(MOCK_ACTIVITY_SERVICE.calls[0].params.user.given_name).to.equal('John'); expect(MOCK_ACTIVITY_SERVICE.calls[0].params.user.family_name).to.equal('Smith'); From 7890ecde7ed356b7ccd54bf0682ea12fc15e6bf9 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Fri, 3 Oct 2025 14:19:38 +0100 Subject: [PATCH 064/113] EUI-2976 activity tracker changes --- charts/ccd-case-activity-api/Chart.yaml | 2 +- charts/ccd-case-activity-api/values.yaml | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/charts/ccd-case-activity-api/Chart.yaml b/charts/ccd-case-activity-api/Chart.yaml index 1a9102da..54475384 100644 --- a/charts/ccd-case-activity-api/Chart.yaml +++ b/charts/ccd-case-activity-api/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 description: Helm chart for the HMCTS CCD Case Activity name: ccd-case-activity-api home: https://github.com/hmcts/ccd-case-activity-api -version: 1.3.14 +version: 1.3.15 maintainers: - name: HMCTS CCD Dev Team email: ccd-devops@HMCTS.NET diff --git a/charts/ccd-case-activity-api/values.yaml b/charts/ccd-case-activity-api/values.yaml index c3cd72c1..921c816c 100644 --- a/charts/ccd-case-activity-api/values.yaml +++ b/charts/ccd-case-activity-api/values.yaml @@ -6,6 +6,12 @@ redis: enabled: false auth: enabled: false + image: + registry: hmctspublic.azurecr.io + repository: imported/bitnami/redis + global: + security: + allowInsecureImages: true nodejs: image: 'hmctspublic.azurecr.io/ccd/case-activity-api:latest' From 493da283d88f62ca871dd8934d9cb5cbd6a3a1a4 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Fri, 3 Oct 2025 16:01:07 +0100 Subject: [PATCH 065/113] EUI-2976 activity tracker changes --- charts/ccd-case-activity-api/values.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/charts/ccd-case-activity-api/values.yaml b/charts/ccd-case-activity-api/values.yaml index 921c816c..62fc8fbc 100644 --- a/charts/ccd-case-activity-api/values.yaml +++ b/charts/ccd-case-activity-api/values.yaml @@ -9,6 +9,7 @@ redis: image: registry: hmctspublic.azurecr.io repository: imported/bitnami/redis + global: security: allowInsecureImages: true From cee7995cdbe098b51ead5efa2d824268a64496b5 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 3 Oct 2025 16:48:18 +0100 Subject: [PATCH 066/113] try add redis img to preview.yaml --- charts/ccd-case-activity-api/values.preview.template.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/charts/ccd-case-activity-api/values.preview.template.yaml b/charts/ccd-case-activity-api/values.preview.template.yaml index 487af142..8b246cf4 100644 --- a/charts/ccd-case-activity-api/values.preview.template.yaml +++ b/charts/ccd-case-activity-api/values.preview.template.yaml @@ -15,3 +15,10 @@ redis: auth: enabled: true password: "fake-password" + image: + registry: hmctspublic.azurecr.io + repository: imported/bitnami/redis + + global: + security: + allowInsecureImages: true From 5fe05311896c2f9227d4dc10d6ff0902d9c0dc4f Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 3 Oct 2025 17:11:48 +0100 Subject: [PATCH 067/113] test old redis ver --- charts/ccd-case-activity-api/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/ccd-case-activity-api/Chart.yaml b/charts/ccd-case-activity-api/Chart.yaml index 54475384..22d3bd49 100644 --- a/charts/ccd-case-activity-api/Chart.yaml +++ b/charts/ccd-case-activity-api/Chart.yaml @@ -11,6 +11,6 @@ dependencies: version: 3.2.0 repository: 'oci://hmctspublic.azurecr.io/helm' - name: redis - version: 20.13.4 + version: 20.11.3 repository: "oci://registry-1.docker.io/bitnamicharts" condition: redis.enabled From 907883ad127b31d228cc50abaacbdccb78ad6ac6 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 3 Oct 2025 17:12:16 +0100 Subject: [PATCH 068/113] fix indent --- charts/ccd-case-activity-api/values.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/charts/ccd-case-activity-api/values.yaml b/charts/ccd-case-activity-api/values.yaml index 62fc8fbc..385b469c 100644 --- a/charts/ccd-case-activity-api/values.yaml +++ b/charts/ccd-case-activity-api/values.yaml @@ -10,9 +10,9 @@ redis: registry: hmctspublic.azurecr.io repository: imported/bitnami/redis - global: - security: - allowInsecureImages: true +global: + security: + allowInsecureImages: true nodejs: image: 'hmctspublic.azurecr.io/ccd/case-activity-api:latest' From 9fbeb67ae08bcd9898d8a31be59f166a373596cd Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 9 Oct 2025 11:20:59 +0100 Subject: [PATCH 069/113] activity ttl sec increased to 3000 --- config/default.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/default.yaml b/config/default.yaml index e4d1be0d..0f627948 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -13,7 +13,7 @@ redis: activityTtlSec: 5 userDetailsTtlSec: 2 socket: - activityTtlSec: 30 + activityTtlSec: 3000 userDetailsTtlSec: 3600 cache: user_info_enabled: true From 248212448c2e0d9a0085a4ca9b45fa862efa6f20 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Fri, 17 Oct 2025 14:09:47 +0100 Subject: [PATCH 070/113] clean up --- app/socket/service/activity-service.js | 29 +------------------------- app/socket/service/handlers.js | 21 ------------------- app/socket/utils/get.js | 2 -- 3 files changed, 1 insertion(+), 51 deletions(-) diff --git a/app/socket/service/activity-service.js b/app/socket/service/activity-service.js index 366e611a..c5fdcb49 100644 --- a/app/socket/service/activity-service.js +++ b/app/socket/service/activity-service.js @@ -25,9 +25,6 @@ module.exports = (config, redis) => { if (Array.isArray(userIds) && userIds.length > 0) { // Get hold of the details. const details = await redis.pipeline(utils.get.users(userIds)).exec(); - - // console.log('user details for userIds ', userIds, ' => ', details); - // Now turn them into a map. return details.reduce((obj, item) => { if (item[1]) { @@ -43,7 +40,6 @@ module.exports = (config, redis) => { const doRemoveSocketActivity = async (socketId) => { // First make sure we actually have some activity to remove. const activity = await getSocketActivity(socketId); - console.log('Removing activity for socketId ', socketId, ' activity ', activity); if (activity) { await redis.pipeline([ utils.remove.userActivity(activity), @@ -77,11 +73,6 @@ module.exports = (config, redis) => { // First, clear out any existing activity on this socket. const removedCaseId = await doRemoveSocketActivity(socketId); - // const cs = await getActivityForCases([caseId]); - // console.log('notifying case activity: addActivity method ', JSON.stringify(cs, null, 2)); - - console.log('removedCaseId => ', removedCaseId); - // Now store this activity. await doAddActivity(caseId, user, socketId, activity); if (removedCaseId !== caseId) { @@ -96,26 +87,19 @@ module.exports = (config, redis) => { if (!Array.isArray(caseIds) || caseIds.length === 0) { return []; } - // console.log('calling getActivityForCases with caseIds:', caseIds); + let uniqueUserIds = []; let caseViewers = []; let caseEditors = []; const now = Date.now(); const getPromise = async (activity, failureMessage, cb) => { - // console.log('Redis Pipeline => ', await redis.pipeline( - // utils.get.caseActivities(caseIds, activity, now))); - const result = await redis.pipeline( utils.get.caseActivities(caseIds, activity, now) ).exec(); - console.log(`raw result for activity '${activity}' =>`, result); - redis.logPipelineFailures(result, failureMessage); cb(result); - // console.log(`result for activity '${activity}' =>`, result); uniqueUserIds = utils.extractUniqueUserIds(result, uniqueUserIds); - // console.log(`uniqueUserIds after extracting for activity '${activity}' =>`, uniqueUserIds); }; // Set up the promises fore view and edit. @@ -130,24 +114,13 @@ module.exports = (config, redis) => { await Promise.all([caseViewersPromise, caseEditorsPromise]); // Get all the user details for both viewers and editors. - console.log('uniqueUserIds => ', uniqueUserIds); - const userDetails = await getUserDetails(uniqueUserIds); - console.log('case viewers => ', caseViewers); - console.log('case editors => ', caseEditors); - - console.log('case ids => ', caseIds); - // console.log('case viewers =>', caseViewers); - // console.log('case editors =>', caseEditors); - console.log('user details => ', userDetails); - // Now produce a response for every case requested. return caseIds.map((caseId, index) => { const cv = caseViewers[index][1]; const ce = caseEditors[index][1]; const viewers = cv ? cv.map((v) => userDetails[v]) : []; - // console.log('viewers for caseId ', caseId, ' => ', viewers); const editors = ce ? ce.map((e) => userDetails[e]) : []; return { caseId, diff --git a/app/socket/service/handlers.js b/app/socket/service/handlers.js index 74b2f95c..97052626 100644 --- a/app/socket/service/handlers.js +++ b/app/socket/service/handlers.js @@ -25,29 +25,8 @@ module.exports = (activityService, socketServer) => { * notified about. */ async function notify(caseId) { - // console.log('notifying change for caseId: ', caseId); const cs = await activityService.getActivityForCases([caseId]); console.log('notifying case activity: ', JSON.stringify(cs, null, 2)); - // Temp hack to get around lack of user details in redis. - // cs = [ - // { - // caseId: '1712141847061149', - // viewers: [ - // { - // id: '1712141847061149', - // forename: 'SSC', - // surname: 'Super User' - // } - // ], - // unknownViewers: 0, - // editors: [ { - // id: '1712141847061149', - // forename: 'SSC', - // surname: 'Super User' - // }], - // unknownEditors: 0 - // } - // ]; socketServer.to(keys.case.base(caseId)).emit('activity', cs); } diff --git a/app/socket/utils/get.js b/app/socket/utils/get.js index 01d6f90d..6c52f817 100644 --- a/app/socket/utils/get.js +++ b/app/socket/utils/get.js @@ -3,11 +3,9 @@ const keys = require('../redis/keys'); const get = { caseActivities: (caseIds, activity, now) => { console.log(`getting case activities for activity '${activity}' and caseIds: `, caseIds); - if (Array.isArray(caseIds) && ['view', 'edit'].indexOf(activity) > -1) { return caseIds.filter((id) => !!id).map((id) => { return ['zrangebyscore', keys.case[activity](id), now, '+inf']; - // return ['zrangebyscore', keys.case[activity](id), '-inf', '+inf']; }); } return []; From ce55c1d024b3f8e35c4dc36f9b0b0f6d03a532c2 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Fri, 17 Oct 2025 14:16:10 +0100 Subject: [PATCH 071/113] code cleanup --- app/socket/service/activity-service.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/socket/service/activity-service.js b/app/socket/service/activity-service.js index c5fdcb49..a84a8b64 100644 --- a/app/socket/service/activity-service.js +++ b/app/socket/service/activity-service.js @@ -87,7 +87,6 @@ module.exports = (config, redis) => { if (!Array.isArray(caseIds) || caseIds.length === 0) { return []; } - let uniqueUserIds = []; let caseViewers = []; let caseEditors = []; From c9d873d625180559d7d38233e321ec9222f8dc98 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Mon, 17 Nov 2025 17:27:11 +0000 Subject: [PATCH 072/113] log update --- app/user/roles-based-authorizer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/user/roles-based-authorizer.js b/app/user/roles-based-authorizer.js index 9bff1832..311c851d 100644 --- a/app/user/roles-based-authorizer.js +++ b/app/user/roles-based-authorizer.js @@ -10,6 +10,7 @@ const blacklist = config.get('security.auth_blacklist') const isUserAuthorized = (request, user) => { const authorized = authorizer.isUserAuthorized(user.roles, whitelist, blacklist); debug(`user roles authorized: ${authorized}`); + console.log(`user roles authorized: ${authorized}`); return authorized; }; From 7268e03e10821217518e00f85dda63180f22d5b4 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Tue, 18 Nov 2025 09:19:11 +0000 Subject: [PATCH 073/113] vulnerability fix --- yarn-audit-known-issues | 1 + 1 file changed, 1 insertion(+) diff --git a/yarn-audit-known-issues b/yarn-audit-known-issues index e693a979..630c3005 100644 --- a/yarn-audit-known-issues +++ b/yarn-audit-known-issues @@ -1,2 +1,3 @@ +{"value":"js-yaml","children":{"ID":1109801,"Issue":"js-yaml has prototype pollution in merge (<<)","URL":"https://github.com/advisories/GHSA-mh29-5h37-fv8m","Severity":"moderate","Vulnerable Versions":"<3.14.2","Tree Versions":["3.14.1"],"Dependents":["@hmcts/nodejs-healthcheck@npm:1.8.6"]}} {"value":"lodash.clone","children":{"ID":"lodash.clone (deprecation)","Issue":"This package is deprecated. Use structuredClone instead.","Severity":"moderate","Vulnerable Versions":"4.5.0","Tree Versions":["4.5.0"],"Dependents":["ioredis@npm:3.2.2"]}} {"value":"lodash.pick","children":{"ID":"lodash.pick (deprecation)","Issue":"This package is deprecated. Use destructuring assignment syntax instead.","Severity":"moderate","Vulnerable Versions":"3.1.0","Tree Versions":["3.1.0"],"Dependents":["ioredis@npm:3.2.2"]}} From a7a1789a9ad3b86717b02fca21afa35dd5d56aba Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Fri, 21 Nov 2025 12:07:24 +0000 Subject: [PATCH 074/113] stop viewing case for user added --- app/socket/router/index.js | 6 +++ app/socket/service/activity-service.js | 54 +++++++++++++++++++++++--- app/socket/service/handlers.js | 11 +++++- 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/app/socket/router/index.js b/app/socket/router/index.js index 609eeb74..f9557635 100644 --- a/app/socket/router/index.js +++ b/app/socket/router/index.js @@ -47,6 +47,12 @@ const router = { handlers.watch(socket, ctx.request.caseIds); next(); }); + iorouter.on('stop', (socket, ctx, next) => { + const user = router.getUser(socket.id); + utils.log(socket, `${ctx.request.caseId} (${user.name})`, 'stop'); + handlers.stop(socket, ctx.request.caseId, user, 'stop'); + next(); + }); // On client connection, attach the router and track the socket. io.on('connection', (socket) => { diff --git a/app/socket/service/activity-service.js b/app/socket/service/activity-service.js index a84a8b64..c168dd0b 100644 --- a/app/socket/service/activity-service.js +++ b/app/socket/service/activity-service.js @@ -37,19 +37,51 @@ module.exports = (config, redis) => { return {}; }; - const doRemoveSocketActivity = async (socketId) => { + // const doRemoveSocketActivity = async (socketId) => { + // // First make sure we actually have some activity to remove. + // const activity = await getSocketActivity(socketId); + // if (activity) { + // await redis.pipeline([ + // utils.remove.userActivity(activity), + // utils.remove.socketEntry(socketId) + // ]).exec(); + // return activity.caseId; + // } + // return null; + // }; + + // const doRemoveUserActivity = async (socketId) => { + // // First make sure we actually have some activity to remove. + // const activity = await getSocketActivity(socketId); + + // if (activity) { + // await redis.pipeline([ + // utils.remove.userActivity(activity), + // ]).exec(); + // return activity.caseId; + // } + // return null; + // }; + + const doRemoveActivity = async (socketId, removeSocketEntry = false) => { // First make sure we actually have some activity to remove. const activity = await getSocketActivity(socketId); if (activity) { - await redis.pipeline([ - utils.remove.userActivity(activity), - utils.remove.socketEntry(socketId) - ]).exec(); + const pipeline = [ utils.remove.userActivity(activity) ]; + if (removeSocketEntry) { + pipeline.push(utils.remove.socketEntry(socketId)); + } + await redis.pipeline(pipeline).exec(); return activity.caseId; } return null; }; + // Backwards-compatible wrappers + const doRemoveSocketActivity = async (socketId) => doRemoveActivity(socketId, true); + const doRemoveUserActivity = async (socketId) => doRemoveActivity(socketId, false); + + const removeSocketActivity = async (socketId) => { const removedCaseId = await doRemoveSocketActivity(socketId); if (removedCaseId) { @@ -57,6 +89,15 @@ module.exports = (config, redis) => { } }; + const removeUserActivity = async (socketId) => { + const removedCaseId = await doRemoveUserActivity(socketId); + if (removedCaseId) { + notifyChange(removedCaseId); + } + }; + + + const doAddActivity = async (caseId, user, socketId, activity) => { // Now store this activity. const activityKey = keys.case[activity](caseId); @@ -139,6 +180,7 @@ module.exports = (config, redis) => { notifyChange, redis, removeSocketActivity, - ttl + ttl, + removeUserActivity }; }; diff --git a/app/socket/service/handlers.js b/app/socket/service/handlers.js index 97052626..f119f882 100644 --- a/app/socket/service/handlers.js +++ b/app/socket/service/handlers.js @@ -59,13 +59,22 @@ module.exports = (activityService, socketServer) => { socket.emit('activity', cs); } + async function stop(socket, caseId) { + // Stop watching the current cases. + console.log('Stop watching cases to ', caseId, ' for socket ', socket.id); + + // Remove the activity for this socket. + await activityService.removeUserActivity(socket.id); + } + return { activityService, addActivity, notify, removeSocketActivity, socketServer, - watch + watch, + stop }; }; From 998ad83f4b801327967b116698a668af0c84cafd Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Fri, 21 Nov 2025 14:30:10 +0000 Subject: [PATCH 075/113] lint fix --- app/socket/service/activity-service.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/socket/service/activity-service.js b/app/socket/service/activity-service.js index c168dd0b..ea496d11 100644 --- a/app/socket/service/activity-service.js +++ b/app/socket/service/activity-service.js @@ -63,11 +63,11 @@ module.exports = (config, redis) => { // return null; // }; - const doRemoveActivity = async (socketId, removeSocketEntry = false) => { + const doRemoveActivity = async (socketId, removeSocketEntry = false) => { // First make sure we actually have some activity to remove. const activity = await getSocketActivity(socketId); if (activity) { - const pipeline = [ utils.remove.userActivity(activity) ]; + const pipeline = [utils.remove.userActivity(activity)]; if (removeSocketEntry) { pipeline.push(utils.remove.socketEntry(socketId)); } @@ -81,7 +81,6 @@ module.exports = (config, redis) => { const doRemoveSocketActivity = async (socketId) => doRemoveActivity(socketId, true); const doRemoveUserActivity = async (socketId) => doRemoveActivity(socketId, false); - const removeSocketActivity = async (socketId) => { const removedCaseId = await doRemoveSocketActivity(socketId); if (removedCaseId) { @@ -96,8 +95,6 @@ module.exports = (config, redis) => { } }; - - const doAddActivity = async (caseId, user, socketId, activity) => { // Now store this activity. const activityKey = keys.case[activity](caseId); From 735a341ae400c37a685096aaf6521960a1f5ece3 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Mon, 1 Dec 2025 18:00:37 +0000 Subject: [PATCH 076/113] socket handshake checks added --- app/socket/router/index.js | 15 ++++++++++++++- charts/ccd-case-activity-api/values.yaml | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/socket/router/index.js b/app/socket/router/index.js index f9557635..9e030c69 100644 --- a/app/socket/router/index.js +++ b/app/socket/router/index.js @@ -57,7 +57,20 @@ const router = { // On client connection, attach the router and track the socket. io.on('connection', (socket) => { router.addConnection(socket); - router.addUser(socket.id, JSON.parse(socket.handshake.query.user)); + let userObj = null; + if ( + socket && + socket.handshake && + socket.handshake.query && + socket.handshake.query.user + ) { + try { + userObj = JSON.parse(socket.handshake.query.user); + } catch (e) { + utils.log(socket, '', 'Failed to parse user from handshake query', console.error, e); + } + } + router.addUser(socket.id, userObj); utils.log(socket, '', `connected (${router.getConnections().length} total)`); // eslint-disable-next-line no-console utils.log(socket, '', `connected (${router.getConnections().length} total)`, console.log, Date.now()); diff --git a/charts/ccd-case-activity-api/values.yaml b/charts/ccd-case-activity-api/values.yaml index 385b469c..cdea8d71 100644 --- a/charts/ccd-case-activity-api/values.yaml +++ b/charts/ccd-case-activity-api/values.yaml @@ -37,7 +37,7 @@ nodejs: REDIS_PORT: 6380 REDIS_SSL_ENABLED: true REDIS_KEY_PREFIX: 'activity:' - REDIS_ACTIVITY_TTL: 5 + REDIS_ACTIVITY_TTL: 30 REDIS_USER_DETAILS_TTL: 6000 APP_REQUEST_TIMEOUT: 5 APP_STORE_CLEANUP_CRONTAB: '* * * * *' From dfdba242bd77de1fc084ee114784150519daa2e0 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Mon, 1 Dec 2025 18:15:00 +0000 Subject: [PATCH 077/113] yarn fix --- app/socket/router/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/socket/router/index.js b/app/socket/router/index.js index 9e030c69..c69ef976 100644 --- a/app/socket/router/index.js +++ b/app/socket/router/index.js @@ -59,10 +59,10 @@ const router = { router.addConnection(socket); let userObj = null; if ( - socket && - socket.handshake && - socket.handshake.query && - socket.handshake.query.user + socket + && socket.handshake + && socket.handshake.query + && socket.handshake.query.user ) { try { userObj = JSON.parse(socket.handshake.query.user); From e890654e3adffa9fe47410481e660d21536c1e98 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Mon, 1 Dec 2025 18:19:13 +0000 Subject: [PATCH 078/113] audit suppression added --- yarn-audit-known-issues | 1 + 1 file changed, 1 insertion(+) diff --git a/yarn-audit-known-issues b/yarn-audit-known-issues index 630c3005..3d68be2d 100644 --- a/yarn-audit-known-issues +++ b/yarn-audit-known-issues @@ -1,3 +1,4 @@ +{"value":"body-parser","children":{"ID":1110858,"Issue":"body-parser is vulnerable to denial of service when url encoding is used","URL":"https://github.com/advisories/GHSA-wqch-xfxh-vrr4","Severity":"moderate","Vulnerable Versions":">=2.2.0 <2.2.1","Tree Versions":["2.2.0"],"Dependents":["express@npm:5.1.0"]}} {"value":"js-yaml","children":{"ID":1109801,"Issue":"js-yaml has prototype pollution in merge (<<)","URL":"https://github.com/advisories/GHSA-mh29-5h37-fv8m","Severity":"moderate","Vulnerable Versions":"<3.14.2","Tree Versions":["3.14.1"],"Dependents":["@hmcts/nodejs-healthcheck@npm:1.8.6"]}} {"value":"lodash.clone","children":{"ID":"lodash.clone (deprecation)","Issue":"This package is deprecated. Use structuredClone instead.","Severity":"moderate","Vulnerable Versions":"4.5.0","Tree Versions":["4.5.0"],"Dependents":["ioredis@npm:3.2.2"]}} {"value":"lodash.pick","children":{"ID":"lodash.pick (deprecation)","Issue":"This package is deprecated. Use destructuring assignment syntax instead.","Severity":"moderate","Vulnerable Versions":"3.1.0","Tree Versions":["3.1.0"],"Dependents":["ioredis@npm:3.2.2"]}} From d5c92f4ef35b12193e31ac2bb301e1dad8324df6 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Wed, 10 Dec 2025 12:48:49 +0000 Subject: [PATCH 079/113] loggers added for socket check demo --- app.js | 4 ++++ app/socket/router/index.js | 3 +++ 2 files changed, 7 insertions(+) diff --git a/app.js b/app.js index 26a0d565..67fa2fd4 100644 --- a/app.js +++ b/app.js @@ -41,12 +41,16 @@ if (config.util.getEnv('NODE_ENV') === 'test') { debug(`starting application with environment: ${config.util.getEnv('NODE_ENV')}`); +logger.warn(`CCD Case Activity API - Starting application in ${config.util.getEnv('NODE_ENV')} mode`); + + app.use(corsHandler); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(express.text()); app.use(authCheckerUserOnlyFilter); + app.use('/', activity); // catch 404 and forward to error handler diff --git a/app/socket/router/index.js b/app/socket/router/index.js index c69ef976..fac85f69 100644 --- a/app/socket/router/index.js +++ b/app/socket/router/index.js @@ -68,10 +68,13 @@ const router = { userObj = JSON.parse(socket.handshake.query.user); } catch (e) { utils.log(socket, '', 'Failed to parse user from handshake query', console.error, e); + logger.warn(`Failed to parse user from handshake query: ${e.message}`); } } router.addUser(socket.id, userObj); utils.log(socket, '', `connected (${router.getConnections().length} total)`); + logger.warn(`Socket connected: ${socket.id} for user ${userObj ? userObj.name : 'unknown'}`); + // eslint-disable-next-line no-console utils.log(socket, '', `connected (${router.getConnections().length} total)`, console.log, Date.now()); socket.use((packet, next) => { From 3084f0e12def26677bbb79bb6135dec52aa0ec30 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Wed, 10 Dec 2025 12:57:27 +0000 Subject: [PATCH 080/113] lint fix --- app.js | 2 -- app/socket/router/index.js | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index 67fa2fd4..297a2f55 100644 --- a/app.js +++ b/app.js @@ -43,14 +43,12 @@ debug(`starting application with environment: ${config.util.getEnv('NODE_ENV')}` logger.warn(`CCD Case Activity API - Starting application in ${config.util.getEnv('NODE_ENV')} mode`); - app.use(corsHandler); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(express.text()); app.use(authCheckerUserOnlyFilter); - app.use('/', activity); // catch 404 and forward to error handler diff --git a/app/socket/router/index.js b/app/socket/router/index.js index fac85f69..f4fb8aae 100644 --- a/app/socket/router/index.js +++ b/app/socket/router/index.js @@ -1,5 +1,7 @@ +const { Logger } = require('@hmcts/nodejs-logging'); const utils = require('../utils'); +const logger = Logger.getLogger('index-socket-router'); const users = {}; const connections = []; const router = { @@ -74,7 +76,7 @@ const router = { router.addUser(socket.id, userObj); utils.log(socket, '', `connected (${router.getConnections().length} total)`); logger.warn(`Socket connected: ${socket.id} for user ${userObj ? userObj.name : 'unknown'}`); - + // eslint-disable-next-line no-console utils.log(socket, '', `connected (${router.getConnections().length} total)`, console.log, Date.now()); socket.use((packet, next) => { From 8ce74e1e0e7cc0d9d0b44c5ad2a5e9e0aad51e02 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Wed, 10 Dec 2025 13:02:31 +0000 Subject: [PATCH 081/113] yarn test fix --- app.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app.js b/app.js index 297a2f55..26a0d565 100644 --- a/app.js +++ b/app.js @@ -41,8 +41,6 @@ if (config.util.getEnv('NODE_ENV') === 'test') { debug(`starting application with environment: ${config.util.getEnv('NODE_ENV')}`); -logger.warn(`CCD Case Activity API - Starting application in ${config.util.getEnv('NODE_ENV')} mode`); - app.use(corsHandler); app.use(express.json()); app.use(express.urlencoded({ extended: false })); From 1db8ff2b22af182423cca407c61f7bbabc6006d6 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Wed, 10 Dec 2025 13:52:50 +0000 Subject: [PATCH 082/113] more log added --- app/user/auth-checker-user-only-filter.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/user/auth-checker-user-only-filter.js b/app/user/auth-checker-user-only-filter.js index 14368c7e..1c52a8ed 100644 --- a/app/user/auth-checker-user-only-filter.js +++ b/app/user/auth-checker-user-only-filter.js @@ -29,6 +29,9 @@ const mapFetchErrors = (error, next) => { const authCheckerUserOnlyFilter = (req, res, next) => { req.authentication = {}; + console.log('Authenticating user'); + logger.warn('Authenticating user'); + userRequestAuthorizer .authorise(req) .then((user) => { From e0e6fb10e5518bd87b20b384c7db1231b40a1bb3 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Wed, 10 Dec 2025 14:12:44 +0000 Subject: [PATCH 083/113] more logs added --- app/socket/router/index.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/socket/router/index.js b/app/socket/router/index.js index f4fb8aae..343d501d 100644 --- a/app/socket/router/index.js +++ b/app/socket/router/index.js @@ -58,6 +58,9 @@ const router = { // On client connection, attach the router and track the socket. io.on('connection', (socket) => { + console.log(`Socket connected: ${socket.id}`); + logger.warn(`Socket connected: ${socket.id}`); + router.addConnection(socket); let userObj = null; if ( @@ -71,6 +74,7 @@ const router = { } catch (e) { utils.log(socket, '', 'Failed to parse user from handshake query', console.error, e); logger.warn(`Failed to parse user from handshake query: ${e.message}`); + console.log(`Failed to parse user from handshake query: ${e.message}`); } } router.addUser(socket.id, userObj); @@ -84,6 +88,9 @@ const router = { }); // When the socket disconnects, do an appropriate teardown. socket.on('disconnect', () => { + console.log(`Socket disconnected: ${socket.id}`); + logger.warn(`Socket disconnected: ${socket.id}`); + utils.log(socket, '', `disconnected (${router.getConnections().length - 1} total)`); // eslint-disable-next-line no-console utils.log(socket, '', `disconnected (${router.getConnections().length - 1} total)`, console.log, Date.now()); From e6b83da6468c34510fdaafb57ebf6a9dd903791e Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Wed, 10 Dec 2025 14:18:35 +0000 Subject: [PATCH 084/113] lint fix --- app/socket/router/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/socket/router/index.js b/app/socket/router/index.js index 343d501d..f6510804 100644 --- a/app/socket/router/index.js +++ b/app/socket/router/index.js @@ -60,7 +60,7 @@ const router = { io.on('connection', (socket) => { console.log(`Socket connected: ${socket.id}`); logger.warn(`Socket connected: ${socket.id}`); - + router.addConnection(socket); let userObj = null; if ( From 32e6e47033489e43064e1109342ab69f4f882a57 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Wed, 10 Dec 2025 14:44:11 +0000 Subject: [PATCH 085/113] more logs added --- app.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app.js b/app.js index 26a0d565..fdbdeb0b 100644 --- a/app.js +++ b/app.js @@ -40,11 +40,15 @@ if (config.util.getEnv('NODE_ENV') === 'test') { } debug(`starting application with environment: ${config.util.getEnv('NODE_ENV')}`); +console.log(`starting application with environment: ${config.util.getEnv('NODE_ENV')}`); app.use(corsHandler); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(express.text()); + +console.log('Applying auth checker user only filter'); + app.use(authCheckerUserOnlyFilter); app.use('/', activity); From 84e1965c21ee9a604b6af89e5c47098eee9d2668 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Wed, 10 Dec 2025 15:05:36 +0000 Subject: [PATCH 086/113] more logs added --- app/util/utils.js | 7 +++++++ server.js | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/app/util/utils.js b/app/util/utils.js index 3ef5572e..20b3fcc6 100644 --- a/app/util/utils.js +++ b/app/util/utils.js @@ -30,8 +30,13 @@ exports.onServerError = (port, logTo, exitRoute) => { throw error; } + console.log(`Server error on port ${port}: ${error.message}`); + const bind = typeof port === 'string' ? `Pipe ${port}` : `Port ${port}`; + console.log(`Handling server error for ${bind}`); + console.log(`Error code: ${error.code}`); + // Handle specific listen errors with friendly messages. switch (error.code) { case 'EACCES': @@ -53,8 +58,10 @@ exports.onServerError = (port, logTo, exitRoute) => { */ exports.onListening = (server, logTo) => { return () => { + console.log('Server listening event triggered'); const addr = server.address(); const bind = typeof addr === 'string' ? `pipe ${addr}` : `port ${addr.port}`; logTo(`Listening on ${bind}`); + console.log(`Listening on ${bind}`); }; }; diff --git a/server.js b/server.js index 9e9e676e..49a35737 100755 --- a/server.js +++ b/server.js @@ -40,6 +40,16 @@ require('./app/socket')(server, redis); /** * Listen on provided port, on all network interfaces. */ + +console.log(`Listening on port ${port}`); server.listen(port); + +console.log(`Server started on port ${port}`); + +console.log('Registering onServerError handler'); + server.on('error', onServerError(port, console.error, process.exit)); + +console.log('Registering onListening handler'); + server.on('listening', onListening(server, debug)); From 869c01622b33296ee79c5544c6a6a588ba135040 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Wed, 10 Dec 2025 15:57:43 +0000 Subject: [PATCH 087/113] more logs added socket issue --- app/socket/index.js | 10 ++++++++++ app/socket/service/activity-service.js | 6 ++++++ app/socket/service/handlers.js | 1 + 3 files changed, 17 insertions(+) diff --git a/app/socket/index.js b/app/socket/index.js index c62eb394..f82decf8 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -19,7 +19,10 @@ const router = require('./router'); * * Some sort of auth / get the credentials when the user connects. */ module.exports = (server, redis) => { + console.log('Setting up socket server'); const activityService = ActivityService(config, redis); + + console.log('Creating socket server'); const socketServer = SocketIO(server, { allowEIO3: true, cors: { @@ -28,10 +31,17 @@ module.exports = (server, redis) => { credentials: true }, }); + + console.log('Setting up socket handlers and router'); const handlers = Handlers(activityService, socketServer); const watcher = redis.duplicate(); + + console.log('Initializing pubsub for socket server'); pubSub.init(watcher, handlers.notify); + + console.log('Initializing router for socket server'); router.init(socketServer, new IORouter(), handlers); + console.log('Socket server setup complete'); return { socketServer, activityService, handlers }; }; diff --git a/app/socket/service/activity-service.js b/app/socket/service/activity-service.js index ea496d11..ca032421 100644 --- a/app/socket/service/activity-service.js +++ b/app/socket/service/activity-service.js @@ -8,21 +8,26 @@ module.exports = (config, redis) => { }; const notifyChange = (caseId) => { + console.log('Notifying change for caseId ', caseId); if (caseId) { redis.publish(keys.case.base(caseId), Date.now().toString()); } }; const getSocketActivity = async (socketId) => { + console.log('Getting socket activity for socketId ', socketId); if (socketId) { const key = keys.socket(socketId); + console.log('Socket activity key: ', key); return JSON.parse(await redis.get(key)); } return null; }; const getUserDetails = async (userIds) => { + console.log('Getting user details for userIds ', userIds); if (Array.isArray(userIds) && userIds.length > 0) { + console.log('Fetching user details from redis'); // Get hold of the details. const details = await redis.pipeline(utils.get.users(userIds)).exec(); // Now turn them into a map. @@ -64,6 +69,7 @@ module.exports = (config, redis) => { // }; const doRemoveActivity = async (socketId, removeSocketEntry = false) => { + console.log('Removing activity for socketId ', socketId, ' removeSocketEntry=', removeSocketEntry); // First make sure we actually have some activity to remove. const activity = await getSocketActivity(socketId); if (activity) { diff --git a/app/socket/service/handlers.js b/app/socket/service/handlers.js index f119f882..bcbed8a7 100644 --- a/app/socket/service/handlers.js +++ b/app/socket/service/handlers.js @@ -36,6 +36,7 @@ module.exports = (activityService, socketServer) => { * @param {*} socketId The id of the socket to remove activity for. */ async function removeSocketActivity(socketId) { + console.log('Removing socket activity for socketId ', socketId); await activityService.removeSocketActivity(socketId); } From 416b170d845704bc413bfc10edf350948141cd85 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Wed, 10 Dec 2025 16:37:02 +0000 Subject: [PATCH 088/113] websocket change --- app/socket/index.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/socket/index.js b/app/socket/index.js index f82decf8..bcf61cfd 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -23,8 +23,18 @@ module.exports = (server, redis) => { const activityService = ActivityService(config, redis); console.log('Creating socket server'); + // const socketServer = SocketIO(server, { + // allowEIO3: true, + // cors: { + // origin: '*', + // methods: ['GET', 'POST'], + // credentials: true + // }, + // }); + const socketServer = SocketIO(server, { allowEIO3: true, + transports: ['websocket', 'polling'], cors: { origin: '*', methods: ['GET', 'POST'], From 1463210358e76f2c25296f63dc9ff4c9d4c549e9 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 11 Dec 2025 09:41:09 +0000 Subject: [PATCH 089/113] temp commented auth check --- app.js | 15 ++++++++++++--- app/user/auth-checker-user-only-filter.js | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index fdbdeb0b..d98ae39c 100644 --- a/app.js +++ b/app.js @@ -3,12 +3,17 @@ const express = require('express'); const logger = require('morgan'); const config = require('config'); const debug = require('debug')('ccd-case-activity-api:app'); + +// Temporary commenting out config import to avoid lint error +// const c = require('config'); const enableAppInsights = require('./app/app-insights/app-insights'); enableAppInsights(); const storeCleanupJob = require('./app/job/store-cleanup-job'); -const authCheckerUserOnlyFilter = require('./app/user/auth-checker-user-only-filter'); + +// Temporary commenting out auth checker import to avoid lint error +// const authCheckerUserOnlyFilter = require('./app/user/auth-checker-user-only-filter'); const corsHandler = require('./app/security/cors'); const redis = require('./app/redis/redis-client'); @@ -48,13 +53,14 @@ app.use(express.urlencoded({ extended: false })); app.use(express.text()); console.log('Applying auth checker user only filter'); - -app.use(authCheckerUserOnlyFilter); +console.log('NOTE: To disable authentication temporarily, comment out the auth checker middleware in app.js'); +// app.use(authCheckerUserOnlyFilter); app.use('/', activity); // catch 404 and forward to error handler app.use((req, res, next) => { + console.log(`404 Not Found for request: ${req.method} ${req.originalUrl}`); const err = new Error('Not Found'); err.status = 404; next(err); @@ -65,11 +71,14 @@ app.use((req, res, next) => { /* eslint-disable no-unused-vars */ app.use((err, req, res, next) => { debug(`Error processing request: ${err}`); + console.log(`Error processing request: ${err}`); // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; + console.log(`Returning error response: ${err.status || 500} - ${err.message}`); + res.status(err.status || 500); res.json({ message: err.message, diff --git a/app/user/auth-checker-user-only-filter.js b/app/user/auth-checker-user-only-filter.js index 1c52a8ed..65318a67 100644 --- a/app/user/auth-checker-user-only-filter.js +++ b/app/user/auth-checker-user-only-filter.js @@ -35,6 +35,7 @@ const authCheckerUserOnlyFilter = (req, res, next) => { userRequestAuthorizer .authorise(req) .then((user) => { + console.log(`User authenticated: ${user.id}`); req.authentication.user = user; }) .then(() => next()) From e3fc50cfca56f70dde63d998b7c9653312d8265e Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 11 Dec 2025 10:13:00 +0000 Subject: [PATCH 090/113] auth error handling temp commented --- app.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/app.js b/app.js index d98ae39c..b13ea23a 100644 --- a/app.js +++ b/app.js @@ -69,20 +69,23 @@ app.use((req, res, next) => { // error handler // FIXME `next` MUST be kept for error handling, even though it causes linting to fail /* eslint-disable no-unused-vars */ -app.use((err, req, res, next) => { - debug(`Error processing request: ${err}`); - console.log(`Error processing request: ${err}`); - // set locals, only providing error in development - res.locals.message = err.message; - res.locals.error = req.app.get('env') === 'development' ? err : {}; +// Temporarily commenting out error handler to check socket functionality - console.log(`Returning error response: ${err.status || 500} - ${err.message}`); +// app.use((err, req, res, next) => { +// debug(`Error processing request: ${err}`); +// console.log(`Error processing request: ${err}`); - res.status(err.status || 500); - res.json({ - message: err.message, - }); -}); +// // set locals, only providing error in development +// res.locals.message = err.message; +// res.locals.error = req.app.get('env') === 'development' ? err : {}; + +// console.log(`Returning error response: ${err.status || 500} - ${err.message}`); + +// res.status(err.status || 500); +// res.json({ +// message: err.message, +// }); +// }); module.exports = app; From b71e5384b6837e97458817b8b613960043631831 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 11 Dec 2025 10:51:00 +0000 Subject: [PATCH 091/113] added error handling back --- app.js | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/app.js b/app.js index b13ea23a..d98ae39c 100644 --- a/app.js +++ b/app.js @@ -69,23 +69,20 @@ app.use((req, res, next) => { // error handler // FIXME `next` MUST be kept for error handling, even though it causes linting to fail /* eslint-disable no-unused-vars */ +app.use((err, req, res, next) => { + debug(`Error processing request: ${err}`); + console.log(`Error processing request: ${err}`); -// Temporarily commenting out error handler to check socket functionality + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; -// app.use((err, req, res, next) => { -// debug(`Error processing request: ${err}`); -// console.log(`Error processing request: ${err}`); + console.log(`Returning error response: ${err.status || 500} - ${err.message}`); -// // set locals, only providing error in development -// res.locals.message = err.message; -// res.locals.error = req.app.get('env') === 'development' ? err : {}; - -// console.log(`Returning error response: ${err.status || 500} - ${err.message}`); - -// res.status(err.status || 500); -// res.json({ -// message: err.message, -// }); -// }); + res.status(err.status || 500); + res.json({ + message: err.message, + }); +}); module.exports = app; From f332519c49d474fa0da29661d9e28dabcaf88303 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 11 Dec 2025 11:54:46 +0000 Subject: [PATCH 092/113] auth added again --- app.js | 10 +++------- app/routes/get-activities.js | 5 +++++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app.js b/app.js index d98ae39c..6a63316e 100644 --- a/app.js +++ b/app.js @@ -3,17 +3,13 @@ const express = require('express'); const logger = require('morgan'); const config = require('config'); const debug = require('debug')('ccd-case-activity-api:app'); - -// Temporary commenting out config import to avoid lint error -// const c = require('config'); +const c = require('config'); const enableAppInsights = require('./app/app-insights/app-insights'); enableAppInsights(); const storeCleanupJob = require('./app/job/store-cleanup-job'); - -// Temporary commenting out auth checker import to avoid lint error -// const authCheckerUserOnlyFilter = require('./app/user/auth-checker-user-only-filter'); +const authCheckerUserOnlyFilter = require('./app/user/auth-checker-user-only-filter'); const corsHandler = require('./app/security/cors'); const redis = require('./app/redis/redis-client'); @@ -54,7 +50,7 @@ app.use(express.text()); console.log('Applying auth checker user only filter'); console.log('NOTE: To disable authentication temporarily, comment out the auth checker middleware in app.js'); -// app.use(authCheckerUserOnlyFilter); +app.use(authCheckerUserOnlyFilter); app.use('/', activity); diff --git a/app/routes/get-activities.js b/app/routes/get-activities.js index c195c210..a2df721f 100644 --- a/app/routes/get-activities.js +++ b/app/routes/get-activities.js @@ -4,9 +4,14 @@ const utils = require('../util/utils'); const { ifNotTimedOut } = utils; const getActivities = (activityService) => (req, res, next) => { + + console.log(`GET_ACTIVITIES request received at ${new Date().toISOString()}`); + const caseIds = req.params.caseids.split(','); const { user } = req.authentication; + console.log(`GET_ACTIVITIES request for caseIds: ${caseIds}`); + debug(`GET_ACTIVITIES request for caseIds: ${caseIds}`); activityService.getActivities(caseIds, user) .then((result) => ifNotTimedOut(req, () => { From afbadc78a19832b22a3fce4ec1398a1dbdc89191 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 11 Dec 2025 12:11:20 +0000 Subject: [PATCH 093/113] lint fix --- app.js | 1 - app/routes/get-activities.js | 1 - app/socket/router/index.js | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app.js b/app.js index 6a63316e..7687e549 100644 --- a/app.js +++ b/app.js @@ -49,7 +49,6 @@ app.use(express.urlencoded({ extended: false })); app.use(express.text()); console.log('Applying auth checker user only filter'); -console.log('NOTE: To disable authentication temporarily, comment out the auth checker middleware in app.js'); app.use(authCheckerUserOnlyFilter); app.use('/', activity); diff --git a/app/routes/get-activities.js b/app/routes/get-activities.js index a2df721f..89a90fdf 100644 --- a/app/routes/get-activities.js +++ b/app/routes/get-activities.js @@ -4,7 +4,6 @@ const utils = require('../util/utils'); const { ifNotTimedOut } = utils; const getActivities = (activityService) => (req, res, next) => { - console.log(`GET_ACTIVITIES request received at ${new Date().toISOString()}`); const caseIds = req.params.caseids.split(','); diff --git a/app/socket/router/index.js b/app/socket/router/index.js index f6510804..0391e955 100644 --- a/app/socket/router/index.js +++ b/app/socket/router/index.js @@ -30,6 +30,7 @@ const router = { return [...connections]; }, init: (io, iorouter, handlers) => { + console.log('Initializing socket router'); // Set up routes for each type of message. iorouter.on('view', (socket, ctx, next) => { const user = router.getUser(socket.id); From 7a35ace669d40192454149d48ebe41a5f089f7ae Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 11 Dec 2025 12:19:51 +0000 Subject: [PATCH 094/113] lint fix --- app.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app.js b/app.js index 7687e549..7d8ea55c 100644 --- a/app.js +++ b/app.js @@ -3,7 +3,6 @@ const express = require('express'); const logger = require('morgan'); const config = require('config'); const debug = require('debug')('ccd-case-activity-api:app'); -const c = require('config'); const enableAppInsights = require('./app/app-insights/app-insights'); enableAppInsights(); From 19d54fce8927fa26d8c7ceffcc47f2bedd21cb6a Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 11 Dec 2025 12:19:51 +0000 Subject: [PATCH 095/113] lint fix --- app.js | 2 +- app/routes/activity-route.js | 6 ++++++ app/user/auth-checker-user-only-filter.js | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 7d8ea55c..3350ce4d 100644 --- a/app.js +++ b/app.js @@ -50,6 +50,7 @@ app.use(express.text()); console.log('Applying auth checker user only filter'); app.use(authCheckerUserOnlyFilter); +console.log('Mounting activity route at /'); app.use('/', activity); // catch 404 and forward to error handler @@ -78,5 +79,4 @@ app.use((err, req, res, next) => { message: err.message, }); }); - module.exports = app; diff --git a/app/routes/activity-route.js b/app/routes/activity-route.js index 07bf05f4..634f068e 100644 --- a/app/routes/activity-route.js +++ b/app/routes/activity-route.js @@ -6,6 +6,8 @@ const validateRequest = require('./validate-request'); const router = express.Router(); module.exports = (activityService, config) => { + console.log(`Initializing activity route at ${new Date().toISOString()}`); + const addActivity = require('./add-activity')(activityService); // eslint-disable-line global-require const getActivities = require('./get-activities')(activityService); // eslint-disable-line global-require @@ -15,6 +17,8 @@ module.exports = (activityService, config) => { router.use(timeout(toMillis(config.get('app.requestTimeoutSec')))); + console.log(`Setting request timeout to ${config.get('app.requestTimeoutSec')} seconds`); + router.post('/cases/:caseid/activity', (req, res, next) => { validateRequest(caseIdSchema, req.params.caseid)(req, res, next); }, @@ -25,5 +29,7 @@ module.exports = (activityService, config) => { }, getActivities); + console.log('Activity route initialized => ready to accept requests ', router); + return router; }; diff --git a/app/user/auth-checker-user-only-filter.js b/app/user/auth-checker-user-only-filter.js index 65318a67..3357b4a0 100644 --- a/app/user/auth-checker-user-only-filter.js +++ b/app/user/auth-checker-user-only-filter.js @@ -35,7 +35,8 @@ const authCheckerUserOnlyFilter = (req, res, next) => { userRequestAuthorizer .authorise(req) .then((user) => { - console.log(`User authenticated: ${user.id}`); + console.log(`User authenticated: ${user}`); + console.log(`User authenticated id: ${user.id}`); req.authentication.user = user; }) .then(() => next()) From 8df6e1b3e915e140d5f00df9f6663d212020062e Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 11 Dec 2025 13:51:09 +0000 Subject: [PATCH 096/113] user json log --- app/user/auth-checker-user-only-filter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/user/auth-checker-user-only-filter.js b/app/user/auth-checker-user-only-filter.js index 3357b4a0..29fef61e 100644 --- a/app/user/auth-checker-user-only-filter.js +++ b/app/user/auth-checker-user-only-filter.js @@ -35,7 +35,7 @@ const authCheckerUserOnlyFilter = (req, res, next) => { userRequestAuthorizer .authorise(req) .then((user) => { - console.log(`User authenticated: ${user}`); + console.log(`User authenticated: ${JSON.stringify(user)}`); console.log(`User authenticated id: ${user.id}`); req.authentication.user = user; }) From 24530fa32af348046b293e7ac55ce6ad1d9b5cdd Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 11 Dec 2025 15:23:06 +0000 Subject: [PATCH 097/113] socket server log added --- app/socket/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/socket/index.js b/app/socket/index.js index bcf61cfd..b5276034 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -53,5 +53,7 @@ module.exports = (server, redis) => { router.init(socketServer, new IORouter(), handlers); console.log('Socket server setup complete'); + + console.log('socket Server ', socketServer); return { socketServer, activityService, handlers }; }; From 6b1e3e264c512889677f0bdd5e66f7794decf5e0 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 11 Dec 2025 16:13:45 +0000 Subject: [PATCH 098/113] user auth uid log --- app/user/auth-checker-user-only-filter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/user/auth-checker-user-only-filter.js b/app/user/auth-checker-user-only-filter.js index 29fef61e..72d7176b 100644 --- a/app/user/auth-checker-user-only-filter.js +++ b/app/user/auth-checker-user-only-filter.js @@ -36,7 +36,7 @@ const authCheckerUserOnlyFilter = (req, res, next) => { .authorise(req) .then((user) => { console.log(`User authenticated: ${JSON.stringify(user)}`); - console.log(`User authenticated id: ${user.id}`); + console.log(`User authenticated uid: ${user.uid}`); req.authentication.user = user; }) .then(() => next()) From 361a3eb1cba1498c726905beaa078b2f5b9a4780 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 11 Dec 2025 16:54:27 +0000 Subject: [PATCH 099/113] pubsub log added --- app/socket/index.js | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/app/socket/index.js b/app/socket/index.js index b5276034..def844bf 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -42,18 +42,37 @@ module.exports = (server, redis) => { }, }); + // console.log('Setting up socket handlers and router'); + // const handlers = Handlers(activityService, socketServer); + // const watcher = redis.duplicate(); + + // console.log('Initializing pubsub for socket server'); + // pubSub.init(watcher, handlers.notify); + + // console.log('Initializing router for socket server'); + // router.init(socketServer, new IORouter(), handlers); + + // console.log('Socket server setup complete'); + + // console.log('socket Server ', socketServer); console.log('Setting up socket handlers and router'); const handlers = Handlers(activityService, socketServer); const watcher = redis.duplicate(); - console.log('Initializing pubsub for socket server'); - pubSub.init(watcher, handlers.notify); - console.log('Initializing router for socket server'); router.init(socketServer, new IORouter(), handlers); - console.log('Socket server setup complete'); + console.log('Initializing pubsub for socket server'); + try { + pubSub.init(watcher, handlers.notify); + console.log('PubSub initialized'); + } catch (e) { + console.error('PubSub init failed (sockets still running):', e); + } - console.log('socket Server ', socketServer); + // Optional: log connections to confirm traffic in Azure + socketServer.on('connection', (s) => { + console.log('Socket connected:', s.id, 'transport:', s.conn.transport.name); + }); return { socketServer, activityService, handlers }; }; From d9529dd1b13a471dc3854c9bc6090a9ad55cbbe9 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 11 Dec 2025 17:29:25 +0000 Subject: [PATCH 100/113] socket server cors update --- app/socket/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/socket/index.js b/app/socket/index.js index def844bf..b660b82c 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -36,7 +36,7 @@ module.exports = (server, redis) => { allowEIO3: true, transports: ['websocket', 'polling'], cors: { - origin: '*', + origin: ['https://manage-case-int1.demo.platform.hmcts.net', 'http://localhost:3000'], methods: ['GET', 'POST'], credentials: true }, From 3e38fcb0db6e291e7876ddf043a3042872f2c46d Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 11 Dec 2025 17:52:21 +0000 Subject: [PATCH 101/113] index js update socket --- app/socket/index.js | 138 +++++++++++++++++++++++++++++++++----------- 1 file changed, 103 insertions(+), 35 deletions(-) diff --git a/app/socket/index.js b/app/socket/index.js index b660b82c..d4f8b2a1 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -1,3 +1,4 @@ +// index.js const config = require('config'); const IORouter = require('socket.io-router-middleware'); const SocketIO = require('socket.io'); @@ -8,59 +9,101 @@ const pubSub = require('./redis/pub-sub')(); const router = require('./router'); /** - * Sets up a series of routes for a "socket" endpoint, that - * leverages socket.io and will more than likely use long polling - * instead of websockets as the latter isn't supported by Azure - * Front Door. + * Sets up a series of routes for a "socket" endpoint. * - * The behaviour is the same, though. - * - * TODO: - * * Some sort of auth / get the credentials when the user connects. + * Adds extra logging for CORS and Upgrade headers to help debug Front Door / proxy issues. */ module.exports = (server, redis) => { console.log('Setting up socket server'); const activityService = ActivityService(config, redis); console.log('Creating socket server'); - // const socketServer = SocketIO(server, { - // allowEIO3: true, - // cors: { - // origin: '*', - // methods: ['GET', 'POST'], - // credentials: true - // }, - // }); - - const socketServer = SocketIO(server, { + + // Allowed origins: add any FD/custom domains or local dev hosts you need + const allowedOrigins = new Set([ + 'https://manage-case-int1.demo.platform.hmcts.net', + 'https://manage-case.demo.platform.hmcts.net', + 'http://localhost:3000', + 'http://127.0.0.1:3000' + ]); + + // Socket.IO options with detailed CORS handling + diagnostics + const io = SocketIO(server, { allowEIO3: true, transports: ['websocket', 'polling'], + // Use origin function to accept/reject and log cors: { - origin: ['https://manage-case-int1.demo.platform.hmcts.net', 'http://localhost:3000'], + origin: (origin, callback) => { + // Note: `origin` will be undefined for non-browser clients (e.g. wscat) + if (!origin) { + console.log('[CORS] No Origin header (non-browser client?) — allowing by default.'); + return callback(null, true); + } + + console.log('[CORS] Incoming Origin:', origin); + if (allowedOrigins.has(origin)) { + console.log('[CORS] Origin allowed:', origin); + return callback(null, true); + } + + console.warn('[CORS] Origin rejected:', origin); + // Provide an error to the callback so the client sees rejection + return callback(new Error(`Origin ${origin} not allowed by CORS`), false); + }, methods: ['GET', 'POST'], credentials: true }, - }); - // console.log('Setting up socket handlers and router'); - // const handlers = Handlers(activityService, socketServer); - // const watcher = redis.duplicate(); + /** + * allowRequest gives us raw access to the handshake request and lets us accept/reject + * it before Socket.IO processes it. Useful for logging Upgrade headers and Connection headers. + * + * accept(err, success) -> if success === false the handshake is rejected. + */ + allowRequest: (req, accept) => { + try { + const origin = req.headers.origin; + const upgrade = req.headers.upgrade; + const connectionHeader = req.headers.connection; + const url = req.url || req._parsedUrl && req._parsedUrl.pathname; - // console.log('Initializing pubsub for socket server'); - // pubSub.init(watcher, handlers.notify); + console.log('[ALLOW_REQUEST] handshake url=', url); + console.log('[ALLOW_REQUEST] headers.origin=', origin); + console.log('[ALLOW_REQUEST] headers.upgrade=', upgrade); + console.log('[ALLOW_REQUEST] headers.connection=', connectionHeader); - // console.log('Initializing router for socket server'); - // router.init(socketServer, new IORouter(), handlers); + // Basic validation: ensure allowed origin if provided + if (origin && !allowedOrigins.has(origin)) { + console.warn('[ALLOW_REQUEST] Rejecting handshake due to disallowed origin:', origin); + // reject the request — the client will get a CORS error / handshake failure + return accept(new Error('Origin not allowed'), false); + } - // console.log('Socket server setup complete'); + // Optionally enforce that Upgrade header is present for websocket transport requests + // (Don't strictly require here because Socket.IO may start with polling) + // If you'd like to log transport type: req._query or req.url may contain transport param + + // Accept handshake + return accept(null, true); + } catch (err) { + console.error('[ALLOW_REQUEST] Unexpected error while checking handshake:', err); + return accept(err, false); + } + }, - // console.log('socket Server ', socketServer); + // Tune timeouts (helps reduce disconnects behind proxies) + pingInterval: 25000, + pingTimeout: 60000, + maxHttpBufferSize: 1e6 + }); + + // Router / handlers / pubsub wiring (same as before) with robust logging console.log('Setting up socket handlers and router'); - const handlers = Handlers(activityService, socketServer); + const handlers = Handlers(activityService, io); const watcher = redis.duplicate(); console.log('Initializing router for socket server'); - router.init(socketServer, new IORouter(), handlers); + router.init(io, new IORouter(), handlers); console.log('Initializing pubsub for socket server'); try { @@ -70,9 +113,34 @@ module.exports = (server, redis) => { console.error('PubSub init failed (sockets still running):', e); } - // Optional: log connections to confirm traffic in Azure - socketServer.on('connection', (s) => { - console.log('Socket connected:', s.id, 'transport:', s.conn.transport.name); + // Connection logging to confirm traffic (shows transport used) + io.on('connection', (socket) => { + try { + console.log('[WS] client connected:', socket.id, 'transport:', socket.conn.transport.name); + // If you want more detail on handshake: + if (socket.handshake && socket.handshake.headers) { + console.log('[WS] handshake.headers.origin=', socket.handshake.headers.origin); + console.log('[WS] handshake.headers.upgrade=', socket.handshake.headers.upgrade); + console.log('[WS] handshake.headers.connection=', socket.handshake.headers.connection); + } + + socket.on('error', (err) => { + console.error('[WS] socket error for', socket.id, err); + }); + + socket.on('disconnect', (reason) => { + console.log('[WS] disconnect', socket.id, reason); + }); + } catch (ex) { + console.error('[WS] error in connection handler', ex); + } }); - return { socketServer, activityService, handlers }; + + // Global engine-level error logging (handshake/connect errors) + io.engine.on('connection_error', (err) => { + console.warn('[ENGINE] connection_error:', err && err.message ? err.message : err); + }); + + console.log('Socket server setup complete'); + return { socketServer: io, activityService, handlers }; }; From 406074f84c4bab6d3e418ea90fd983f92ebdc5e5 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 11 Dec 2025 18:01:52 +0000 Subject: [PATCH 102/113] index js update socket --- app/socket/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/socket/index.js b/app/socket/index.js index d4f8b2a1..8925781e 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -62,11 +62,12 @@ module.exports = (server, redis) => { */ allowRequest: (req, accept) => { try { - const origin = req.headers.origin; - const upgrade = req.headers.upgrade; + const { origin } = req.headers; + const { upgrade } = req.headers; const connectionHeader = req.headers.connection; - const url = req.url || req._parsedUrl && req._parsedUrl.pathname; + const { url } = req; + console.log('[ALLOW_REQUEST] handshake req=', req); console.log('[ALLOW_REQUEST] handshake url=', url); console.log('[ALLOW_REQUEST] headers.origin=', origin); console.log('[ALLOW_REQUEST] headers.upgrade=', upgrade); From d90b96a4e34bd3d92bbd8f34be8b8fcc56e37fd6 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 11 Dec 2025 18:03:42 +0000 Subject: [PATCH 103/113] index js update socket --- app/socket/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/socket/index.js b/app/socket/index.js index 8925781e..24f36cd3 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -46,7 +46,7 @@ module.exports = (server, redis) => { return callback(null, true); } - console.warn('[CORS] Origin rejected:', origin); + console.log('[CORS] Origin rejected:', origin); // Provide an error to the callback so the client sees rejection return callback(new Error(`Origin ${origin} not allowed by CORS`), false); }, @@ -75,7 +75,7 @@ module.exports = (server, redis) => { // Basic validation: ensure allowed origin if provided if (origin && !allowedOrigins.has(origin)) { - console.warn('[ALLOW_REQUEST] Rejecting handshake due to disallowed origin:', origin); + console.log('[ALLOW_REQUEST] Rejecting handshake due to disallowed origin:', origin); // reject the request — the client will get a CORS error / handshake failure return accept(new Error('Origin not allowed'), false); } @@ -139,7 +139,7 @@ module.exports = (server, redis) => { // Global engine-level error logging (handshake/connect errors) io.engine.on('connection_error', (err) => { - console.warn('[ENGINE] connection_error:', err && err.message ? err.message : err); + console.log('[ENGINE] connection_error:', err && err.message ? err.message : err); }); console.log('Socket server setup complete'); From 7fca32aa3d35a30d953cf9b22856c69d9c475439 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 11 Dec 2025 18:16:03 +0000 Subject: [PATCH 104/113] test lint fix --- test/spec/app/socket/index.spec.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/test/spec/app/socket/index.spec.js b/test/spec/app/socket/index.spec.js index 34f8b893..5c7c3fb9 100644 --- a/test/spec/app/socket/index.spec.js +++ b/test/spec/app/socket/index.spec.js @@ -1,9 +1,11 @@ const SocketIO = require('socket.io'); const expect = require('chai').expect; +const http = require('http'); const Socket = require('../../../../app/socket'); describe('socket', () => { - const MOCK_SERVER = {}; + let server; + const MOCK_REDIS = { duplicated: false, duplicate: () => { @@ -14,12 +16,23 @@ describe('socket', () => { on: () => {} }; - afterEach(() => { + beforeEach(() => { + // Create a real HTTP server for Socket.IO to attach to + server = http.createServer((req, res) => res.end()); + }); + + afterEach((done) => { MOCK_REDIS.duplicated = false; + // Close the server to avoid handle leaks + if (server && server.close) { + server.close(() => done()); + } else { + done(); + } }); it('should be appropriately initialised', () => { - const socket = Socket(MOCK_SERVER, MOCK_REDIS); + const socket = Socket(server, MOCK_REDIS); expect(socket).not.to.be.undefined; expect(socket.socketServer).to.be.instanceOf(SocketIO.Server); expect(socket.activityService).to.be.an('object'); @@ -28,5 +41,6 @@ describe('socket', () => { expect(socket.handlers.activityService).to.equal(socket.activityService); expect(socket.handlers.socketServer).to.equal(socket.socketServer); expect(MOCK_REDIS.duplicated).to.be.true; - }) -}); \ No newline at end of file + }); +}); +// ...existing code... \ No newline at end of file From f5265c8c4b03be2bec3ec1ac84906946e3807b67 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 11 Dec 2025 19:31:26 +0000 Subject: [PATCH 105/113] origin all cred false temp --- app/socket/index.js | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/app/socket/index.js b/app/socket/index.js index 24f36cd3..1a49232e 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -33,25 +33,26 @@ module.exports = (server, redis) => { transports: ['websocket', 'polling'], // Use origin function to accept/reject and log cors: { - origin: (origin, callback) => { - // Note: `origin` will be undefined for non-browser clients (e.g. wscat) - if (!origin) { - console.log('[CORS] No Origin header (non-browser client?) — allowing by default.'); - return callback(null, true); - } - - console.log('[CORS] Incoming Origin:', origin); - if (allowedOrigins.has(origin)) { - console.log('[CORS] Origin allowed:', origin); - return callback(null, true); - } - - console.log('[CORS] Origin rejected:', origin); - // Provide an error to the callback so the client sees rejection - return callback(new Error(`Origin ${origin} not allowed by CORS`), false); - }, - methods: ['GET', 'POST'], - credentials: true + // origin: (origin, callback) => { + // // Note: `origin` will be undefined for non-browser clients (e.g. wscat) + // if (!origin) { + // console.log('[CORS] No Origin header (non-browser client?) — allowing by default.'); + // return callback(null, true); + // } + + // console.log('[CORS] Incoming Origin:', origin); + // if (allowedOrigins.has(origin)) { + // console.log('[CORS] Origin allowed:', origin); + // return callback(null, true); + // } + + // console.log('[CORS] Origin rejected:', origin); + // // Provide an error to the callback so the client sees rejection + // return callback(new Error(`Origin ${origin} not allowed by CORS`), false); + // }, + origin: '*', + // methods: ['GET', 'POST'], + credentials: false }, /** From b8e3ad2734eaac6488e24e374023bf24b6aca220 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Fri, 12 Dec 2025 09:44:45 +0000 Subject: [PATCH 106/113] dummy commit --- app/socket/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/socket/index.js b/app/socket/index.js index 1a49232e..8d1fb36f 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -52,6 +52,7 @@ module.exports = (server, redis) => { // }, origin: '*', // methods: ['GET', 'POST'], + // Set credentials to false temporarily to allow testing credentials: false }, From 64b3177972beeff567dfa625034da7b05956fb17 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Fri, 12 Dec 2025 11:15:52 +0000 Subject: [PATCH 107/113] index js reverted --- app/socket/index.js | 145 ++++++++--------------------- test/spec/app/socket/index.spec.js | 22 +---- 2 files changed, 41 insertions(+), 126 deletions(-) diff --git a/app/socket/index.js b/app/socket/index.js index 8d1fb36f..b660b82c 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -1,4 +1,3 @@ -// index.js const config = require('config'); const IORouter = require('socket.io-router-middleware'); const SocketIO = require('socket.io'); @@ -9,104 +8,59 @@ const pubSub = require('./redis/pub-sub')(); const router = require('./router'); /** - * Sets up a series of routes for a "socket" endpoint. + * Sets up a series of routes for a "socket" endpoint, that + * leverages socket.io and will more than likely use long polling + * instead of websockets as the latter isn't supported by Azure + * Front Door. * - * Adds extra logging for CORS and Upgrade headers to help debug Front Door / proxy issues. + * The behaviour is the same, though. + * + * TODO: + * * Some sort of auth / get the credentials when the user connects. */ module.exports = (server, redis) => { console.log('Setting up socket server'); const activityService = ActivityService(config, redis); console.log('Creating socket server'); - - // Allowed origins: add any FD/custom domains or local dev hosts you need - const allowedOrigins = new Set([ - 'https://manage-case-int1.demo.platform.hmcts.net', - 'https://manage-case.demo.platform.hmcts.net', - 'http://localhost:3000', - 'http://127.0.0.1:3000' - ]); - - // Socket.IO options with detailed CORS handling + diagnostics - const io = SocketIO(server, { + // const socketServer = SocketIO(server, { + // allowEIO3: true, + // cors: { + // origin: '*', + // methods: ['GET', 'POST'], + // credentials: true + // }, + // }); + + const socketServer = SocketIO(server, { allowEIO3: true, transports: ['websocket', 'polling'], - // Use origin function to accept/reject and log cors: { - // origin: (origin, callback) => { - // // Note: `origin` will be undefined for non-browser clients (e.g. wscat) - // if (!origin) { - // console.log('[CORS] No Origin header (non-browser client?) — allowing by default.'); - // return callback(null, true); - // } - - // console.log('[CORS] Incoming Origin:', origin); - // if (allowedOrigins.has(origin)) { - // console.log('[CORS] Origin allowed:', origin); - // return callback(null, true); - // } - - // console.log('[CORS] Origin rejected:', origin); - // // Provide an error to the callback so the client sees rejection - // return callback(new Error(`Origin ${origin} not allowed by CORS`), false); - // }, - origin: '*', - // methods: ['GET', 'POST'], - // Set credentials to false temporarily to allow testing - credentials: false + origin: ['https://manage-case-int1.demo.platform.hmcts.net', 'http://localhost:3000'], + methods: ['GET', 'POST'], + credentials: true }, + }); - /** - * allowRequest gives us raw access to the handshake request and lets us accept/reject - * it before Socket.IO processes it. Useful for logging Upgrade headers and Connection headers. - * - * accept(err, success) -> if success === false the handshake is rejected. - */ - allowRequest: (req, accept) => { - try { - const { origin } = req.headers; - const { upgrade } = req.headers; - const connectionHeader = req.headers.connection; - const { url } = req; - - console.log('[ALLOW_REQUEST] handshake req=', req); - console.log('[ALLOW_REQUEST] handshake url=', url); - console.log('[ALLOW_REQUEST] headers.origin=', origin); - console.log('[ALLOW_REQUEST] headers.upgrade=', upgrade); - console.log('[ALLOW_REQUEST] headers.connection=', connectionHeader); + // console.log('Setting up socket handlers and router'); + // const handlers = Handlers(activityService, socketServer); + // const watcher = redis.duplicate(); - // Basic validation: ensure allowed origin if provided - if (origin && !allowedOrigins.has(origin)) { - console.log('[ALLOW_REQUEST] Rejecting handshake due to disallowed origin:', origin); - // reject the request — the client will get a CORS error / handshake failure - return accept(new Error('Origin not allowed'), false); - } + // console.log('Initializing pubsub for socket server'); + // pubSub.init(watcher, handlers.notify); - // Optionally enforce that Upgrade header is present for websocket transport requests - // (Don't strictly require here because Socket.IO may start with polling) - // If you'd like to log transport type: req._query or req.url may contain transport param + // console.log('Initializing router for socket server'); + // router.init(socketServer, new IORouter(), handlers); - // Accept handshake - return accept(null, true); - } catch (err) { - console.error('[ALLOW_REQUEST] Unexpected error while checking handshake:', err); - return accept(err, false); - } - }, + // console.log('Socket server setup complete'); - // Tune timeouts (helps reduce disconnects behind proxies) - pingInterval: 25000, - pingTimeout: 60000, - maxHttpBufferSize: 1e6 - }); - - // Router / handlers / pubsub wiring (same as before) with robust logging + // console.log('socket Server ', socketServer); console.log('Setting up socket handlers and router'); - const handlers = Handlers(activityService, io); + const handlers = Handlers(activityService, socketServer); const watcher = redis.duplicate(); console.log('Initializing router for socket server'); - router.init(io, new IORouter(), handlers); + router.init(socketServer, new IORouter(), handlers); console.log('Initializing pubsub for socket server'); try { @@ -116,34 +70,9 @@ module.exports = (server, redis) => { console.error('PubSub init failed (sockets still running):', e); } - // Connection logging to confirm traffic (shows transport used) - io.on('connection', (socket) => { - try { - console.log('[WS] client connected:', socket.id, 'transport:', socket.conn.transport.name); - // If you want more detail on handshake: - if (socket.handshake && socket.handshake.headers) { - console.log('[WS] handshake.headers.origin=', socket.handshake.headers.origin); - console.log('[WS] handshake.headers.upgrade=', socket.handshake.headers.upgrade); - console.log('[WS] handshake.headers.connection=', socket.handshake.headers.connection); - } - - socket.on('error', (err) => { - console.error('[WS] socket error for', socket.id, err); - }); - - socket.on('disconnect', (reason) => { - console.log('[WS] disconnect', socket.id, reason); - }); - } catch (ex) { - console.error('[WS] error in connection handler', ex); - } + // Optional: log connections to confirm traffic in Azure + socketServer.on('connection', (s) => { + console.log('Socket connected:', s.id, 'transport:', s.conn.transport.name); }); - - // Global engine-level error logging (handshake/connect errors) - io.engine.on('connection_error', (err) => { - console.log('[ENGINE] connection_error:', err && err.message ? err.message : err); - }); - - console.log('Socket server setup complete'); - return { socketServer: io, activityService, handlers }; + return { socketServer, activityService, handlers }; }; diff --git a/test/spec/app/socket/index.spec.js b/test/spec/app/socket/index.spec.js index 5c7c3fb9..24f07895 100644 --- a/test/spec/app/socket/index.spec.js +++ b/test/spec/app/socket/index.spec.js @@ -1,11 +1,9 @@ const SocketIO = require('socket.io'); const expect = require('chai').expect; -const http = require('http'); const Socket = require('../../../../app/socket'); describe('socket', () => { - let server; - + const MOCK_SERVER = {}; const MOCK_REDIS = { duplicated: false, duplicate: () => { @@ -16,23 +14,12 @@ describe('socket', () => { on: () => {} }; - beforeEach(() => { - // Create a real HTTP server for Socket.IO to attach to - server = http.createServer((req, res) => res.end()); - }); - - afterEach((done) => { + afterEach(() => { MOCK_REDIS.duplicated = false; - // Close the server to avoid handle leaks - if (server && server.close) { - server.close(() => done()); - } else { - done(); - } }); it('should be appropriately initialised', () => { - const socket = Socket(server, MOCK_REDIS); + const socket = Socket(MOCK_SERVER, MOCK_REDIS); expect(socket).not.to.be.undefined; expect(socket.socketServer).to.be.instanceOf(SocketIO.Server); expect(socket.activityService).to.be.an('object'); @@ -41,6 +28,5 @@ describe('socket', () => { expect(socket.handlers.activityService).to.equal(socket.activityService); expect(socket.handlers.socketServer).to.equal(socket.socketServer); expect(MOCK_REDIS.duplicated).to.be.true; - }); + }) }); -// ...existing code... \ No newline at end of file From 46cf9a7d31e4a28b3d19872eafeeb2672ad5d73a Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Fri, 12 Dec 2025 13:31:09 +0000 Subject: [PATCH 108/113] socket io redis adapter added --- app/socket/index.js | 89 +++++++++++++++++++++++++++++++++++---------- package.json | 2 + yarn.lock | 89 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 159 insertions(+), 21 deletions(-) diff --git a/app/socket/index.js b/app/socket/index.js index b660b82c..ac7e173f 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -7,6 +7,10 @@ const Handlers = require('./service/handlers'); const pubSub = require('./redis/pub-sub')(); const router = require('./router'); +// Missing imports — REQUIRED for Redis Adapter +const { createClient } = require('redis'); +const { createAdapter } = require('@socket.io/redis-adapter'); + /** * Sets up a series of routes for a "socket" endpoint, that * leverages socket.io and will more than likely use long polling @@ -23,38 +27,79 @@ module.exports = (server, redis) => { const activityService = ActivityService(config, redis); console.log('Creating socket server'); + const socketServer = SocketIO(server, { + allowEIO3: true, + cors: { + origin: '*', + methods: ['GET', 'POST'], + credentials: false + }, + }); + // const socketServer = SocketIO(server, { // allowEIO3: true, + // transports: ['websocket', 'polling'], // cors: { - // origin: '*', + // origin: [ + // 'https://manage-case-int1.demo.platform.hmcts.net', + // 'http://localhost:3000' + // ], // methods: ['GET', 'POST'], // credentials: true // }, // }); - const socketServer = SocketIO(server, { - allowEIO3: true, - transports: ['websocket', 'polling'], - cors: { - origin: ['https://manage-case-int1.demo.platform.hmcts.net', 'http://localhost:3000'], - methods: ['GET', 'POST'], - credentials: true - }, - }); + // + // --------------------------------------------------------- + // ENABLE REDIS ADAPTER (Fixes “Session ID unknown”) + // --------------------------------------------------------- + // + async function enableRedisAdapter(io) { + try { + + const redisPort = config.get('redis.port'); + const redisHost = config.get('redis.host'); + + // HMCTS secret pattern → password is inside .value + const redisPwdObj = config.get('secrets.ccd.activity-redis-password'); + const redisPwd = redisPwdObj?.value ?? redisPwdObj; // supports both flat and nested + + if (!redisHost || !redisPort) { + console.warn('[SOCKET.IO] redis.host/redis.port missing — Redis adapter not enabled'); + return; + } + + const redisUrl = redisPwd + ? `redis://:${encodeURIComponent(redisPwd)}@${redisHost}:${redisPort}` + : `redis://${redisHost}:${redisPort}`; - // console.log('Setting up socket handlers and router'); - // const handlers = Handlers(activityService, socketServer); - // const watcher = redis.duplicate(); + console.log('[SOCKET.IO] Connecting to Redis at', redisUrl); - // console.log('Initializing pubsub for socket server'); - // pubSub.init(watcher, handlers.notify); + const pubClient = createClient({ url: redisUrl }); + const subClient = pubClient.duplicate(); - // console.log('Initializing router for socket server'); - // router.init(socketServer, new IORouter(), handlers); + await pubClient.connect(); + await subClient.connect(); + + io.adapter(createAdapter(pubClient, subClient)); + + console.log('[SOCKET.IO] Redis adapter enabled'); + } catch (err) { + console.error('[SOCKET.IO] Failed to enable Redis adapter:', err); + } + } + + // Call the adapter initialisation (non-blocking) + enableRedisAdapter(socketServer).catch(err => { + console.error('[SOCKET.IO] Redis adapter init failed:', err); + }); - // console.log('Socket server setup complete'); - // console.log('socket Server ', socketServer); + // + // --------------------------------------------------------- + // SETUP ROUTER + HANDLERS + PUBSUB + // --------------------------------------------------------- + // console.log('Setting up socket handlers and router'); const handlers = Handlers(activityService, socketServer); const watcher = redis.duplicate(); @@ -70,7 +115,11 @@ module.exports = (server, redis) => { console.error('PubSub init failed (sockets still running):', e); } - // Optional: log connections to confirm traffic in Azure + // + // --------------------------------------------------------- + // LOG CONNECTION EVENTS + // --------------------------------------------------------- + // socketServer.on('connection', (s) => { console.log('Socket connected:', s.id, 'transport:', s.conn.transport.name); }); diff --git a/package.json b/package.json index b8b5e900..4aacf2bf 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@hmcts/nodejs-healthcheck": "^1.8.0", "@hmcts/nodejs-logging": "^4.0.4", "@hmcts/properties-volume": "^0.0.14", + "@socket.io/redis-adapter": "^8.3.0", "applicationinsights": "2.9.8", "body-parser": "^1.20.1", "config": "^1.26.1", @@ -58,6 +59,7 @@ "node-cron": "^1.2.1", "node-fetch": "^2.6.7", "or": "^0.2.0", + "redis": "^5.10.0", "socket.io": "^4.8.1", "socket.io-router-middleware": "^1.1.2", "yarn": "^1.22.22" diff --git a/yarn.lock b/yarn.lock index 4b142522..7f1a27fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -589,6 +589,51 @@ __metadata: languageName: node linkType: hard +"@redis/bloom@npm:5.10.0": + version: 5.10.0 + resolution: "@redis/bloom@npm:5.10.0" + peerDependencies: + "@redis/client": ^5.10.0 + checksum: 10/c9d3d3113a2f0ccaadaff74ad261e90581acc1929d36366172d0ce757631c8b40af04b7e5bb4a1dc5e31d02e1abca98d89cae598cbd1b8b516b24148168c37a7 + languageName: node + linkType: hard + +"@redis/client@npm:5.10.0": + version: 5.10.0 + resolution: "@redis/client@npm:5.10.0" + dependencies: + cluster-key-slot: "npm:1.1.2" + checksum: 10/c60d7de94afd943a2f9be19fd9595f94f67afa2b9b4e944bfe19321cd8578f9908705297f349c5ffb4b0630153ac762ab450d930b32c7900fe158e6e5c237375 + languageName: node + linkType: hard + +"@redis/json@npm:5.10.0": + version: 5.10.0 + resolution: "@redis/json@npm:5.10.0" + peerDependencies: + "@redis/client": ^5.10.0 + checksum: 10/68ee37f6b1c82ffaf4345af987f4497871f3ce8b26c83bf243127cd2464f6fbb998ffe34492eaf49b8ab153e99bb4b5e3fbd63582813e3a457a6ec71e25e7d8e + languageName: node + linkType: hard + +"@redis/search@npm:5.10.0": + version: 5.10.0 + resolution: "@redis/search@npm:5.10.0" + peerDependencies: + "@redis/client": ^5.10.0 + checksum: 10/677980fd6a74428b434a93486c0fec7319f91786f639c1c65196776f05ed9543062f78cc10900f2947c1c8d4d0a832626a025f8a4475ea2fb788a8a462b5521c + languageName: node + linkType: hard + +"@redis/time-series@npm:5.10.0": + version: 5.10.0 + resolution: "@redis/time-series@npm:5.10.0" + peerDependencies: + "@redis/client": ^5.10.0 + checksum: 10/17a3328b694f19a9de441f2b36b35dbf493ba877790abede5b599d7720420bf944cd33b5294381a2870c14fdd0a49a00fe9bd85e11938024568d19647d468275 + languageName: node + linkType: hard + "@rtsao/scc@npm:^1.1.0": version: 1.1.0 resolution: "@rtsao/scc@npm:1.1.0" @@ -670,6 +715,19 @@ __metadata: languageName: node linkType: hard +"@socket.io/redis-adapter@npm:^8.3.0": + version: 8.3.0 + resolution: "@socket.io/redis-adapter@npm:8.3.0" + dependencies: + debug: "npm:~4.3.1" + notepack.io: "npm:~3.0.1" + uid2: "npm:1.0.0" + peerDependencies: + socket.io-adapter: ^2.5.4 + checksum: 10/e536492df65c16fb31a52f41a2c9cf94e712d2458f151351ea8a909e72a81d02892aa42abfc6acad7a4da0318e2b3ffed053879c510ccfb28b5e86cef2305b20 + languageName: node + linkType: hard + "@types/chai@npm:4": version: 4.3.20 resolution: "@types/chai@npm:4.3.20" @@ -1328,6 +1386,7 @@ __metadata: "@hmcts/nodejs-healthcheck": "npm:^1.8.0" "@hmcts/nodejs-logging": "npm:^4.0.4" "@hmcts/properties-volume": "npm:^0.0.14" + "@socket.io/redis-adapter": "npm:^8.3.0" applicationinsights: "npm:2.9.8" body-parser: "npm:^1.20.1" chai: "npm:^4.3.6" @@ -1360,6 +1419,7 @@ __metadata: nyc: "npm:^15.0.0" or: "npm:^0.2.0" proxyquire: "npm:^2.1.3" + redis: "npm:^5.10.0" sinon: "npm:^18.0.1" sinon-chai: "npm:^3.5.0" sinon-express-mock: "npm:^2.2.1" @@ -1549,7 +1609,7 @@ __metadata: languageName: node linkType: hard -"cluster-key-slot@npm:^1.0.6": +"cluster-key-slot@npm:1.1.2, cluster-key-slot@npm:^1.0.6": version: 1.1.2 resolution: "cluster-key-slot@npm:1.1.2" checksum: 10/516ed8b5e1a14d9c3a9c96c72ef6de2d70dfcdbaa0ec3a90bc7b9216c5457e39c09a5775750c272369070308542e671146120153062ab5f2f481bed5de2c925f @@ -4736,6 +4796,13 @@ __metadata: languageName: node linkType: hard +"notepack.io@npm:~3.0.1": + version: 3.0.1 + resolution: "notepack.io@npm:3.0.1" + checksum: 10/8c96fc32ea742d28df54f5d3b08b4ad18c513e972c586f02371870dd017e4e0c860df9536f4370cd992c3ef7760f0a1e9a6be68a57f3fefed67f9faa5ec37d4b + languageName: node + linkType: hard + "nyc@npm:^15.0.0": version: 15.1.0 resolution: "nyc@npm:15.1.0" @@ -5300,6 +5367,19 @@ __metadata: languageName: node linkType: hard +"redis@npm:^5.10.0": + version: 5.10.0 + resolution: "redis@npm:5.10.0" + dependencies: + "@redis/bloom": "npm:5.10.0" + "@redis/client": "npm:5.10.0" + "@redis/json": "npm:5.10.0" + "@redis/search": "npm:5.10.0" + "@redis/time-series": "npm:5.10.0" + checksum: 10/f88a7098e25798ebae8f9b0898835b60395285fc62d9e80c33e87aa317ed23970f8c9da416c4db8fd84a055a2735639aec15e2689c9c5bf6907bdc5c06207b7d + languageName: node + linkType: hard + "reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.9": version: 1.0.10 resolution: "reflect.getprototypeof@npm:1.0.10" @@ -6411,6 +6491,13 @@ __metadata: languageName: node linkType: hard +"uid2@npm:1.0.0": + version: 1.0.0 + resolution: "uid2@npm:1.0.0" + checksum: 10/7efad0da3839ef2bebc6fae4bd29905702cd64233b3907e3300aa2d7ea1a00c1ae8c41a5e16ca34ac2db2d25c5607d5989673e1df51a2a076fefbeed51605ec3 + languageName: node + linkType: hard + "unbox-primitive@npm:^1.1.0": version: 1.1.0 resolution: "unbox-primitive@npm:1.1.0" From 9a370dd0de944552b8b09a7aff3c10a5d9bd68f7 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Fri, 12 Dec 2025 14:04:06 +0000 Subject: [PATCH 109/113] lint fix --- app/socket/index.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/app/socket/index.js b/app/socket/index.js index ac7e173f..3b29a911 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -1,16 +1,15 @@ const config = require('config'); const IORouter = require('socket.io-router-middleware'); const SocketIO = require('socket.io'); +// Missing imports — REQUIRED for Redis Adapter +const { createClient } = require('redis'); +const { createAdapter } = require('@socket.io/redis-adapter'); const ActivityService = require('./service/activity-service'); const Handlers = require('./service/handlers'); const pubSub = require('./redis/pub-sub')(); const router = require('./router'); -// Missing imports — REQUIRED for Redis Adapter -const { createClient } = require('redis'); -const { createAdapter } = require('@socket.io/redis-adapter'); - /** * Sets up a series of routes for a "socket" endpoint, that * leverages socket.io and will more than likely use long polling @@ -56,13 +55,16 @@ module.exports = (server, redis) => { // async function enableRedisAdapter(io) { try { - const redisPort = config.get('redis.port'); const redisHost = config.get('redis.host'); // HMCTS secret pattern → password is inside .value const redisPwdObj = config.get('secrets.ccd.activity-redis-password'); - const redisPwd = redisPwdObj?.value ?? redisPwdObj; // supports both flat and nested + // const redisPwd = redisPwdObj?.value ?? redisPwdObj; // supports both flat and nested + + const redisPwd = redisPwdObj && redisPwdObj.value + ? redisPwdObj.value + : redisPwdObj; if (!redisHost || !redisPort) { console.warn('[SOCKET.IO] redis.host/redis.port missing — Redis adapter not enabled'); @@ -90,11 +92,10 @@ module.exports = (server, redis) => { } // Call the adapter initialisation (non-blocking) - enableRedisAdapter(socketServer).catch(err => { + enableRedisAdapter(socketServer).catch((err) => { console.error('[SOCKET.IO] Redis adapter init failed:', err); }); - // // --------------------------------------------------------- // SETUP ROUTER + HANDLERS + PUBSUB From d5f67e3100a17ca3bac1cacd1aa163d5572ee68c Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Fri, 12 Dec 2025 15:33:56 +0000 Subject: [PATCH 110/113] handle errors added --- app/socket/index.js | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/app/socket/index.js b/app/socket/index.js index 3b29a911..81cbd9dd 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -76,10 +76,27 @@ module.exports = (server, redis) => { : `redis://${redisHost}:${redisPort}`; console.log('[SOCKET.IO] Connecting to Redis at', redisUrl); - const pubClient = createClient({ url: redisUrl }); const subClient = pubClient.duplicate(); + const attachErrorHandlers = (client, name) => { + client.on('error', (err) => { + console.log(`[SOCKET.IO][REDIS][${name}] redis client error:`, err && err.message ? err.message : err); + }); + client.on('connect', () => { + console.log(`[SOCKET.IO][REDIS][${name}] connected`); + }); + client.on('end', () => { + console.log(`[SOCKET.IO][REDIS][${name}] connection ended`); + }); + client.on('reconnecting', () => { + console.log(`[SOCKET.IO][REDIS][${name}] reconnecting`); + }); + }; + + attachErrorHandlers(pubClient, 'pub'); + attachErrorHandlers(subClient, 'sub'); + await pubClient.connect(); await subClient.connect(); @@ -87,13 +104,13 @@ module.exports = (server, redis) => { console.log('[SOCKET.IO] Redis adapter enabled'); } catch (err) { - console.error('[SOCKET.IO] Failed to enable Redis adapter:', err); + console.log('[SOCKET.IO] Failed to enable Redis adapter:', err); } } // Call the adapter initialisation (non-blocking) enableRedisAdapter(socketServer).catch((err) => { - console.error('[SOCKET.IO] Redis adapter init failed:', err); + console.log('[SOCKET.IO] Redis adapter init failed:', err); }); // @@ -103,13 +120,13 @@ module.exports = (server, redis) => { // console.log('Setting up socket handlers and router'); const handlers = Handlers(activityService, socketServer); - const watcher = redis.duplicate(); console.log('Initializing router for socket server'); router.init(socketServer, new IORouter(), handlers); console.log('Initializing pubsub for socket server'); try { + const watcher = redis.duplicate(); pubSub.init(watcher, handlers.notify); console.log('PubSub initialized'); } catch (e) { @@ -124,5 +141,8 @@ module.exports = (server, redis) => { socketServer.on('connection', (s) => { console.log('Socket connected:', s.id, 'transport:', s.conn.transport.name); }); + socketServer.on('error', (err) => { + console.log('[SOCKET.IO] server error:', err && err.message ? err.message : err); + }); return { socketServer, activityService, handlers }; }; From 9c0439953595fc89aea6b166641fa90ebea1ea1e Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Mon, 15 Dec 2025 18:00:31 +0000 Subject: [PATCH 111/113] dummy commit --- app/socket/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/socket/index.js b/app/socket/index.js index 81cbd9dd..f30fa79d 100644 --- a/app/socket/index.js +++ b/app/socket/index.js @@ -138,6 +138,7 @@ module.exports = (server, redis) => { // LOG CONNECTION EVENTS // --------------------------------------------------------- // + // log connections and errors socketServer.on('connection', (s) => { console.log('Socket connected:', s.id, 'transport:', s.conn.transport.name); }); From e9cb3404f9a0d5741f6e3a6612bc36dd0832f761 Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Thu, 18 Dec 2025 11:06:00 +0000 Subject: [PATCH 112/113] multi pod test --- charts/ccd-case-activity-api/values.preview.template.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/charts/ccd-case-activity-api/values.preview.template.yaml b/charts/ccd-case-activity-api/values.preview.template.yaml index 8b246cf4..3e2c0def 100644 --- a/charts/ccd-case-activity-api/values.preview.template.yaml +++ b/charts/ccd-case-activity-api/values.preview.template.yaml @@ -8,6 +8,7 @@ nodejs: REDIS_SSL_ENABLED: "" CORS_ORIGIN_WHITELIST: "*" keyVaults: + replicas: 2 redis: enabled: true From 1a900370495b3ea7af9d8e55a7ae7ced70422f1c Mon Sep 17 00:00:00 2001 From: Balaji Sridharan Date: Wed, 21 Jan 2026 15:44:56 +0000 Subject: [PATCH 113/113] dummy commit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 587b0189..66c92f87 100644 --- a/README.md +++ b/README.md @@ -94,3 +94,4 @@ $ yarn start ccd-case-activity-api:server Listening on port 3000 +19ms ccd-case-activity-api:redis-client connected to Redis +7ms ``` +