├── .gitignore ├── LICENSE ├── README.md ├── api ├── lib │ ├── assignUser.js │ └── isMobile.js ├── package-lock.json ├── package.json └── server.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── manifest.json └── robots.txt └── src ├── App.css ├── App.js ├── component ├── About.js ├── ActiveVisitors.js ├── AvgVisitDuration.js ├── Devices.js ├── HoursOfActivityPerDay.js ├── NewVsReturn.js ├── Referrer.js ├── TopPages.js └── VisitsByCountry.js ├── config.json ├── index.css └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | api/node_modules 3 | /node_modules 4 | /.pnp 5 | /.pnp.js 6 | 7 | # testing 8 | /coverage 9 | 10 | # production 11 | /build 12 | 13 | # misc 14 | /.DS_Store 15 | /.env.local 16 | /.env.development.local 17 | /.env.test.local 18 | /.env.production.local 19 | 20 | /npm-debug.log* 21 | /yarn-debug.log* 22 | /yarn-error.log* 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Daniel31X13 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Open Analytics 4 | 5 | ![GitHub](https://img.shields.io/github/license/Daniel31x13/open-analytics) ![GitHub top language](https://img.shields.io/github/languages/top/daniel31x13/open-analytics) 6 | 7 | **Self-hosted, accurate analytics tool for your website in a clean minimalist interface.** 8 | 9 | [Demo](https://open-analytics-demo.herokuapp.com/) (w/ fake data) 10 | 11 |
12 | 13 | --- 14 | 15 | This project is a derivation of [open-analytics-core](https://github.com/Daniel31x13/open-analytics-core). 16 | So like its predecessor it logs the following to a MongoDB database: Device Ip, User-agent, IsMobile, Date, Active Time (Seconds), Visited url, Location, Referrer... 17 | 18 | ### And uses those data to visualize: 19 | - Average time on each page 20 | - Current active users 21 | - Most used device (Mobile/Desktop) 22 | - Visits by each country 23 | - Websites most users are comming from 24 | - Top visited links 25 | - New vs returning users 26 | - Sum of all users activity per day 27 | 28 | --- 29 | ### Setup 30 | 31 | #### First Step: 32 | Clone this repository. 33 | 34 | #### Second Step: 35 | Add this code inside the `body` tag of whatever page you want to include in the analytics: 36 | 37 | ```javascript 38 | // If you need to include in multiple pages, add the latter script tag to a seperate .js file to avoid unnecessary lines of code. 39 | 40 | 52 | ``` 53 | #### Third Step: 54 | Edit the [`./src/config.json`](src/config.json) file accordingly: 55 | ```javascript 56 | { 57 | "api": { // Edit here accordingly 58 | "port": 8070, 59 | "uri": "mongodb://localhost:27017", 60 | "databaseName": "DatabaseName", 61 | "collectionName": "CollectionName", 62 | "address": "http://192.168.1.7" // DONT include the "/" at the end 63 | }, 64 | "tls_support": { // Leave this as is if you dont need HTTPS support 65 | "enabled": false, 66 | "key": "", // key.pem file path (relative to "./api/server.js") 67 | "cert": "" // cert.pem file path (relative to "./api/server.js") 68 | } 69 | } 70 | ``` 71 | 72 | #### Fourth Step: 73 | Run `(cd api && node server.js) & npm start` in the main folder, the dashboard will be available in [http://localhost:3000](http://localhost:3000/). 74 | -------------------------------------------------------------------------------- /api/lib/assignUser.js: -------------------------------------------------------------------------------- 1 | const isMobile = require("./isMobile"); 2 | 3 | function assignUser(clientIp, country, clientHeader, clientURL, clientReferrer, time, activeTime, isNewUser) { 4 | return { // Store client info in an object 5 | "Ip": clientIp, 6 | "Location": country, 7 | "User-Agent": clientHeader, 8 | "IsMobile": isMobile(clientHeader), 9 | "Url": clientURL, 10 | "Referrer": clientReferrer, 11 | "Date": time, 12 | "ActiveTimeInSecond": activeTime, 13 | "isNewUser": isNewUser 14 | } 15 | } 16 | 17 | module.exports = assignUser; 18 | -------------------------------------------------------------------------------- /api/lib/isMobile.js: -------------------------------------------------------------------------------- 1 | function isMobile(clientHeader) { // Checks if client is mobile 2 | const toMatch = [ 3 | /Android/i, 4 | /webOS/i, 5 | /iPhone/i, 6 | /iPad/i, 7 | /iPod/i, 8 | /BlackBerry/i, 9 | /Windows Phone/i 10 | ]; 11 | 12 | return toMatch.some((toMatchItem) => { 13 | return clientHeader.match(toMatchItem); 14 | }); 15 | } 16 | 17 | module.exports = isMobile; -------------------------------------------------------------------------------- /api/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "open-analytics-core", 3 | "version": "1.1.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "open-analytics-core", 9 | "version": "1.1.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "cors": "^2.8.5", 13 | "express": "^4.17.1", 14 | "fast-geoip": "^1.1.54", 15 | "mongodb": "^4.3.0", 16 | "socket.io": "^4.4.0" 17 | } 18 | }, 19 | "node_modules/@socket.io/base64-arraybuffer": { 20 | "version": "1.0.2", 21 | "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", 22 | "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==", 23 | "engines": { 24 | "node": ">= 0.6.0" 25 | } 26 | }, 27 | "node_modules/@types/component-emitter": { 28 | "version": "1.2.11", 29 | "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", 30 | "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==" 31 | }, 32 | "node_modules/@types/cookie": { 33 | "version": "0.4.1", 34 | "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", 35 | "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" 36 | }, 37 | "node_modules/@types/cors": { 38 | "version": "2.8.12", 39 | "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", 40 | "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" 41 | }, 42 | "node_modules/@types/node": { 43 | "version": "17.0.9", 44 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.9.tgz", 45 | "integrity": "sha512-5dNBXu/FOER+EXnyah7rn8xlNrfMOQb/qXnw4NQgLkCygKBKhdmF/CA5oXVOKZLBEahw8s2WP9LxIcN/oDDRgQ==" 46 | }, 47 | "node_modules/@types/webidl-conversions": { 48 | "version": "6.1.1", 49 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", 50 | "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" 51 | }, 52 | "node_modules/@types/whatwg-url": { 53 | "version": "8.2.1", 54 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz", 55 | "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==", 56 | "dependencies": { 57 | "@types/node": "*", 58 | "@types/webidl-conversions": "*" 59 | } 60 | }, 61 | "node_modules/accepts": { 62 | "version": "1.3.7", 63 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 64 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 65 | "dependencies": { 66 | "mime-types": "~2.1.24", 67 | "negotiator": "0.6.2" 68 | }, 69 | "engines": { 70 | "node": ">= 0.6" 71 | } 72 | }, 73 | "node_modules/array-flatten": { 74 | "version": "1.1.1", 75 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 76 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 77 | }, 78 | "node_modules/base64-js": { 79 | "version": "1.5.1", 80 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 81 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 82 | "funding": [ 83 | { 84 | "type": "github", 85 | "url": "https://github.com/sponsors/feross" 86 | }, 87 | { 88 | "type": "patreon", 89 | "url": "https://www.patreon.com/feross" 90 | }, 91 | { 92 | "type": "consulting", 93 | "url": "https://feross.org/support" 94 | } 95 | ] 96 | }, 97 | "node_modules/base64id": { 98 | "version": "2.0.0", 99 | "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", 100 | "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", 101 | "engines": { 102 | "node": "^4.5.0 || >= 5.9" 103 | } 104 | }, 105 | "node_modules/body-parser": { 106 | "version": "1.19.0", 107 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 108 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 109 | "dependencies": { 110 | "bytes": "3.1.0", 111 | "content-type": "~1.0.4", 112 | "debug": "2.6.9", 113 | "depd": "~1.1.2", 114 | "http-errors": "1.7.2", 115 | "iconv-lite": "0.4.24", 116 | "on-finished": "~2.3.0", 117 | "qs": "6.7.0", 118 | "raw-body": "2.4.0", 119 | "type-is": "~1.6.17" 120 | }, 121 | "engines": { 122 | "node": ">= 0.8" 123 | } 124 | }, 125 | "node_modules/body-parser/node_modules/debug": { 126 | "version": "2.6.9", 127 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 128 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 129 | "dependencies": { 130 | "ms": "2.0.0" 131 | } 132 | }, 133 | "node_modules/body-parser/node_modules/ms": { 134 | "version": "2.0.0", 135 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 136 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 137 | }, 138 | "node_modules/bson": { 139 | "version": "4.6.1", 140 | "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.1.tgz", 141 | "integrity": "sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw==", 142 | "dependencies": { 143 | "buffer": "^5.6.0" 144 | }, 145 | "engines": { 146 | "node": ">=6.9.0" 147 | } 148 | }, 149 | "node_modules/buffer": { 150 | "version": "5.7.1", 151 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 152 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 153 | "funding": [ 154 | { 155 | "type": "github", 156 | "url": "https://github.com/sponsors/feross" 157 | }, 158 | { 159 | "type": "patreon", 160 | "url": "https://www.patreon.com/feross" 161 | }, 162 | { 163 | "type": "consulting", 164 | "url": "https://feross.org/support" 165 | } 166 | ], 167 | "dependencies": { 168 | "base64-js": "^1.3.1", 169 | "ieee754": "^1.1.13" 170 | } 171 | }, 172 | "node_modules/bytes": { 173 | "version": "3.1.0", 174 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 175 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", 176 | "engines": { 177 | "node": ">= 0.8" 178 | } 179 | }, 180 | "node_modules/component-emitter": { 181 | "version": "1.3.0", 182 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", 183 | "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" 184 | }, 185 | "node_modules/content-disposition": { 186 | "version": "0.5.3", 187 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 188 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 189 | "dependencies": { 190 | "safe-buffer": "5.1.2" 191 | }, 192 | "engines": { 193 | "node": ">= 0.6" 194 | } 195 | }, 196 | "node_modules/content-type": { 197 | "version": "1.0.4", 198 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 199 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", 200 | "engines": { 201 | "node": ">= 0.6" 202 | } 203 | }, 204 | "node_modules/cookie": { 205 | "version": "0.4.1", 206 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", 207 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", 208 | "engines": { 209 | "node": ">= 0.6" 210 | } 211 | }, 212 | "node_modules/cookie-signature": { 213 | "version": "1.0.6", 214 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 215 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 216 | }, 217 | "node_modules/cors": { 218 | "version": "2.8.5", 219 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 220 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 221 | "dependencies": { 222 | "object-assign": "^4", 223 | "vary": "^1" 224 | }, 225 | "engines": { 226 | "node": ">= 0.10" 227 | } 228 | }, 229 | "node_modules/denque": { 230 | "version": "2.0.1", 231 | "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", 232 | "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==", 233 | "engines": { 234 | "node": ">=0.10" 235 | } 236 | }, 237 | "node_modules/depd": { 238 | "version": "1.1.2", 239 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 240 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", 241 | "engines": { 242 | "node": ">= 0.6" 243 | } 244 | }, 245 | "node_modules/destroy": { 246 | "version": "1.0.4", 247 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 248 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 249 | }, 250 | "node_modules/ee-first": { 251 | "version": "1.1.1", 252 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 253 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 254 | }, 255 | "node_modules/encodeurl": { 256 | "version": "1.0.2", 257 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 258 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", 259 | "engines": { 260 | "node": ">= 0.8" 261 | } 262 | }, 263 | "node_modules/engine.io": { 264 | "version": "6.1.1", 265 | "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.1.tgz", 266 | "integrity": "sha512-AyMc20q8JUUdvKd46+thc9o7yCZ6iC6MoBCChG5Z1XmFMpp+2+y/oKvwpZTUJB0KCjxScw1dV9c2h5pjiYBLuQ==", 267 | "dependencies": { 268 | "@types/cookie": "^0.4.1", 269 | "@types/cors": "^2.8.12", 270 | "@types/node": ">=10.0.0", 271 | "accepts": "~1.3.4", 272 | "base64id": "2.0.0", 273 | "cookie": "~0.4.1", 274 | "cors": "~2.8.5", 275 | "debug": "~4.3.1", 276 | "engine.io-parser": "~5.0.0", 277 | "ws": "~8.2.3" 278 | }, 279 | "engines": { 280 | "node": ">=10.0.0" 281 | } 282 | }, 283 | "node_modules/engine.io-parser": { 284 | "version": "5.0.3", 285 | "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", 286 | "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", 287 | "dependencies": { 288 | "@socket.io/base64-arraybuffer": "~1.0.2" 289 | }, 290 | "engines": { 291 | "node": ">=10.0.0" 292 | } 293 | }, 294 | "node_modules/engine.io/node_modules/debug": { 295 | "version": "4.3.3", 296 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 297 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 298 | "dependencies": { 299 | "ms": "2.1.2" 300 | }, 301 | "engines": { 302 | "node": ">=6.0" 303 | }, 304 | "peerDependenciesMeta": { 305 | "supports-color": { 306 | "optional": true 307 | } 308 | } 309 | }, 310 | "node_modules/engine.io/node_modules/ms": { 311 | "version": "2.1.2", 312 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 313 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 314 | }, 315 | "node_modules/escape-html": { 316 | "version": "1.0.3", 317 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 318 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 319 | }, 320 | "node_modules/etag": { 321 | "version": "1.8.1", 322 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 323 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", 324 | "engines": { 325 | "node": ">= 0.6" 326 | } 327 | }, 328 | "node_modules/express": { 329 | "version": "4.17.1", 330 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 331 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 332 | "dependencies": { 333 | "accepts": "~1.3.7", 334 | "array-flatten": "1.1.1", 335 | "body-parser": "1.19.0", 336 | "content-disposition": "0.5.3", 337 | "content-type": "~1.0.4", 338 | "cookie": "0.4.0", 339 | "cookie-signature": "1.0.6", 340 | "debug": "2.6.9", 341 | "depd": "~1.1.2", 342 | "encodeurl": "~1.0.2", 343 | "escape-html": "~1.0.3", 344 | "etag": "~1.8.1", 345 | "finalhandler": "~1.1.2", 346 | "fresh": "0.5.2", 347 | "merge-descriptors": "1.0.1", 348 | "methods": "~1.1.2", 349 | "on-finished": "~2.3.0", 350 | "parseurl": "~1.3.3", 351 | "path-to-regexp": "0.1.7", 352 | "proxy-addr": "~2.0.5", 353 | "qs": "6.7.0", 354 | "range-parser": "~1.2.1", 355 | "safe-buffer": "5.1.2", 356 | "send": "0.17.1", 357 | "serve-static": "1.14.1", 358 | "setprototypeof": "1.1.1", 359 | "statuses": "~1.5.0", 360 | "type-is": "~1.6.18", 361 | "utils-merge": "1.0.1", 362 | "vary": "~1.1.2" 363 | }, 364 | "engines": { 365 | "node": ">= 0.10.0" 366 | } 367 | }, 368 | "node_modules/express/node_modules/cookie": { 369 | "version": "0.4.0", 370 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 371 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", 372 | "engines": { 373 | "node": ">= 0.6" 374 | } 375 | }, 376 | "node_modules/express/node_modules/debug": { 377 | "version": "2.6.9", 378 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 379 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 380 | "dependencies": { 381 | "ms": "2.0.0" 382 | } 383 | }, 384 | "node_modules/express/node_modules/ms": { 385 | "version": "2.0.0", 386 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 387 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 388 | }, 389 | "node_modules/fast-geoip": { 390 | "version": "1.1.54", 391 | "resolved": "https://registry.npmjs.org/fast-geoip/-/fast-geoip-1.1.54.tgz", 392 | "integrity": "sha512-X3tpXwYWh+aPn69mHBnWleOBNTSuN6eutF8olXCEBekdYj5ERFnNDPGhiqQbjnJT+fVLx3vWnvanX2EpLcakCQ==" 393 | }, 394 | "node_modules/finalhandler": { 395 | "version": "1.1.2", 396 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 397 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 398 | "dependencies": { 399 | "debug": "2.6.9", 400 | "encodeurl": "~1.0.2", 401 | "escape-html": "~1.0.3", 402 | "on-finished": "~2.3.0", 403 | "parseurl": "~1.3.3", 404 | "statuses": "~1.5.0", 405 | "unpipe": "~1.0.0" 406 | }, 407 | "engines": { 408 | "node": ">= 0.8" 409 | } 410 | }, 411 | "node_modules/finalhandler/node_modules/debug": { 412 | "version": "2.6.9", 413 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 414 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 415 | "dependencies": { 416 | "ms": "2.0.0" 417 | } 418 | }, 419 | "node_modules/finalhandler/node_modules/ms": { 420 | "version": "2.0.0", 421 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 422 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 423 | }, 424 | "node_modules/forwarded": { 425 | "version": "0.2.0", 426 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 427 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 428 | "engines": { 429 | "node": ">= 0.6" 430 | } 431 | }, 432 | "node_modules/fresh": { 433 | "version": "0.5.2", 434 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 435 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", 436 | "engines": { 437 | "node": ">= 0.6" 438 | } 439 | }, 440 | "node_modules/http-errors": { 441 | "version": "1.7.2", 442 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 443 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 444 | "dependencies": { 445 | "depd": "~1.1.2", 446 | "inherits": "2.0.3", 447 | "setprototypeof": "1.1.1", 448 | "statuses": ">= 1.5.0 < 2", 449 | "toidentifier": "1.0.0" 450 | }, 451 | "engines": { 452 | "node": ">= 0.6" 453 | } 454 | }, 455 | "node_modules/iconv-lite": { 456 | "version": "0.4.24", 457 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 458 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 459 | "dependencies": { 460 | "safer-buffer": ">= 2.1.2 < 3" 461 | }, 462 | "engines": { 463 | "node": ">=0.10.0" 464 | } 465 | }, 466 | "node_modules/ieee754": { 467 | "version": "1.2.1", 468 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 469 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 470 | "funding": [ 471 | { 472 | "type": "github", 473 | "url": "https://github.com/sponsors/feross" 474 | }, 475 | { 476 | "type": "patreon", 477 | "url": "https://www.patreon.com/feross" 478 | }, 479 | { 480 | "type": "consulting", 481 | "url": "https://feross.org/support" 482 | } 483 | ] 484 | }, 485 | "node_modules/inherits": { 486 | "version": "2.0.3", 487 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 488 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 489 | }, 490 | "node_modules/ip": { 491 | "version": "1.1.5", 492 | "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", 493 | "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" 494 | }, 495 | "node_modules/ipaddr.js": { 496 | "version": "1.9.1", 497 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 498 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 499 | "engines": { 500 | "node": ">= 0.10" 501 | } 502 | }, 503 | "node_modules/media-typer": { 504 | "version": "0.3.0", 505 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 506 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", 507 | "engines": { 508 | "node": ">= 0.6" 509 | } 510 | }, 511 | "node_modules/memory-pager": { 512 | "version": "1.5.0", 513 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 514 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 515 | "optional": true 516 | }, 517 | "node_modules/merge-descriptors": { 518 | "version": "1.0.1", 519 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 520 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 521 | }, 522 | "node_modules/methods": { 523 | "version": "1.1.2", 524 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 525 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", 526 | "engines": { 527 | "node": ">= 0.6" 528 | } 529 | }, 530 | "node_modules/mime": { 531 | "version": "1.6.0", 532 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 533 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 534 | "bin": { 535 | "mime": "cli.js" 536 | }, 537 | "engines": { 538 | "node": ">=4" 539 | } 540 | }, 541 | "node_modules/mime-db": { 542 | "version": "1.51.0", 543 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", 544 | "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", 545 | "engines": { 546 | "node": ">= 0.6" 547 | } 548 | }, 549 | "node_modules/mime-types": { 550 | "version": "2.1.34", 551 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", 552 | "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", 553 | "dependencies": { 554 | "mime-db": "1.51.0" 555 | }, 556 | "engines": { 557 | "node": ">= 0.6" 558 | } 559 | }, 560 | "node_modules/mongodb": { 561 | "version": "4.3.0", 562 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.3.0.tgz", 563 | "integrity": "sha512-ovq9ZD9wEvab+LsaQOiwtne1Sy2egaHW8K/H5M18Tv+V5PgTRi+qdmxDGlbm94TSL3h56m6amstptu115Nzgow==", 564 | "dependencies": { 565 | "bson": "^4.6.1", 566 | "denque": "^2.0.1", 567 | "mongodb-connection-string-url": "^2.3.2", 568 | "socks": "^2.6.1" 569 | }, 570 | "engines": { 571 | "node": ">=12.9.0" 572 | }, 573 | "optionalDependencies": { 574 | "saslprep": "^1.0.3" 575 | } 576 | }, 577 | "node_modules/mongodb-connection-string-url": { 578 | "version": "2.4.1", 579 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.4.1.tgz", 580 | "integrity": "sha512-d5Kd2bVsKcSA7YI/yo57fSTtMwRQdFkvc5IZwod1RRxJtECeWPPSo7zqcUGJELifRA//Igs4spVtYAmvFCatug==", 581 | "dependencies": { 582 | "@types/whatwg-url": "^8.2.1", 583 | "whatwg-url": "^11.0.0" 584 | } 585 | }, 586 | "node_modules/negotiator": { 587 | "version": "0.6.2", 588 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 589 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", 590 | "engines": { 591 | "node": ">= 0.6" 592 | } 593 | }, 594 | "node_modules/object-assign": { 595 | "version": "4.1.1", 596 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 597 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 598 | "engines": { 599 | "node": ">=0.10.0" 600 | } 601 | }, 602 | "node_modules/on-finished": { 603 | "version": "2.3.0", 604 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 605 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 606 | "dependencies": { 607 | "ee-first": "1.1.1" 608 | }, 609 | "engines": { 610 | "node": ">= 0.8" 611 | } 612 | }, 613 | "node_modules/parseurl": { 614 | "version": "1.3.3", 615 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 616 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 617 | "engines": { 618 | "node": ">= 0.8" 619 | } 620 | }, 621 | "node_modules/path-to-regexp": { 622 | "version": "0.1.7", 623 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 624 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 625 | }, 626 | "node_modules/proxy-addr": { 627 | "version": "2.0.7", 628 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 629 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 630 | "dependencies": { 631 | "forwarded": "0.2.0", 632 | "ipaddr.js": "1.9.1" 633 | }, 634 | "engines": { 635 | "node": ">= 0.10" 636 | } 637 | }, 638 | "node_modules/punycode": { 639 | "version": "2.1.1", 640 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 641 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 642 | "engines": { 643 | "node": ">=6" 644 | } 645 | }, 646 | "node_modules/qs": { 647 | "version": "6.7.0", 648 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 649 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", 650 | "engines": { 651 | "node": ">=0.6" 652 | } 653 | }, 654 | "node_modules/range-parser": { 655 | "version": "1.2.1", 656 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 657 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 658 | "engines": { 659 | "node": ">= 0.6" 660 | } 661 | }, 662 | "node_modules/raw-body": { 663 | "version": "2.4.0", 664 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 665 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 666 | "dependencies": { 667 | "bytes": "3.1.0", 668 | "http-errors": "1.7.2", 669 | "iconv-lite": "0.4.24", 670 | "unpipe": "1.0.0" 671 | }, 672 | "engines": { 673 | "node": ">= 0.8" 674 | } 675 | }, 676 | "node_modules/safe-buffer": { 677 | "version": "5.1.2", 678 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 679 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 680 | }, 681 | "node_modules/safer-buffer": { 682 | "version": "2.1.2", 683 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 684 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 685 | }, 686 | "node_modules/saslprep": { 687 | "version": "1.0.3", 688 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", 689 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", 690 | "optional": true, 691 | "dependencies": { 692 | "sparse-bitfield": "^3.0.3" 693 | }, 694 | "engines": { 695 | "node": ">=6" 696 | } 697 | }, 698 | "node_modules/send": { 699 | "version": "0.17.1", 700 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 701 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 702 | "dependencies": { 703 | "debug": "2.6.9", 704 | "depd": "~1.1.2", 705 | "destroy": "~1.0.4", 706 | "encodeurl": "~1.0.2", 707 | "escape-html": "~1.0.3", 708 | "etag": "~1.8.1", 709 | "fresh": "0.5.2", 710 | "http-errors": "~1.7.2", 711 | "mime": "1.6.0", 712 | "ms": "2.1.1", 713 | "on-finished": "~2.3.0", 714 | "range-parser": "~1.2.1", 715 | "statuses": "~1.5.0" 716 | }, 717 | "engines": { 718 | "node": ">= 0.8.0" 719 | } 720 | }, 721 | "node_modules/send/node_modules/debug": { 722 | "version": "2.6.9", 723 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 724 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 725 | "dependencies": { 726 | "ms": "2.0.0" 727 | } 728 | }, 729 | "node_modules/send/node_modules/debug/node_modules/ms": { 730 | "version": "2.0.0", 731 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 732 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 733 | }, 734 | "node_modules/send/node_modules/ms": { 735 | "version": "2.1.1", 736 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 737 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 738 | }, 739 | "node_modules/serve-static": { 740 | "version": "1.14.1", 741 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 742 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 743 | "dependencies": { 744 | "encodeurl": "~1.0.2", 745 | "escape-html": "~1.0.3", 746 | "parseurl": "~1.3.3", 747 | "send": "0.17.1" 748 | }, 749 | "engines": { 750 | "node": ">= 0.8.0" 751 | } 752 | }, 753 | "node_modules/setprototypeof": { 754 | "version": "1.1.1", 755 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 756 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 757 | }, 758 | "node_modules/smart-buffer": { 759 | "version": "4.2.0", 760 | "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", 761 | "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", 762 | "engines": { 763 | "node": ">= 6.0.0", 764 | "npm": ">= 3.0.0" 765 | } 766 | }, 767 | "node_modules/socket.io": { 768 | "version": "4.4.1", 769 | "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.1.tgz", 770 | "integrity": "sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg==", 771 | "dependencies": { 772 | "accepts": "~1.3.4", 773 | "base64id": "~2.0.0", 774 | "debug": "~4.3.2", 775 | "engine.io": "~6.1.0", 776 | "socket.io-adapter": "~2.3.3", 777 | "socket.io-parser": "~4.0.4" 778 | }, 779 | "engines": { 780 | "node": ">=10.0.0" 781 | } 782 | }, 783 | "node_modules/socket.io-adapter": { 784 | "version": "2.3.3", 785 | "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", 786 | "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==" 787 | }, 788 | "node_modules/socket.io-parser": { 789 | "version": "4.0.4", 790 | "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", 791 | "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", 792 | "dependencies": { 793 | "@types/component-emitter": "^1.2.10", 794 | "component-emitter": "~1.3.0", 795 | "debug": "~4.3.1" 796 | }, 797 | "engines": { 798 | "node": ">=10.0.0" 799 | } 800 | }, 801 | "node_modules/socket.io-parser/node_modules/debug": { 802 | "version": "4.3.3", 803 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 804 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 805 | "dependencies": { 806 | "ms": "2.1.2" 807 | }, 808 | "engines": { 809 | "node": ">=6.0" 810 | }, 811 | "peerDependenciesMeta": { 812 | "supports-color": { 813 | "optional": true 814 | } 815 | } 816 | }, 817 | "node_modules/socket.io-parser/node_modules/ms": { 818 | "version": "2.1.2", 819 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 820 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 821 | }, 822 | "node_modules/socket.io/node_modules/debug": { 823 | "version": "4.3.3", 824 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 825 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 826 | "dependencies": { 827 | "ms": "2.1.2" 828 | }, 829 | "engines": { 830 | "node": ">=6.0" 831 | }, 832 | "peerDependenciesMeta": { 833 | "supports-color": { 834 | "optional": true 835 | } 836 | } 837 | }, 838 | "node_modules/socket.io/node_modules/ms": { 839 | "version": "2.1.2", 840 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 841 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 842 | }, 843 | "node_modules/socks": { 844 | "version": "2.6.1", 845 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", 846 | "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", 847 | "dependencies": { 848 | "ip": "^1.1.5", 849 | "smart-buffer": "^4.1.0" 850 | }, 851 | "engines": { 852 | "node": ">= 10.13.0", 853 | "npm": ">= 3.0.0" 854 | } 855 | }, 856 | "node_modules/sparse-bitfield": { 857 | "version": "3.0.3", 858 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 859 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", 860 | "optional": true, 861 | "dependencies": { 862 | "memory-pager": "^1.0.2" 863 | } 864 | }, 865 | "node_modules/statuses": { 866 | "version": "1.5.0", 867 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 868 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", 869 | "engines": { 870 | "node": ">= 0.6" 871 | } 872 | }, 873 | "node_modules/toidentifier": { 874 | "version": "1.0.0", 875 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 876 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", 877 | "engines": { 878 | "node": ">=0.6" 879 | } 880 | }, 881 | "node_modules/tr46": { 882 | "version": "3.0.0", 883 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", 884 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", 885 | "dependencies": { 886 | "punycode": "^2.1.1" 887 | }, 888 | "engines": { 889 | "node": ">=12" 890 | } 891 | }, 892 | "node_modules/type-is": { 893 | "version": "1.6.18", 894 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 895 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 896 | "dependencies": { 897 | "media-typer": "0.3.0", 898 | "mime-types": "~2.1.24" 899 | }, 900 | "engines": { 901 | "node": ">= 0.6" 902 | } 903 | }, 904 | "node_modules/unpipe": { 905 | "version": "1.0.0", 906 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 907 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", 908 | "engines": { 909 | "node": ">= 0.8" 910 | } 911 | }, 912 | "node_modules/utils-merge": { 913 | "version": "1.0.1", 914 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 915 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", 916 | "engines": { 917 | "node": ">= 0.4.0" 918 | } 919 | }, 920 | "node_modules/vary": { 921 | "version": "1.1.2", 922 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 923 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", 924 | "engines": { 925 | "node": ">= 0.8" 926 | } 927 | }, 928 | "node_modules/webidl-conversions": { 929 | "version": "7.0.0", 930 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", 931 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", 932 | "engines": { 933 | "node": ">=12" 934 | } 935 | }, 936 | "node_modules/whatwg-url": { 937 | "version": "11.0.0", 938 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", 939 | "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", 940 | "dependencies": { 941 | "tr46": "^3.0.0", 942 | "webidl-conversions": "^7.0.0" 943 | }, 944 | "engines": { 945 | "node": ">=12" 946 | } 947 | }, 948 | "node_modules/ws": { 949 | "version": "8.2.3", 950 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", 951 | "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", 952 | "engines": { 953 | "node": ">=10.0.0" 954 | }, 955 | "peerDependencies": { 956 | "bufferutil": "^4.0.1", 957 | "utf-8-validate": "^5.0.2" 958 | }, 959 | "peerDependenciesMeta": { 960 | "bufferutil": { 961 | "optional": true 962 | }, 963 | "utf-8-validate": { 964 | "optional": true 965 | } 966 | } 967 | } 968 | }, 969 | "dependencies": { 970 | "@socket.io/base64-arraybuffer": { 971 | "version": "1.0.2", 972 | "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", 973 | "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==" 974 | }, 975 | "@types/component-emitter": { 976 | "version": "1.2.11", 977 | "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", 978 | "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==" 979 | }, 980 | "@types/cookie": { 981 | "version": "0.4.1", 982 | "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", 983 | "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" 984 | }, 985 | "@types/cors": { 986 | "version": "2.8.12", 987 | "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", 988 | "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" 989 | }, 990 | "@types/node": { 991 | "version": "17.0.9", 992 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.9.tgz", 993 | "integrity": "sha512-5dNBXu/FOER+EXnyah7rn8xlNrfMOQb/qXnw4NQgLkCygKBKhdmF/CA5oXVOKZLBEahw8s2WP9LxIcN/oDDRgQ==" 994 | }, 995 | "@types/webidl-conversions": { 996 | "version": "6.1.1", 997 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", 998 | "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" 999 | }, 1000 | "@types/whatwg-url": { 1001 | "version": "8.2.1", 1002 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz", 1003 | "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==", 1004 | "requires": { 1005 | "@types/node": "*", 1006 | "@types/webidl-conversions": "*" 1007 | } 1008 | }, 1009 | "accepts": { 1010 | "version": "1.3.7", 1011 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 1012 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 1013 | "requires": { 1014 | "mime-types": "~2.1.24", 1015 | "negotiator": "0.6.2" 1016 | } 1017 | }, 1018 | "array-flatten": { 1019 | "version": "1.1.1", 1020 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 1021 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 1022 | }, 1023 | "base64-js": { 1024 | "version": "1.5.1", 1025 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 1026 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 1027 | }, 1028 | "base64id": { 1029 | "version": "2.0.0", 1030 | "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", 1031 | "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" 1032 | }, 1033 | "body-parser": { 1034 | "version": "1.19.0", 1035 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 1036 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 1037 | "requires": { 1038 | "bytes": "3.1.0", 1039 | "content-type": "~1.0.4", 1040 | "debug": "2.6.9", 1041 | "depd": "~1.1.2", 1042 | "http-errors": "1.7.2", 1043 | "iconv-lite": "0.4.24", 1044 | "on-finished": "~2.3.0", 1045 | "qs": "6.7.0", 1046 | "raw-body": "2.4.0", 1047 | "type-is": "~1.6.17" 1048 | }, 1049 | "dependencies": { 1050 | "debug": { 1051 | "version": "2.6.9", 1052 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1053 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1054 | "requires": { 1055 | "ms": "2.0.0" 1056 | } 1057 | }, 1058 | "ms": { 1059 | "version": "2.0.0", 1060 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1061 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1062 | } 1063 | } 1064 | }, 1065 | "bson": { 1066 | "version": "4.6.1", 1067 | "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.1.tgz", 1068 | "integrity": "sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw==", 1069 | "requires": { 1070 | "buffer": "^5.6.0" 1071 | } 1072 | }, 1073 | "buffer": { 1074 | "version": "5.7.1", 1075 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 1076 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 1077 | "requires": { 1078 | "base64-js": "^1.3.1", 1079 | "ieee754": "^1.1.13" 1080 | } 1081 | }, 1082 | "bytes": { 1083 | "version": "3.1.0", 1084 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 1085 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 1086 | }, 1087 | "component-emitter": { 1088 | "version": "1.3.0", 1089 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", 1090 | "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" 1091 | }, 1092 | "content-disposition": { 1093 | "version": "0.5.3", 1094 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 1095 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 1096 | "requires": { 1097 | "safe-buffer": "5.1.2" 1098 | } 1099 | }, 1100 | "content-type": { 1101 | "version": "1.0.4", 1102 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 1103 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 1104 | }, 1105 | "cookie": { 1106 | "version": "0.4.1", 1107 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", 1108 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" 1109 | }, 1110 | "cookie-signature": { 1111 | "version": "1.0.6", 1112 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 1113 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 1114 | }, 1115 | "cors": { 1116 | "version": "2.8.5", 1117 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 1118 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 1119 | "requires": { 1120 | "object-assign": "^4", 1121 | "vary": "^1" 1122 | } 1123 | }, 1124 | "denque": { 1125 | "version": "2.0.1", 1126 | "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", 1127 | "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==" 1128 | }, 1129 | "depd": { 1130 | "version": "1.1.2", 1131 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 1132 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 1133 | }, 1134 | "destroy": { 1135 | "version": "1.0.4", 1136 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 1137 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 1138 | }, 1139 | "ee-first": { 1140 | "version": "1.1.1", 1141 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 1142 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 1143 | }, 1144 | "encodeurl": { 1145 | "version": "1.0.2", 1146 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 1147 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 1148 | }, 1149 | "engine.io": { 1150 | "version": "6.1.1", 1151 | "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.1.tgz", 1152 | "integrity": "sha512-AyMc20q8JUUdvKd46+thc9o7yCZ6iC6MoBCChG5Z1XmFMpp+2+y/oKvwpZTUJB0KCjxScw1dV9c2h5pjiYBLuQ==", 1153 | "requires": { 1154 | "@types/cookie": "^0.4.1", 1155 | "@types/cors": "^2.8.12", 1156 | "@types/node": ">=10.0.0", 1157 | "accepts": "~1.3.4", 1158 | "base64id": "2.0.0", 1159 | "cookie": "~0.4.1", 1160 | "cors": "~2.8.5", 1161 | "debug": "~4.3.1", 1162 | "engine.io-parser": "~5.0.0", 1163 | "ws": "~8.2.3" 1164 | }, 1165 | "dependencies": { 1166 | "debug": { 1167 | "version": "4.3.3", 1168 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 1169 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 1170 | "requires": { 1171 | "ms": "2.1.2" 1172 | } 1173 | }, 1174 | "ms": { 1175 | "version": "2.1.2", 1176 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1177 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1178 | } 1179 | } 1180 | }, 1181 | "engine.io-parser": { 1182 | "version": "5.0.3", 1183 | "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", 1184 | "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", 1185 | "requires": { 1186 | "@socket.io/base64-arraybuffer": "~1.0.2" 1187 | } 1188 | }, 1189 | "escape-html": { 1190 | "version": "1.0.3", 1191 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 1192 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 1193 | }, 1194 | "etag": { 1195 | "version": "1.8.1", 1196 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 1197 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 1198 | }, 1199 | "express": { 1200 | "version": "4.17.1", 1201 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 1202 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 1203 | "requires": { 1204 | "accepts": "~1.3.7", 1205 | "array-flatten": "1.1.1", 1206 | "body-parser": "1.19.0", 1207 | "content-disposition": "0.5.3", 1208 | "content-type": "~1.0.4", 1209 | "cookie": "0.4.0", 1210 | "cookie-signature": "1.0.6", 1211 | "debug": "2.6.9", 1212 | "depd": "~1.1.2", 1213 | "encodeurl": "~1.0.2", 1214 | "escape-html": "~1.0.3", 1215 | "etag": "~1.8.1", 1216 | "finalhandler": "~1.1.2", 1217 | "fresh": "0.5.2", 1218 | "merge-descriptors": "1.0.1", 1219 | "methods": "~1.1.2", 1220 | "on-finished": "~2.3.0", 1221 | "parseurl": "~1.3.3", 1222 | "path-to-regexp": "0.1.7", 1223 | "proxy-addr": "~2.0.5", 1224 | "qs": "6.7.0", 1225 | "range-parser": "~1.2.1", 1226 | "safe-buffer": "5.1.2", 1227 | "send": "0.17.1", 1228 | "serve-static": "1.14.1", 1229 | "setprototypeof": "1.1.1", 1230 | "statuses": "~1.5.0", 1231 | "type-is": "~1.6.18", 1232 | "utils-merge": "1.0.1", 1233 | "vary": "~1.1.2" 1234 | }, 1235 | "dependencies": { 1236 | "cookie": { 1237 | "version": "0.4.0", 1238 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 1239 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 1240 | }, 1241 | "debug": { 1242 | "version": "2.6.9", 1243 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1244 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1245 | "requires": { 1246 | "ms": "2.0.0" 1247 | } 1248 | }, 1249 | "ms": { 1250 | "version": "2.0.0", 1251 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1252 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1253 | } 1254 | } 1255 | }, 1256 | "fast-geoip": { 1257 | "version": "1.1.54", 1258 | "resolved": "https://registry.npmjs.org/fast-geoip/-/fast-geoip-1.1.54.tgz", 1259 | "integrity": "sha512-X3tpXwYWh+aPn69mHBnWleOBNTSuN6eutF8olXCEBekdYj5ERFnNDPGhiqQbjnJT+fVLx3vWnvanX2EpLcakCQ==" 1260 | }, 1261 | "finalhandler": { 1262 | "version": "1.1.2", 1263 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 1264 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 1265 | "requires": { 1266 | "debug": "2.6.9", 1267 | "encodeurl": "~1.0.2", 1268 | "escape-html": "~1.0.3", 1269 | "on-finished": "~2.3.0", 1270 | "parseurl": "~1.3.3", 1271 | "statuses": "~1.5.0", 1272 | "unpipe": "~1.0.0" 1273 | }, 1274 | "dependencies": { 1275 | "debug": { 1276 | "version": "2.6.9", 1277 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1278 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1279 | "requires": { 1280 | "ms": "2.0.0" 1281 | } 1282 | }, 1283 | "ms": { 1284 | "version": "2.0.0", 1285 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1286 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1287 | } 1288 | } 1289 | }, 1290 | "forwarded": { 1291 | "version": "0.2.0", 1292 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 1293 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 1294 | }, 1295 | "fresh": { 1296 | "version": "0.5.2", 1297 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 1298 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 1299 | }, 1300 | "http-errors": { 1301 | "version": "1.7.2", 1302 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 1303 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 1304 | "requires": { 1305 | "depd": "~1.1.2", 1306 | "inherits": "2.0.3", 1307 | "setprototypeof": "1.1.1", 1308 | "statuses": ">= 1.5.0 < 2", 1309 | "toidentifier": "1.0.0" 1310 | } 1311 | }, 1312 | "iconv-lite": { 1313 | "version": "0.4.24", 1314 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 1315 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 1316 | "requires": { 1317 | "safer-buffer": ">= 2.1.2 < 3" 1318 | } 1319 | }, 1320 | "ieee754": { 1321 | "version": "1.2.1", 1322 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 1323 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 1324 | }, 1325 | "inherits": { 1326 | "version": "2.0.3", 1327 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 1328 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 1329 | }, 1330 | "ip": { 1331 | "version": "1.1.5", 1332 | "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", 1333 | "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" 1334 | }, 1335 | "ipaddr.js": { 1336 | "version": "1.9.1", 1337 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 1338 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 1339 | }, 1340 | "media-typer": { 1341 | "version": "0.3.0", 1342 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1343 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 1344 | }, 1345 | "memory-pager": { 1346 | "version": "1.5.0", 1347 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 1348 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 1349 | "optional": true 1350 | }, 1351 | "merge-descriptors": { 1352 | "version": "1.0.1", 1353 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 1354 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 1355 | }, 1356 | "methods": { 1357 | "version": "1.1.2", 1358 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1359 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 1360 | }, 1361 | "mime": { 1362 | "version": "1.6.0", 1363 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1364 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 1365 | }, 1366 | "mime-db": { 1367 | "version": "1.51.0", 1368 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", 1369 | "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" 1370 | }, 1371 | "mime-types": { 1372 | "version": "2.1.34", 1373 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", 1374 | "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", 1375 | "requires": { 1376 | "mime-db": "1.51.0" 1377 | } 1378 | }, 1379 | "mongodb": { 1380 | "version": "4.3.0", 1381 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.3.0.tgz", 1382 | "integrity": "sha512-ovq9ZD9wEvab+LsaQOiwtne1Sy2egaHW8K/H5M18Tv+V5PgTRi+qdmxDGlbm94TSL3h56m6amstptu115Nzgow==", 1383 | "requires": { 1384 | "bson": "^4.6.1", 1385 | "denque": "^2.0.1", 1386 | "mongodb-connection-string-url": "^2.3.2", 1387 | "saslprep": "^1.0.3", 1388 | "socks": "^2.6.1" 1389 | } 1390 | }, 1391 | "mongodb-connection-string-url": { 1392 | "version": "2.4.1", 1393 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.4.1.tgz", 1394 | "integrity": "sha512-d5Kd2bVsKcSA7YI/yo57fSTtMwRQdFkvc5IZwod1RRxJtECeWPPSo7zqcUGJELifRA//Igs4spVtYAmvFCatug==", 1395 | "requires": { 1396 | "@types/whatwg-url": "^8.2.1", 1397 | "whatwg-url": "^11.0.0" 1398 | } 1399 | }, 1400 | "negotiator": { 1401 | "version": "0.6.2", 1402 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 1403 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 1404 | }, 1405 | "object-assign": { 1406 | "version": "4.1.1", 1407 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1408 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 1409 | }, 1410 | "on-finished": { 1411 | "version": "2.3.0", 1412 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1413 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1414 | "requires": { 1415 | "ee-first": "1.1.1" 1416 | } 1417 | }, 1418 | "parseurl": { 1419 | "version": "1.3.3", 1420 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1421 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 1422 | }, 1423 | "path-to-regexp": { 1424 | "version": "0.1.7", 1425 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1426 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 1427 | }, 1428 | "proxy-addr": { 1429 | "version": "2.0.7", 1430 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1431 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1432 | "requires": { 1433 | "forwarded": "0.2.0", 1434 | "ipaddr.js": "1.9.1" 1435 | } 1436 | }, 1437 | "punycode": { 1438 | "version": "2.1.1", 1439 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1440 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 1441 | }, 1442 | "qs": { 1443 | "version": "6.7.0", 1444 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 1445 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 1446 | }, 1447 | "range-parser": { 1448 | "version": "1.2.1", 1449 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1450 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 1451 | }, 1452 | "raw-body": { 1453 | "version": "2.4.0", 1454 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 1455 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 1456 | "requires": { 1457 | "bytes": "3.1.0", 1458 | "http-errors": "1.7.2", 1459 | "iconv-lite": "0.4.24", 1460 | "unpipe": "1.0.0" 1461 | } 1462 | }, 1463 | "safe-buffer": { 1464 | "version": "5.1.2", 1465 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1466 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1467 | }, 1468 | "safer-buffer": { 1469 | "version": "2.1.2", 1470 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1471 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1472 | }, 1473 | "saslprep": { 1474 | "version": "1.0.3", 1475 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", 1476 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", 1477 | "optional": true, 1478 | "requires": { 1479 | "sparse-bitfield": "^3.0.3" 1480 | } 1481 | }, 1482 | "send": { 1483 | "version": "0.17.1", 1484 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 1485 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 1486 | "requires": { 1487 | "debug": "2.6.9", 1488 | "depd": "~1.1.2", 1489 | "destroy": "~1.0.4", 1490 | "encodeurl": "~1.0.2", 1491 | "escape-html": "~1.0.3", 1492 | "etag": "~1.8.1", 1493 | "fresh": "0.5.2", 1494 | "http-errors": "~1.7.2", 1495 | "mime": "1.6.0", 1496 | "ms": "2.1.1", 1497 | "on-finished": "~2.3.0", 1498 | "range-parser": "~1.2.1", 1499 | "statuses": "~1.5.0" 1500 | }, 1501 | "dependencies": { 1502 | "debug": { 1503 | "version": "2.6.9", 1504 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1505 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1506 | "requires": { 1507 | "ms": "2.0.0" 1508 | }, 1509 | "dependencies": { 1510 | "ms": { 1511 | "version": "2.0.0", 1512 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1513 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1514 | } 1515 | } 1516 | }, 1517 | "ms": { 1518 | "version": "2.1.1", 1519 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 1520 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 1521 | } 1522 | } 1523 | }, 1524 | "serve-static": { 1525 | "version": "1.14.1", 1526 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 1527 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 1528 | "requires": { 1529 | "encodeurl": "~1.0.2", 1530 | "escape-html": "~1.0.3", 1531 | "parseurl": "~1.3.3", 1532 | "send": "0.17.1" 1533 | } 1534 | }, 1535 | "setprototypeof": { 1536 | "version": "1.1.1", 1537 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 1538 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 1539 | }, 1540 | "smart-buffer": { 1541 | "version": "4.2.0", 1542 | "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", 1543 | "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" 1544 | }, 1545 | "socket.io": { 1546 | "version": "4.4.1", 1547 | "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.1.tgz", 1548 | "integrity": "sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg==", 1549 | "requires": { 1550 | "accepts": "~1.3.4", 1551 | "base64id": "~2.0.0", 1552 | "debug": "~4.3.2", 1553 | "engine.io": "~6.1.0", 1554 | "socket.io-adapter": "~2.3.3", 1555 | "socket.io-parser": "~4.0.4" 1556 | }, 1557 | "dependencies": { 1558 | "debug": { 1559 | "version": "4.3.3", 1560 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 1561 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 1562 | "requires": { 1563 | "ms": "2.1.2" 1564 | } 1565 | }, 1566 | "ms": { 1567 | "version": "2.1.2", 1568 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1569 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1570 | } 1571 | } 1572 | }, 1573 | "socket.io-adapter": { 1574 | "version": "2.3.3", 1575 | "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", 1576 | "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==" 1577 | }, 1578 | "socket.io-parser": { 1579 | "version": "4.0.4", 1580 | "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", 1581 | "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", 1582 | "requires": { 1583 | "@types/component-emitter": "^1.2.10", 1584 | "component-emitter": "~1.3.0", 1585 | "debug": "~4.3.1" 1586 | }, 1587 | "dependencies": { 1588 | "debug": { 1589 | "version": "4.3.3", 1590 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 1591 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 1592 | "requires": { 1593 | "ms": "2.1.2" 1594 | } 1595 | }, 1596 | "ms": { 1597 | "version": "2.1.2", 1598 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1599 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1600 | } 1601 | } 1602 | }, 1603 | "socks": { 1604 | "version": "2.6.1", 1605 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", 1606 | "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", 1607 | "requires": { 1608 | "ip": "^1.1.5", 1609 | "smart-buffer": "^4.1.0" 1610 | } 1611 | }, 1612 | "sparse-bitfield": { 1613 | "version": "3.0.3", 1614 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 1615 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", 1616 | "optional": true, 1617 | "requires": { 1618 | "memory-pager": "^1.0.2" 1619 | } 1620 | }, 1621 | "statuses": { 1622 | "version": "1.5.0", 1623 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1624 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 1625 | }, 1626 | "toidentifier": { 1627 | "version": "1.0.0", 1628 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 1629 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 1630 | }, 1631 | "tr46": { 1632 | "version": "3.0.0", 1633 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", 1634 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", 1635 | "requires": { 1636 | "punycode": "^2.1.1" 1637 | } 1638 | }, 1639 | "type-is": { 1640 | "version": "1.6.18", 1641 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1642 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1643 | "requires": { 1644 | "media-typer": "0.3.0", 1645 | "mime-types": "~2.1.24" 1646 | } 1647 | }, 1648 | "unpipe": { 1649 | "version": "1.0.0", 1650 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1651 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 1652 | }, 1653 | "utils-merge": { 1654 | "version": "1.0.1", 1655 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1656 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 1657 | }, 1658 | "vary": { 1659 | "version": "1.1.2", 1660 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1661 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 1662 | }, 1663 | "webidl-conversions": { 1664 | "version": "7.0.0", 1665 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", 1666 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" 1667 | }, 1668 | "whatwg-url": { 1669 | "version": "11.0.0", 1670 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", 1671 | "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", 1672 | "requires": { 1673 | "tr46": "^3.0.0", 1674 | "webidl-conversions": "^7.0.0" 1675 | } 1676 | }, 1677 | "ws": { 1678 | "version": "8.2.3", 1679 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", 1680 | "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", 1681 | "requires": {} 1682 | } 1683 | } 1684 | } 1685 | -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "open-analytics-core", 3 | "version": "1.1.0", 4 | "description": "Open analytics backend.", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node index" 8 | }, 9 | "author": "Daniel", 10 | "license": "MIT", 11 | "dependencies": { 12 | "cors": "^2.8.5", 13 | "express": "^4.17.1", 14 | "fast-geoip": "^1.1.54", 15 | "mongodb": "^4.3.0", 16 | "socket.io": "^4.4.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /api/server.js: -------------------------------------------------------------------------------- 1 | const assignUser = require("./lib/assignUser"); 2 | const geoip = require("fast-geoip"); 3 | const express = require("express"); 4 | const { MongoClient } = require("mongodb"); 5 | const app = express(); 6 | const config = require("../src/config.json"); 7 | const fs = require('fs'); 8 | 9 | tls_key = () => { 10 | try { 11 | return fs.readFileSync(config.tls_support.key); 12 | } catch (err) { 13 | return ""; 14 | console.log(err); 15 | } 16 | } 17 | 18 | tls_cert = () => { 19 | try { 20 | return fs.readFileSync(config.tls_support.cert); 21 | } catch (err) { 22 | return ""; 23 | console.log(err); 24 | } 25 | } 26 | 27 | const option = { 28 | key: tls_key, 29 | cert: tls_cert, 30 | cors: { 31 | origin: '*', 32 | } 33 | } 34 | 35 | const http = require(config.tls_support.enabled ? "https" : "http").createServer(option, app); 36 | const io = require("socket.io")(http, { 37 | cors: { 38 | origin: "*", 39 | }, 40 | }); 41 | 42 | const cors = require("cors"); 43 | 44 | const PORT = config.api.port; 45 | let count = 0; 46 | let totalClients = []; 47 | 48 | // Connect to MongoDB 49 | const uri = config.api.uri; 50 | const MClient = new MongoClient(uri); 51 | MClient.connect().catch(console.error); 52 | 53 | let results; 54 | app.use(async (req, res, next) => { 55 | let lastMonth = new Date( 56 | new Date().getFullYear(), 57 | new Date().getMonth() - 1, 58 | new Date().getDate() 59 | ); 60 | results = await MClient.db(config.api.databaseName) 61 | .collection(config.api.collectionName) 62 | .find({Date: {$gt: lastMonth}}) 63 | .toArray(); 64 | next(); 65 | }); 66 | 67 | app.get("/api", cors(), (req, res) => { 68 | res.send(results); 69 | }); 70 | 71 | app.get("/api/active", cors(), (req, res) => { 72 | res.send(count.toString()); 73 | }); 74 | 75 | // On connection 76 | io.on("connection", async (socket) => { 77 | let clientIp = socket.request.connection.remoteAddress; 78 | let clientHeader = socket.request.headers["user-agent"]; 79 | let geo = await geoip.lookup(clientIp.slice(7)); // Check location by ip (Does not work for local ip addresses) 80 | let clientURL; 81 | let clientReferrer; 82 | let isNewUser; 83 | 84 | // Add user to array 85 | totalClients.push(clientIp); 86 | 87 | // The number of unique clients from the array 88 | count = totalClients.filter(function (item, pos) { 89 | return totalClients.indexOf(item) == pos; 90 | }).length; 91 | 92 | const connectDate = new Date(); 93 | 94 | console.clear(); // Clear previous log 95 | 96 | io.emit("socketClientID", socket.client.id); 97 | socket.on("clientMessage", (data) => { // Get data from client 98 | clientURL = data.url; 99 | clientReferrer = data.referrer; 100 | isNewUser = data.isFirstVisit; 101 | isNewUser = data.isFirstVisit; 102 | }); 103 | 104 | // On disconnection 105 | socket.on("disconnect", () => { 106 | const disconnectDate = new Date(); 107 | let activeTime = Math.ceil( 108 | (disconnectDate - connectDate) / 1000 109 | ).toString(); 110 | 111 | // Remove user from array & update the number of unique clients from the array 112 | totalClients.splice(totalClients.indexOf(clientIp), 1); 113 | count = totalClients.filter(function (item, pos) { 114 | return totalClients.indexOf(item) == pos; 115 | }).length; 116 | 117 | let user = assignUser( 118 | clientIp, 119 | geo.country, 120 | clientHeader, 121 | clientURL, 122 | clientReferrer, 123 | connectDate, 124 | activeTime, 125 | isNewUser 126 | ); 127 | 128 | // Add to DB 129 | MClient.db(config.api.databaseName).collection(config.api.collectionName).insertOne(user); 130 | }); 131 | }); 132 | 133 | http.listen(PORT, () => { 134 | console.clear(); 135 | console.log("ALL SET!"); 136 | }); 137 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pen-analytics", 3 | "description": "Self-hosted, accurate analytics tool for your website in a clean minimalist interface.", 4 | "version": "0.1.0", 5 | "private": true, 6 | "dependencies": { 7 | "@testing-library/jest-dom": "^5.16.1", 8 | "@testing-library/react": "^12.1.2", 9 | "@testing-library/user-event": "^13.5.0", 10 | "chart.js": "^3.7.0", 11 | "react": "^17.0.2", 12 | "react-chartjs-2": "^4.0.1", 13 | "react-dom": "^17.0.2", 14 | "react-scripts": "5.0.0" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject" 21 | }, 22 | "eslintConfig": { 23 | "extends": [ 24 | "react-app", 25 | "react-app/jest" 26 | ] 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel31x13/open-analytics/200808ca75f2fa68e49a8253aa418d0aea959e92/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | React App 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "theme_color": "#000000", 7 | "background_color": "#ffffff" 8 | } 9 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 650px) { 2 | .group { 3 | border-radius: 10px; 4 | display: flex; 5 | justify-content: space-evenly; 6 | } 7 | 8 | .group > div { 9 | width: 100%; 10 | } 11 | } 12 | 13 | .container { 14 | display: flex; 15 | flex-direction: column; 16 | padding: 10px; 17 | text-align: center; 18 | max-width: 1200px; 19 | margin-right: auto; 20 | margin-left: auto; 21 | } 22 | 23 | .container > div { 24 | box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 4px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -3px 0px inset; 25 | margin-bottom: 10px; 26 | padding: 10px; 27 | } 28 | 29 | .About { 30 | border-radius: 10px; 31 | text-align: center; 32 | } 33 | 34 | .rainbowEffect { 35 | font-size: 1.5em; 36 | font-weight: 700; 37 | text-decoration: underline; 38 | font-family: monospace; 39 | } 40 | 41 | .rainbowEffect:hover { 42 | font-size: 1.5em; 43 | font-weight: 700; 44 | text-decoration: none; 45 | font-family: monospace; 46 | animation: colorRotate 5s linear 0s infinite; 47 | } 48 | 49 | @keyframes colorRotate { 50 | from { 51 | color: #6666ff; 52 | } 53 | 10% { 54 | color: #0099ff; 55 | } 56 | 50% { 57 | color: #00ff00; 58 | } 59 | 75% { 60 | color: #ff3399; 61 | } 62 | 100% { 63 | color: #6666ff; 64 | } 65 | } 66 | 67 | .About h1 { 68 | font-weight: 300; 69 | color: #444444; 70 | background: #FFFFFF; 71 | text-shadow: 1px 0px 1px #CCCCCC, 0px 1px 1px #EEEEEE, 2px 1px 1px #CCCCCC, 1px 2px 1px #EEEEEE, 3px 2px 1px #CCCCCC, 2px 3px 1px #EEEEEE, 4px 3px 1px #CCCCCC, 3px 4px 1px #EEEEEE, 5px 4px 1px #CCCCCC, 4px 5px 1px #EEEEEE, 6px 5px 1px #CCCCCC, 5px 6px 1px #EEEEEE, 7px 6px 1px #CCCCCC; 72 | color: #444444; 73 | background: #FFFFFF; 74 | } 75 | 76 | .About h5 { 77 | color: gray; 78 | } 79 | 80 | .ActiveVisitors { 81 | border-radius: 10px; 82 | text-align: center; 83 | } 84 | 85 | .AvgVisitDuration { 86 | border-radius: 10px; 87 | text-align: center; 88 | } 89 | 90 | .VisitsByCountry { 91 | border-radius: 10px; 92 | text-align: center; 93 | height: 250px; 94 | } 95 | 96 | .Devices { 97 | border-radius: 10px; 98 | text-align: center; 99 | height: 250px; 100 | } 101 | 102 | .TopPages { 103 | height: 300px; 104 | border-radius: 10px; 105 | text-align: center; 106 | } 107 | 108 | .HoursOfActivityPerDay { 109 | height: 300px; 110 | border-radius: 10px; 111 | text-align: center; 112 | } 113 | 114 | .NewVsReturn { 115 | height: 300px; 116 | border-radius: 10px; 117 | text-align: center; 118 | } 119 | 120 | .Referrer { 121 | height: 300px; 122 | border-radius: 10px; 123 | text-align: center; 124 | } 125 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import './App.css'; 3 | import ActiveVisitors from './component/ActiveVisitors'; 4 | import AvgVisitDuration from './component/AvgVisitDuration'; 5 | import Devices from './component/Devices'; 6 | import NewVsReturn from './component/NewVsReturn'; 7 | import TopPages from './component/TopPages'; 8 | import VisitsByCountry from './component/VisitsByCountry'; 9 | import HoursOfActivityPerDay from './component/HoursOfActivityPerDay'; 10 | import About from './component/About'; 11 | import Referrer from './component/Referrer'; 12 | import config from "./config.json"; 13 | 14 | function App() { 15 | const [stats, setStats] = useState([]); 16 | const [active, setActive] = useState("0"); 17 | 18 | useEffect(() => { 19 | async function fetchStats() { 20 | const res = await fetch(config.api.address + ":" + config.api.port + '/api'); 21 | const data = await res.json(); 22 | data.sort((b, a) => new Date(b.Date) - new Date(a.Date)) // Sort by date 23 | setStats(data); 24 | } 25 | fetchStats(); 26 | async function fetchActiveUsers() { 27 | const res = await fetch(config.api.address + ":" + config.api.port + "/api/active"); 28 | const data = await res.text(); 29 | setActive(parseInt(data)); 30 | } 31 | fetchActiveUsers(); 32 | }, []); 33 | 34 | return ( 35 |
36 | 37 |
38 | 39 | 40 |
41 |
42 | 43 | 44 |
45 |
46 | 47 | 48 |
49 | 50 | 51 |
52 | ); 53 | } 54 | 55 | export default App; 56 | -------------------------------------------------------------------------------- /src/component/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const About = () => { 4 | return (
5 |

Open Analytics

6 |

A somewhat "Minimalist" SEO analytic for your website.

7 |

Check it out on my GitHub!

8 |
); 9 | }; 10 | 11 | export default About; 12 | -------------------------------------------------------------------------------- /src/component/ActiveVisitors.js: -------------------------------------------------------------------------------- 1 | const ActiveVisitors = ({active}) => { 2 | return ( 3 |
4 | Right now: 5 |

{active.toString()}

6 | Active Users 7 |
8 | ); 9 | }; 10 | 11 | export default ActiveVisitors; 12 | -------------------------------------------------------------------------------- /src/component/AvgVisitDuration.js: -------------------------------------------------------------------------------- 1 | const AvgVisitDuration = ({ time }) => { 2 | function getAvg() { 3 | let Times = time.map((entity) => {return parseInt(entity.ActiveTimeInSecond)}); 4 | Times = Times.filter((entity) => {return !Number.isNaN(entity)}); 5 | 6 | const average = Times.reduce((a, b) => a + b, 0) / Times.length 7 | 8 | if(!isNaN(average)) { 9 | return (Math.round(average * 100) / 100).toString(); 10 | } 11 | 12 | else { 13 | return "0"; 14 | } 15 | } 16 | 17 | return
Average time on each page:

{getAvg()}

Seconds
; 18 | }; 19 | 20 | export default AvgVisitDuration; 21 | -------------------------------------------------------------------------------- /src/component/Devices.js: -------------------------------------------------------------------------------- 1 | import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'; 2 | import { Pie } from 'react-chartjs-2'; 3 | 4 | const Devices = ({devices}) => { 5 | function getDevicesKeys() { 6 | const Devices = devices.map((entity) => {return entity.IsMobile}); 7 | 8 | let deviceStats = Devices.reduce((a, b) => a.set(b, (a.get(b) || 0) + 1), new Map()); 9 | let devicesKeys = [...deviceStats.keys()].map(entity => entity = entity.toString()); 10 | 11 | return devicesKeys; 12 | } 13 | 14 | function getDevicesValue() { 15 | const Devices = devices.map((entity) => {return entity.IsMobile}); 16 | 17 | let deviceStats = Devices.reduce((a, b) => a.set(b, (a.get(b) || 0) + 1), new Map()); 18 | let devicesValues = [...deviceStats.values()]; 19 | 20 | return devicesValues; 21 | } 22 | 23 | const data = { 24 | labels: getDevicesKeys(), 25 | datasets: [ 26 | { 27 | label: 'Devices', 28 | data: getDevicesValue(), 29 | backgroundColor: [ 30 | 'rgba(255, 99, 132, 0.2)', 31 | 'rgba(54, 162, 235, 0.2)', 32 | ], 33 | borderWidth: 2, 34 | borderColor: 'rgba(100, 0, 100, 1)', 35 | borderRadius: 5 36 | }, 37 | ], 38 | }; 39 | 40 | return (
41 | 48 |
); 49 | }; 50 | 51 | export default Devices; 52 | -------------------------------------------------------------------------------- /src/component/HoursOfActivityPerDay.js: -------------------------------------------------------------------------------- 1 | import { 2 | Chart as ChartJS, 3 | CategoryScale, 4 | LinearScale, 5 | BarElement, 6 | Title, 7 | Tooltip, 8 | Legend, 9 | } from 'chart.js'; 10 | import { Bar } from 'react-chartjs-2'; 11 | 12 | const HoursOfActivityPerDay = ({ activity }) => { 13 | let rawData = activity.map((entity) => { // Parse the information so that only the Date (as the key) and the time (as the value) is extracted 14 | const date = new Date(entity.Date).toISOString().substring(0, 10) 15 | const num = parseInt(entity.ActiveTimeInSecond); 16 | const obj = { [date]: num } 17 | return obj; 18 | }); 19 | 20 | let Data = {}; 21 | 22 | for(let i = 0; i < rawData.length; ++i){ // If the keys were the same then add the values together 23 | for(let obj in rawData[i]){ 24 | Data[obj] = Data[obj] ? Data[obj] + rawData[i][obj] : rawData[i][obj]; 25 | } 26 | } 27 | 28 | const date = Object.keys(Data); 29 | const Sec = Object.values(Data).map((entity) => { 30 | return (Math.round(entity / 60 * 100) / 100) 31 | }); 32 | 33 | const labels = date; 34 | 35 | const data = { 36 | labels, 37 | datasets: [ 38 | { 39 | label: 'Minutes', 40 | data: Sec, 41 | backgroundColor: 'rgba(0, 116, 204, 0.2)', 42 | borderColor: 'rgba(0, 116, 204, 1)', 43 | borderWidth: 2, 44 | maxBarThickness: 50 45 | } 46 | ], 47 | }; 48 | 49 | return (
50 | 55 |
); 56 | }; 57 | 58 | export default HoursOfActivityPerDay; 59 | -------------------------------------------------------------------------------- /src/component/NewVsReturn.js: -------------------------------------------------------------------------------- 1 | import 'chart.js/auto'; 2 | import { Line } from "react-chartjs-2"; 3 | 4 | const NewVsReturn = ({users}) => { 5 | let rawData = users.map((entity) => { // Parse the information so that only the Date (as the key) and the ip (as the value) is extracted 6 | const date = new Date(entity.Date).toISOString().substring(0, 10) 7 | const isNewUser = entity.isNewUser; 8 | const obj = { x: date, y: +isNewUser } 9 | return obj; 10 | }); 11 | 12 | let Dates = rawData.map((entity) => { 13 | const obj = entity.x; 14 | return obj; 15 | }).filter(entity => {return entity}); 16 | 17 | let uniqueDates = [...new Set(Dates)].sort((a, b) => { 18 | return new Date(a) - new Date(b); 19 | }); 20 | 21 | function newUsers() { 22 | let rawUsers = rawData.filter((entity) => { 23 | return Boolean(entity.y) 24 | }); 25 | 26 | for(const i in rawUsers) { 27 | let counts = 0; 28 | rawUsers.forEach((x) => {if (x.x === rawUsers[i].x) counts++}); 29 | rawUsers[i].y = counts; 30 | } 31 | 32 | 33 | return rawUsers.filter((value, index, self) => index === self.findIndex((t) => t.x === value.x)); 34 | } 35 | 36 | function returningUsers() { 37 | let rawUsers = rawData.filter((entity) => { 38 | return !Boolean(entity.y) 39 | }).map((entity) => { 40 | return { x: entity.x , y: 1 } 41 | }); 42 | 43 | for(const i in rawUsers) { 44 | let counts = 0; 45 | rawUsers.forEach((x) => {if (x.x === rawUsers[i].x) counts++}); 46 | rawUsers[i].y = counts; 47 | } 48 | 49 | return rawUsers.filter((value, index, self) => index === self.findIndex((t) => t.x === value.x)); 50 | } 51 | 52 | const data = { 53 | labels: uniqueDates, 54 | datasets: [ 55 | { 56 | label: "New Users", 57 | data: newUsers(), 58 | fill: true, 59 | backgroundColor: "rgba(75,192,192,0.2)", 60 | borderColor: "rgba(75,192,192,1)", 61 | borderWidth: 2, 62 | tension: 0.3 63 | }, 64 | { 65 | label: "Returning Users", 66 | data: returningUsers(), 67 | fill: true, 68 | backgroundColor: "rgba(0, 116, 204, 0.2)", 69 | borderColor: "rgba(0, 116, 204, 1)", 70 | borderWidth: 2, 71 | tension: 0.3 72 | } 73 | ] 74 | }; 75 | 76 | return (
77 | 81 |
); 82 | }; 83 | 84 | export default NewVsReturn; 85 | -------------------------------------------------------------------------------- /src/component/Referrer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Chart as ChartJS, 4 | CategoryScale, 5 | LinearScale, 6 | BarElement, 7 | Title, 8 | Tooltip, 9 | Legend, 10 | } from 'chart.js'; 11 | import { Bar } from 'react-chartjs-2'; 12 | 13 | const Referrer = ({ links }) => { 14 | function getTopLinksKeys() { 15 | const Links = links.map((entity) => {return entity.Referrer}); 16 | 17 | let stats = Links.reduce((a, b) => a.set(b, (a.get(b) || 0) + 1), new Map()); 18 | // let topKeys = [...stats.keys()]; 19 | // let topValues = [...stats.values()]; 20 | let topEntries = [...stats.entries()]; 21 | 22 | topEntries.sort((function(index){ 23 | return function(a, b){ 24 | return (a[index] === b[index] ? 0 : (b[index] < a[index] ? -1 : 1)); 25 | }; 26 | })(1)); 27 | 28 | let sortedTop = topEntries.map((i) => { return i[0] }); 29 | sortedTop = sortedTop.map((i) => { 30 | if(i == "") { 31 | return 'Browser'; 32 | } 33 | else { 34 | return i; 35 | } 36 | }); 37 | return sortedTop; 38 | } 39 | 40 | function getTopLinksValues() { 41 | const Links = links.map((entity) => {return entity.Referrer}); 42 | 43 | let stats = Links.reduce((a, b) => a.set(b, (a.get(b) || 0) + 1), new Map()); 44 | // let topKeys = [...stats.keys()]; 45 | // let topValues = [...stats.values()]; 46 | let topEntries = [...stats.entries()]; 47 | 48 | topEntries.sort((function(index){ 49 | return function(a, b){ 50 | return (a[index] === b[index] ? 0 : (b[index] < a[index] ? -1 : 1)); 51 | }; 52 | })(1)); 53 | 54 | const sortedTop = topEntries.map((i) => { return i[1] }); 55 | 56 | return sortedTop; 57 | } 58 | 59 | const labels = getTopLinksKeys().slice(0, 5); 60 | 61 | const data = { 62 | labels, 63 | datasets: [ 64 | { 65 | label: 'Referrer', 66 | data: getTopLinksValues().slice(0, 5), 67 | backgroundColor: 'rgba(0, 116, 204, 0.2)', 68 | borderColor: 'rgba(0, 116, 204, 1)', 69 | borderWidth: 2, 70 | maxBarThickness: 50 71 | } 72 | ], 73 | }; 74 | 75 | return (
76 | 81 |
); 82 | }; 83 | 84 | export default Referrer; 85 | -------------------------------------------------------------------------------- /src/component/TopPages.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Chart as ChartJS, 4 | CategoryScale, 5 | LinearScale, 6 | BarElement, 7 | Title, 8 | Tooltip, 9 | Legend, 10 | } from 'chart.js'; 11 | import { Bar } from 'react-chartjs-2'; 12 | 13 | const TopPages = ({pages}) => { 14 | function getTopPagesKeys() { 15 | const Pages = pages.map((entity) => {return entity.Url}); 16 | 17 | let stats = Pages.reduce((a, b) => a.set(b, (a.get(b) || 0) + 1), new Map()); 18 | // let topKeys = [...stats.keys()]; 19 | // let topValues = [...stats.values()]; 20 | let topEntries = [...stats.entries()]; 21 | 22 | topEntries.sort((function(index){ 23 | return function(a, b){ 24 | return (a[index] === b[index] ? 0 : (b[index] < a[index] ? -1 : 1)); 25 | }; 26 | })(1)); 27 | 28 | const sortedTop = topEntries.map((i) => { return i[0] }); 29 | 30 | return sortedTop; 31 | } 32 | 33 | function getTopPagesValues() { 34 | const Pages = pages.map((entity) => {return entity.Url}); 35 | 36 | let stats = Pages.reduce((a, b) => a.set(b, (a.get(b) || 0) + 1), new Map()); 37 | // let topKeys = [...stats.keys()]; 38 | // let topValues = [...stats.values()]; 39 | let topEntries = [...stats.entries()]; 40 | 41 | topEntries.sort((function(index){ 42 | return function(a, b){ 43 | return (a[index] === b[index] ? 0 : (b[index] < a[index] ? -1 : 1)); 44 | }; 45 | })(1)); 46 | 47 | const sortedTop = topEntries.map((i) => { return i[1] }); 48 | 49 | return sortedTop; 50 | } 51 | 52 | 53 | 54 | const labels = getTopPagesKeys().slice(0, 5); 55 | 56 | const data = { 57 | labels, 58 | datasets: [ 59 | { 60 | label: 'Links', 61 | data: getTopPagesValues().slice(0, 5), 62 | backgroundColor: 'rgba(0, 116, 204, 0.2)', 63 | borderColor: 'rgba(0, 116, 204, 1)', 64 | borderWidth: 2, 65 | maxBarThickness: 50 66 | } 67 | ], 68 | }; 69 | 70 | return (
71 | 76 |
); 77 | }; 78 | 79 | export default TopPages; 80 | -------------------------------------------------------------------------------- /src/component/VisitsByCountry.js: -------------------------------------------------------------------------------- 1 | import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'; 2 | import { Doughnut } from 'react-chartjs-2'; 3 | 4 | const VisitsByCountry = ({location}) => { 5 | function getTopCountriesKeys() { 6 | const Location = location.map((entity) => {return entity.Location}); 7 | 8 | let stats = Location.reduce((a, b) => a.set(b, (a.get(b) || 0) + 1), new Map()); 9 | // let topKeys = [...stats.keys()]; 10 | // let topValues = [...stats.values()]; 11 | let topEntries = [...stats.entries()]; 12 | 13 | topEntries.sort((function(index){ 14 | return function(a, b){ 15 | return (a[index] === b[index] ? 0 : (b[index] < a[index] ? -1 : 1)); 16 | }; 17 | })(1)); 18 | 19 | const sortedTop = topEntries.map((i) => { return i[0] }); 20 | 21 | return sortedTop; 22 | } 23 | 24 | function getTopCountriesValues() { 25 | const Location = location.map((entity) => {return entity.Location}); 26 | 27 | let stats = Location.reduce((a, b) => a.set(b, (a.get(b) || 0) + 1), new Map()); 28 | // let topKeys = [...stats.keys()]; 29 | // let topValues = [...stats.values()]; 30 | let topEntries = [...stats.entries()]; 31 | 32 | topEntries.sort((function(index){ 33 | return function(a, b){ 34 | return (a[index] === b[index] ? 0 : (b[index] < a[index] ? -1 : 1)); 35 | }; 36 | })(1)); 37 | 38 | const sortedTop = topEntries.map((i) => { return i[1] }); 39 | 40 | return sortedTop; 41 | } 42 | 43 | const data = { 44 | labels: getTopCountriesKeys().slice(0, 5), 45 | datasets: [ 46 | { 47 | label: 'Visits by country', 48 | data: getTopCountriesValues().slice(0, 5), 49 | backgroundColor: [ 50 | 'rgba(255, 99, 132, 0.2)', 51 | 'rgba(54, 162, 235, 0.2)', 52 | 'rgba(255, 206, 86, 0.2)', 53 | 'rgba(75, 192, 192, 0.2)', 54 | 'rgba(153, 102, 255, 0.2)' 55 | ], 56 | borderWidth: 2, 57 | borderColor: 'rgba(100, 0, 100, 1)', 58 | borderRadius: 5 59 | }, 60 | ], 61 | }; 62 | 63 | return (
64 | 71 |
); 72 | }; 73 | 74 | export default VisitsByCountry; 75 | -------------------------------------------------------------------------------- /src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "api": { 3 | "port": 8070, 4 | "uri": "mongodb://localhost:27017", 5 | "databaseName": "DatabaseName", 6 | "collectionName": "CollectionName", 7 | "address": "http://192.168.1.7" 8 | }, 9 | "tls_support": { 10 | "enabled": false, 11 | "key": "", 12 | "cert": "" 13 | } 14 | } -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('root') 9 | ); 10 | --------------------------------------------------------------------------------