├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── client ├── package-lock.json ├── package.json └── src │ ├── api │ ├── auth.js │ └── client.js │ ├── components │ ├── authForm.js │ └── withAuth.js │ ├── next.config.js │ ├── pages │ ├── index.js │ ├── login.js │ ├── private-perm-required.js │ ├── private.js │ └── register.js │ ├── server.js │ └── store.js └── server ├── config ├── default.json └── production.json ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── app.hooks.js ├── app.js ├── authentication.js ├── channels.js ├── hooks │ └── logger.js ├── index.js ├── middleware │ └── index.js ├── models │ └── users.model.js └── services │ ├── counters │ ├── counters.class.js │ ├── counters.hooks.js │ └── counters.service.js │ ├── index.js │ └── users │ ├── users.hooks.js │ └── users.service.js └── test ├── app.test.js └── services ├── counters.test.js └── users.test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true, 5 | "mocha": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 2017 9 | }, 10 | "extends": "eslint:recommended", 11 | "rules": { 12 | "indent": [ 13 | "error", 14 | 2 15 | ], 16 | "linebreak-style": [ 17 | "error", 18 | "unix" 19 | ], 20 | "quotes": [ 21 | "error", 22 | "single" 23 | ], 24 | "semi": [ 25 | "error", 26 | "always" 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | # IDEs and editors (shamelessly copied from @angular/cli's .gitignore) 31 | /.idea 32 | .project 33 | .classpath 34 | .c9/ 35 | *.launch 36 | .settings/ 37 | *.sublime-workspace 38 | 39 | # IDE - VSCode 40 | .vscode/* 41 | !.vscode/settings.json 42 | !.vscode/tasks.json 43 | !.vscode/launch.json 44 | !.vscode/extensions.json 45 | 46 | ### Linux ### 47 | *~ 48 | 49 | # temporary files which can be created if a process still has a handle open of a deleted file 50 | .fuse_hidden* 51 | 52 | # KDE directory preferences 53 | .directory 54 | 55 | # Linux trash folder which might appear on any partition or disk 56 | .Trash-* 57 | 58 | # .nfs files are created when an open file is removed but is still being accessed 59 | .nfs* 60 | 61 | ### OSX ### 62 | *.DS_Store 63 | .AppleDouble 64 | .LSOverride 65 | 66 | # Icon must end with two \r 67 | Icon 68 | 69 | 70 | # Thumbnails 71 | ._* 72 | 73 | # Files that might appear in the root of a volume 74 | .DocumentRevisions-V100 75 | .fseventsd 76 | .Spotlight-V100 77 | .TemporaryItems 78 | .Trashes 79 | .VolumeIcon.icns 80 | .com.apple.timemachine.donotpresent 81 | 82 | # Directories potentially created on remote AFP share 83 | .AppleDB 84 | .AppleDesktop 85 | Network Trash Folder 86 | Temporary Items 87 | .apdisk 88 | 89 | ### Windows ### 90 | # Windows thumbnail cache files 91 | Thumbs.db 92 | ehthumbs.db 93 | ehthumbs_vista.db 94 | 95 | # Folder config file 96 | Desktop.ini 97 | 98 | # Recycle Bin used on file shares 99 | $RECYCLE.BIN/ 100 | 101 | # Windows Installer files 102 | *.cab 103 | *.msi 104 | *.msm 105 | *.msp 106 | 107 | # Windows shortcuts 108 | *.lnk 109 | 110 | # Others 111 | lib/ 112 | data/ 113 | .vscode/ 114 | .idea/ 115 | .history/ 116 | .next/ 117 | client/.gitignore 118 | 119 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Feathers 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # feathers-next 2 | 3 | This project shows how to integrate a [Next.js](https://github.com/zeit/next.js) application with a [Feathers](http://feathersjs.com) backend, including authentication (with user name/password) and Redux. 4 | 5 | ## About 6 | 7 | The project was inspired by [feathers-next-example](https://github.com/Albert-Gao/feathers-next-example) 8 | and by [this](https://github.com/hugotox/next.js/tree/canary/examples/with-cookie-auth-redux) example for the authentication part. 9 | 10 | Contrary to [feathers-next-example](https://github.com/Albert-Gao/feathers-next-example), I decided to keep the Feathers backend (the API) separated from the "server" (SSR) part of the Next.js frontend. This means that we're running two separate server (node.js) processes. 11 | 12 | This might add a (tiny) bit of overhead, but ultimately it makes the app easier to develop and maintain (and configure) because we don't mingle Feathers API backend code with Next.js server rendering code. 13 | 14 | ## Getting Started 15 | 16 | The repository contains both the backend (Feathers) and the frontend (Next.js), in two separate directories ```server``` and ```client```. 17 | 18 | To install the app (backend and frontend), open a terminal and issue the following commands: 19 | 20 | ``` 21 | # Clone the repo: 22 | git clone https://github.com/leob/feathers-next 23 | # Now make sure that "nodemon" is installed, either as a global dependency, or as a local one - 24 | # for details see the section 'Installing nodemon' 25 | # Install and run the server part: 26 | cd server 27 | npm install 28 | npm run start 29 | # Then in another terminal, install and run the client part: 30 | cd .. 31 | cd client 32 | npm install 33 | npm run dev 34 | # The command above ("npm run dev") supports hot reload, and is perfect for developing. 35 | # For production however, be sure to do a "build" and "run" as follows: 36 | npm run build 37 | npm run start 38 | # Click through the app both in 'development' and 'production' mode, and notice how the app is MUCH faster in production mode! 39 | ``` 40 | To view the app, open your browser and go to `http://localhost:3000`. 41 | You should see the home page containing Login and Register links. 42 | 43 | ## Installing `nodemon` 44 | 45 | When installing the project, right after the first step ("Cloning the repo"), you should make sure that `nodemon` is available, because this is needed for the app to run. You can do this in two ways: install `nodemon` globally, or install it locally. 46 | 47 | If you want to install `nodemon` globally (so that it's available for all your node projects), then execute this command: 48 | 49 | ``` 50 | npm install nodemon -g 51 | ``` 52 | 53 | Alternatively, if you want to install it locally, add it as a dev dependency in `package.json` by executing: 54 | 55 | ``` 56 | npm install nodemon --save-dev 57 | ``` 58 | 59 | Both methods work, so this is a matter of preference (some people dislike global dependencies). 60 | 61 | ## Using the app 62 | 63 | The home page of the app contains "Login" and "Register" links. Click on "Register", enter a user name and password of your choosing (choose anything you want, there are no restrictions) and click "Submit". You are now registered, and logged in. 64 | 65 | Click on the other links (```private``` and ```private-perm-required```) to see if they work. The ```private``` page demonstrates how to call a Feathers service which requires authentication (in the Feathers backend we've implemented a simple "counters" service which always returns the same set of data). 66 | 67 | To access the ```private-perm-required``` page, you need an "admin" user. Click ```Logout``` on the home page and then click ```Register```, and register a new user with the user name "admin". You should now be able to access the ```private-perm-required``` page. 68 | -------------------------------------------------------------------------------- /client/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feathers-next-client", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "version": "1.0.0", 9 | "dependencies": { 10 | "@feathersjs/authentication-client": "^1.0.2", 11 | "@feathersjs/feathers": "^3.1.0", 12 | "@feathersjs/socketio-client": "^1.0.2", 13 | "cookie": "^0.7.0", 14 | "express": "^4.21.2", 15 | "next": "^14.2.21", 16 | "next-redux-wrapper": "^1.0.0", 17 | "react": "^16.2.0", 18 | "react-dom": "^16.3.3", 19 | "react-redux": "^5.0.1", 20 | "redux": "^3.6.0", 21 | "redux-devtools-extension": "^2.13.2", 22 | "redux-thunk": "^2.1.0", 23 | "socket.io-client": "^4.7.5" 24 | } 25 | }, 26 | "node_modules/@feathersjs/authentication-client": { 27 | "version": "1.0.2", 28 | "resolved": "https://registry.npmjs.org/@feathersjs/authentication-client/-/authentication-client-1.0.2.tgz", 29 | "integrity": "sha512-uZfWWMLqNkXcGi5bCuvQwLjBWvUhHtGjK4yZJh3KIHFAESBlQ4/TlqI/JRz2RuswfGfbyNtj9ARPam71YCGOpw==", 30 | "dependencies": { 31 | "@feathersjs/errors": "^3.0.0", 32 | "debug": "^3.1.0", 33 | "jwt-decode": "^2.1.0" 34 | }, 35 | "engines": { 36 | "node": ">= 6" 37 | } 38 | }, 39 | "node_modules/@feathersjs/commons": { 40 | "version": "1.4.1", 41 | "resolved": "https://registry.npmjs.org/@feathersjs/commons/-/commons-1.4.1.tgz", 42 | "integrity": "sha512-hs3Tz0JV/nwd14B9s+mv4SG+Tll9pDxqEn2wuc5CzL4I2vc1+EnwnhpOkokvQMTAdzsaxwOLoQ4y1BPm6WmMNA==", 43 | "engines": { 44 | "node": ">= 6" 45 | } 46 | }, 47 | "node_modules/@feathersjs/errors": { 48 | "version": "3.3.0", 49 | "resolved": "https://registry.npmjs.org/@feathersjs/errors/-/errors-3.3.0.tgz", 50 | "integrity": "sha512-9oYAhAj4CKIix5KITRDEzvyNJNIaqNde5lGqmrQLw4pTuyWMvx9tgBhtXPA0l8lS1KnMKw4Qf1gHo6aKrM+OyQ==", 51 | "dependencies": { 52 | "debug": "^3.1.0" 53 | }, 54 | "engines": { 55 | "node": ">= 6" 56 | } 57 | }, 58 | "node_modules/@feathersjs/feathers": { 59 | "version": "3.1.4", 60 | "resolved": "https://registry.npmjs.org/@feathersjs/feathers/-/feathers-3.1.4.tgz", 61 | "integrity": "sha512-zFT8a28SoORxxWMHLC2IUSXa3UwKBMlBl1mSLCVALk5GsmnvJpf+P/SU8TMcuzh39Uew7Dj4tZSatu71mnY9cQ==", 62 | "dependencies": { 63 | "@feathersjs/commons": "^1.4.0", 64 | "debug": "^3.1.0", 65 | "events": "^2.0.0", 66 | "uberproto": "^1.2.0" 67 | }, 68 | "engines": { 69 | "node": ">= 6" 70 | } 71 | }, 72 | "node_modules/@feathersjs/socketio-client": { 73 | "version": "1.1.0", 74 | "resolved": "https://registry.npmjs.org/@feathersjs/socketio-client/-/socketio-client-1.1.0.tgz", 75 | "integrity": "sha512-2ry9vX8qIF1Nax3G3r121Utsnj5NQbGgBG7K0M3RTJc+93Gg46BrnLVMFKue5fz2YTIOpHfHDRu1qqbddTJLXQ==", 76 | "dependencies": { 77 | "@feathersjs/transport-commons": "^4.0.0" 78 | }, 79 | "engines": { 80 | "node": ">= 6.0.0" 81 | } 82 | }, 83 | "node_modules/@feathersjs/transport-commons": { 84 | "version": "4.0.0", 85 | "resolved": "https://registry.npmjs.org/@feathersjs/transport-commons/-/transport-commons-4.0.0.tgz", 86 | "integrity": "sha512-O+kuJWpdao0Lw5Pg/65eOuMIlxu+aP9bnK9jNlxfwVdqCxw4eX3Jh8WS0WmQpfZYILk5oyhP/IUdHgSqcjaXgw==", 87 | "dependencies": { 88 | "@feathersjs/commons": "^1.4.0", 89 | "@feathersjs/errors": "^3.0.0", 90 | "debug": "^3.1.0", 91 | "lodash": "^4.17.4", 92 | "radix-router": "^3.0.1" 93 | }, 94 | "engines": { 95 | "node": ">= 6" 96 | } 97 | }, 98 | "node_modules/@next/env": { 99 | "version": "14.2.21", 100 | "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.21.tgz", 101 | "integrity": "sha512-lXcwcJd5oR01tggjWJ6SrNNYFGuOOMB9c251wUNkjCpkoXOPkDeF/15c3mnVlBqrW4JJXb2kVxDFhC4GduJt2A==" 102 | }, 103 | "node_modules/@next/swc-darwin-arm64": { 104 | "version": "14.2.21", 105 | "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.21.tgz", 106 | "integrity": "sha512-HwEjcKsXtvszXz5q5Z7wCtrHeTTDSTgAbocz45PHMUjU3fBYInfvhR+ZhavDRUYLonm53aHZbB09QtJVJj8T7g==", 107 | "cpu": [ 108 | "arm64" 109 | ], 110 | "optional": true, 111 | "os": [ 112 | "darwin" 113 | ], 114 | "engines": { 115 | "node": ">= 10" 116 | } 117 | }, 118 | "node_modules/@next/swc-darwin-x64": { 119 | "version": "14.2.21", 120 | "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.21.tgz", 121 | "integrity": "sha512-TSAA2ROgNzm4FhKbTbyJOBrsREOMVdDIltZ6aZiKvCi/v0UwFmwigBGeqXDA97TFMpR3LNNpw52CbVelkoQBxA==", 122 | "cpu": [ 123 | "x64" 124 | ], 125 | "optional": true, 126 | "os": [ 127 | "darwin" 128 | ], 129 | "engines": { 130 | "node": ">= 10" 131 | } 132 | }, 133 | "node_modules/@next/swc-linux-arm64-gnu": { 134 | "version": "14.2.21", 135 | "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.21.tgz", 136 | "integrity": "sha512-0Dqjn0pEUz3JG+AImpnMMW/m8hRtl1GQCNbO66V1yp6RswSTiKmnHf3pTX6xMdJYSemf3O4Q9ykiL0jymu0TuA==", 137 | "cpu": [ 138 | "arm64" 139 | ], 140 | "optional": true, 141 | "os": [ 142 | "linux" 143 | ], 144 | "engines": { 145 | "node": ">= 10" 146 | } 147 | }, 148 | "node_modules/@next/swc-linux-arm64-musl": { 149 | "version": "14.2.21", 150 | "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.21.tgz", 151 | "integrity": "sha512-Ggfw5qnMXldscVntwnjfaQs5GbBbjioV4B4loP+bjqNEb42fzZlAaK+ldL0jm2CTJga9LynBMhekNfV8W4+HBw==", 152 | "cpu": [ 153 | "arm64" 154 | ], 155 | "optional": true, 156 | "os": [ 157 | "linux" 158 | ], 159 | "engines": { 160 | "node": ">= 10" 161 | } 162 | }, 163 | "node_modules/@next/swc-linux-x64-gnu": { 164 | "version": "14.2.21", 165 | "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.21.tgz", 166 | "integrity": "sha512-uokj0lubN1WoSa5KKdThVPRffGyiWlm/vCc/cMkWOQHw69Qt0X1o3b2PyLLx8ANqlefILZh1EdfLRz9gVpG6tg==", 167 | "cpu": [ 168 | "x64" 169 | ], 170 | "optional": true, 171 | "os": [ 172 | "linux" 173 | ], 174 | "engines": { 175 | "node": ">= 10" 176 | } 177 | }, 178 | "node_modules/@next/swc-linux-x64-musl": { 179 | "version": "14.2.21", 180 | "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.21.tgz", 181 | "integrity": "sha512-iAEBPzWNbciah4+0yI4s7Pce6BIoxTQ0AGCkxn/UBuzJFkYyJt71MadYQkjPqCQCJAFQ26sYh7MOKdU+VQFgPg==", 182 | "cpu": [ 183 | "x64" 184 | ], 185 | "optional": true, 186 | "os": [ 187 | "linux" 188 | ], 189 | "engines": { 190 | "node": ">= 10" 191 | } 192 | }, 193 | "node_modules/@next/swc-win32-arm64-msvc": { 194 | "version": "14.2.21", 195 | "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.21.tgz", 196 | "integrity": "sha512-plykgB3vL2hB4Z32W3ktsfqyuyGAPxqwiyrAi2Mr8LlEUhNn9VgkiAl5hODSBpzIfWweX3er1f5uNpGDygfQVQ==", 197 | "cpu": [ 198 | "arm64" 199 | ], 200 | "optional": true, 201 | "os": [ 202 | "win32" 203 | ], 204 | "engines": { 205 | "node": ">= 10" 206 | } 207 | }, 208 | "node_modules/@next/swc-win32-ia32-msvc": { 209 | "version": "14.2.21", 210 | "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.21.tgz", 211 | "integrity": "sha512-w5bacz4Vxqrh06BjWgua3Yf7EMDb8iMcVhNrNx8KnJXt8t+Uu0Zg4JHLDL/T7DkTCEEfKXO/Er1fcfWxn2xfPA==", 212 | "cpu": [ 213 | "ia32" 214 | ], 215 | "optional": true, 216 | "os": [ 217 | "win32" 218 | ], 219 | "engines": { 220 | "node": ">= 10" 221 | } 222 | }, 223 | "node_modules/@next/swc-win32-x64-msvc": { 224 | "version": "14.2.21", 225 | "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.21.tgz", 226 | "integrity": "sha512-sT6+llIkzpsexGYZq8cjjthRyRGe5cJVhqh12FmlbxHqna6zsDDK8UNaV7g41T6atFHCJUPeLb3uyAwrBwy0NA==", 227 | "cpu": [ 228 | "x64" 229 | ], 230 | "optional": true, 231 | "os": [ 232 | "win32" 233 | ], 234 | "engines": { 235 | "node": ">= 10" 236 | } 237 | }, 238 | "node_modules/@socket.io/component-emitter": { 239 | "version": "3.1.2", 240 | "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", 241 | "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" 242 | }, 243 | "node_modules/@swc/counter": { 244 | "version": "0.1.3", 245 | "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", 246 | "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" 247 | }, 248 | "node_modules/@swc/helpers": { 249 | "version": "0.5.5", 250 | "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", 251 | "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", 252 | "dependencies": { 253 | "@swc/counter": "^0.1.3", 254 | "tslib": "^2.4.0" 255 | } 256 | }, 257 | "node_modules/accepts": { 258 | "version": "1.3.8", 259 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 260 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 261 | "dependencies": { 262 | "mime-types": "~2.1.34", 263 | "negotiator": "0.6.3" 264 | }, 265 | "engines": { 266 | "node": ">= 0.6" 267 | } 268 | }, 269 | "node_modules/array-flatten": { 270 | "version": "1.1.1", 271 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 272 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 273 | }, 274 | "node_modules/asap": { 275 | "version": "2.0.6", 276 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", 277 | "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" 278 | }, 279 | "node_modules/body-parser": { 280 | "version": "1.20.3", 281 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", 282 | "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", 283 | "dependencies": { 284 | "bytes": "3.1.2", 285 | "content-type": "~1.0.5", 286 | "debug": "2.6.9", 287 | "depd": "2.0.0", 288 | "destroy": "1.2.0", 289 | "http-errors": "2.0.0", 290 | "iconv-lite": "0.4.24", 291 | "on-finished": "2.4.1", 292 | "qs": "6.13.0", 293 | "raw-body": "2.5.2", 294 | "type-is": "~1.6.18", 295 | "unpipe": "1.0.0" 296 | }, 297 | "engines": { 298 | "node": ">= 0.8", 299 | "npm": "1.2.8000 || >= 1.4.16" 300 | } 301 | }, 302 | "node_modules/body-parser/node_modules/debug": { 303 | "version": "2.6.9", 304 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 305 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 306 | "dependencies": { 307 | "ms": "2.0.0" 308 | } 309 | }, 310 | "node_modules/body-parser/node_modules/iconv-lite": { 311 | "version": "0.4.24", 312 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 313 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 314 | "dependencies": { 315 | "safer-buffer": ">= 2.1.2 < 3" 316 | }, 317 | "engines": { 318 | "node": ">=0.10.0" 319 | } 320 | }, 321 | "node_modules/busboy": { 322 | "version": "1.6.0", 323 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", 324 | "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", 325 | "dependencies": { 326 | "streamsearch": "^1.1.0" 327 | }, 328 | "engines": { 329 | "node": ">=10.16.0" 330 | } 331 | }, 332 | "node_modules/bytes": { 333 | "version": "3.1.2", 334 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 335 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 336 | "engines": { 337 | "node": ">= 0.8" 338 | } 339 | }, 340 | "node_modules/call-bind": { 341 | "version": "1.0.8", 342 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", 343 | "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", 344 | "dependencies": { 345 | "call-bind-apply-helpers": "^1.0.0", 346 | "es-define-property": "^1.0.0", 347 | "get-intrinsic": "^1.2.4", 348 | "set-function-length": "^1.2.2" 349 | }, 350 | "engines": { 351 | "node": ">= 0.4" 352 | }, 353 | "funding": { 354 | "url": "https://github.com/sponsors/ljharb" 355 | } 356 | }, 357 | "node_modules/call-bind-apply-helpers": { 358 | "version": "1.0.1", 359 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", 360 | "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", 361 | "dependencies": { 362 | "es-errors": "^1.3.0", 363 | "function-bind": "^1.1.2" 364 | }, 365 | "engines": { 366 | "node": ">= 0.4" 367 | } 368 | }, 369 | "node_modules/call-bind-apply-helpers/node_modules/function-bind": { 370 | "version": "1.1.2", 371 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 372 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 373 | "funding": { 374 | "url": "https://github.com/sponsors/ljharb" 375 | } 376 | }, 377 | "node_modules/call-bound": { 378 | "version": "1.0.2", 379 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.2.tgz", 380 | "integrity": "sha512-0lk0PHFe/uz0vl527fG9CgdE9WdafjDbCXvBbs+LUv000TVt2Jjhqbs4Jwm8gz070w8xXyEAxrPOMullsxXeGg==", 381 | "dependencies": { 382 | "call-bind": "^1.0.8", 383 | "get-intrinsic": "^1.2.5" 384 | }, 385 | "engines": { 386 | "node": ">= 0.4" 387 | }, 388 | "funding": { 389 | "url": "https://github.com/sponsors/ljharb" 390 | } 391 | }, 392 | "node_modules/caniuse-lite": { 393 | "version": "1.0.30001690", 394 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", 395 | "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", 396 | "funding": [ 397 | { 398 | "type": "opencollective", 399 | "url": "https://opencollective.com/browserslist" 400 | }, 401 | { 402 | "type": "tidelift", 403 | "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 404 | }, 405 | { 406 | "type": "github", 407 | "url": "https://github.com/sponsors/ai" 408 | } 409 | ] 410 | }, 411 | "node_modules/client-only": { 412 | "version": "0.0.1", 413 | "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", 414 | "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" 415 | }, 416 | "node_modules/content-disposition": { 417 | "version": "0.5.4", 418 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 419 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 420 | "dependencies": { 421 | "safe-buffer": "5.2.1" 422 | }, 423 | "engines": { 424 | "node": ">= 0.6" 425 | } 426 | }, 427 | "node_modules/content-type": { 428 | "version": "1.0.5", 429 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 430 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 431 | "engines": { 432 | "node": ">= 0.6" 433 | } 434 | }, 435 | "node_modules/cookie": { 436 | "version": "0.7.0", 437 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.0.tgz", 438 | "integrity": "sha512-qCf+V4dtlNhSRXGAZatc1TasyFO6GjohcOul807YOb5ik3+kQSnb4d7iajeCL8QHaJ4uZEjCgiCJerKXwdRVlQ==", 439 | "engines": { 440 | "node": ">= 0.6" 441 | } 442 | }, 443 | "node_modules/cookie-signature": { 444 | "version": "1.0.6", 445 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 446 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 447 | }, 448 | "node_modules/debug": { 449 | "version": "3.1.0", 450 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 451 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 452 | "dependencies": { 453 | "ms": "2.0.0" 454 | } 455 | }, 456 | "node_modules/define-data-property": { 457 | "version": "1.1.4", 458 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", 459 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", 460 | "dependencies": { 461 | "es-define-property": "^1.0.0", 462 | "es-errors": "^1.3.0", 463 | "gopd": "^1.0.1" 464 | }, 465 | "engines": { 466 | "node": ">= 0.4" 467 | }, 468 | "funding": { 469 | "url": "https://github.com/sponsors/ljharb" 470 | } 471 | }, 472 | "node_modules/define-properties": { 473 | "version": "1.1.2", 474 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", 475 | "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", 476 | "dependencies": { 477 | "foreach": "^2.0.5", 478 | "object-keys": "^1.0.8" 479 | }, 480 | "engines": { 481 | "node": ">= 0.4" 482 | } 483 | }, 484 | "node_modules/depd": { 485 | "version": "2.0.0", 486 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 487 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 488 | "engines": { 489 | "node": ">= 0.8" 490 | } 491 | }, 492 | "node_modules/destroy": { 493 | "version": "1.2.0", 494 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 495 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 496 | "engines": { 497 | "node": ">= 0.8", 498 | "npm": "1.2.8000 || >= 1.4.16" 499 | } 500 | }, 501 | "node_modules/dunder-proto": { 502 | "version": "1.0.0", 503 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", 504 | "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", 505 | "dependencies": { 506 | "call-bind-apply-helpers": "^1.0.0", 507 | "es-errors": "^1.3.0", 508 | "gopd": "^1.2.0" 509 | }, 510 | "engines": { 511 | "node": ">= 0.4" 512 | } 513 | }, 514 | "node_modules/ee-first": { 515 | "version": "1.1.1", 516 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 517 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 518 | }, 519 | "node_modules/encodeurl": { 520 | "version": "2.0.0", 521 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 522 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 523 | "engines": { 524 | "node": ">= 0.8" 525 | } 526 | }, 527 | "node_modules/encoding": { 528 | "version": "0.1.12", 529 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", 530 | "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", 531 | "dependencies": { 532 | "iconv-lite": "~0.4.13" 533 | } 534 | }, 535 | "node_modules/engine.io-client": { 536 | "version": "6.5.4", 537 | "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz", 538 | "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==", 539 | "dependencies": { 540 | "@socket.io/component-emitter": "~3.1.0", 541 | "debug": "~4.3.1", 542 | "engine.io-parser": "~5.2.1", 543 | "ws": "~8.17.1", 544 | "xmlhttprequest-ssl": "~2.0.0" 545 | } 546 | }, 547 | "node_modules/engine.io-client/node_modules/debug": { 548 | "version": "4.3.5", 549 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", 550 | "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", 551 | "dependencies": { 552 | "ms": "2.1.2" 553 | }, 554 | "engines": { 555 | "node": ">=6.0" 556 | }, 557 | "peerDependenciesMeta": { 558 | "supports-color": { 559 | "optional": true 560 | } 561 | } 562 | }, 563 | "node_modules/engine.io-client/node_modules/ms": { 564 | "version": "2.1.2", 565 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 566 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 567 | }, 568 | "node_modules/engine.io-parser": { 569 | "version": "5.2.2", 570 | "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", 571 | "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", 572 | "engines": { 573 | "node": ">=10.0.0" 574 | } 575 | }, 576 | "node_modules/es-define-property": { 577 | "version": "1.0.1", 578 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 579 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 580 | "engines": { 581 | "node": ">= 0.4" 582 | } 583 | }, 584 | "node_modules/es-errors": { 585 | "version": "1.3.0", 586 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 587 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 588 | "engines": { 589 | "node": ">= 0.4" 590 | } 591 | }, 592 | "node_modules/es-object-atoms": { 593 | "version": "1.0.0", 594 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", 595 | "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", 596 | "dependencies": { 597 | "es-errors": "^1.3.0" 598 | }, 599 | "engines": { 600 | "node": ">= 0.4" 601 | } 602 | }, 603 | "node_modules/escape-html": { 604 | "version": "1.0.3", 605 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 606 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 607 | }, 608 | "node_modules/etag": { 609 | "version": "1.8.1", 610 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 611 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 612 | "engines": { 613 | "node": ">= 0.6" 614 | } 615 | }, 616 | "node_modules/events": { 617 | "version": "2.0.0", 618 | "resolved": "https://registry.npmjs.org/events/-/events-2.0.0.tgz", 619 | "integrity": "sha512-r/M5YkNg9zwI8QbSf7tsDWWJvO3PGwZXyG7GpFAxtMASnHL2eblFd7iHiGPtyGKKFPZ59S63NeX10Ws6WqGDcg==", 620 | "engines": { 621 | "node": ">=0.4.x" 622 | } 623 | }, 624 | "node_modules/express": { 625 | "version": "4.21.2", 626 | "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", 627 | "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", 628 | "dependencies": { 629 | "accepts": "~1.3.8", 630 | "array-flatten": "1.1.1", 631 | "body-parser": "1.20.3", 632 | "content-disposition": "0.5.4", 633 | "content-type": "~1.0.4", 634 | "cookie": "0.7.1", 635 | "cookie-signature": "1.0.6", 636 | "debug": "2.6.9", 637 | "depd": "2.0.0", 638 | "encodeurl": "~2.0.0", 639 | "escape-html": "~1.0.3", 640 | "etag": "~1.8.1", 641 | "finalhandler": "1.3.1", 642 | "fresh": "0.5.2", 643 | "http-errors": "2.0.0", 644 | "merge-descriptors": "1.0.3", 645 | "methods": "~1.1.2", 646 | "on-finished": "2.4.1", 647 | "parseurl": "~1.3.3", 648 | "path-to-regexp": "0.1.12", 649 | "proxy-addr": "~2.0.7", 650 | "qs": "6.13.0", 651 | "range-parser": "~1.2.1", 652 | "safe-buffer": "5.2.1", 653 | "send": "0.19.0", 654 | "serve-static": "1.16.2", 655 | "setprototypeof": "1.2.0", 656 | "statuses": "2.0.1", 657 | "type-is": "~1.6.18", 658 | "utils-merge": "1.0.1", 659 | "vary": "~1.1.2" 660 | }, 661 | "engines": { 662 | "node": ">= 0.10.0" 663 | }, 664 | "funding": { 665 | "type": "opencollective", 666 | "url": "https://opencollective.com/express" 667 | } 668 | }, 669 | "node_modules/express/node_modules/cookie": { 670 | "version": "0.7.1", 671 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", 672 | "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", 673 | "engines": { 674 | "node": ">= 0.6" 675 | } 676 | }, 677 | "node_modules/express/node_modules/debug": { 678 | "version": "2.6.9", 679 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 680 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 681 | "dependencies": { 682 | "ms": "2.0.0" 683 | } 684 | }, 685 | "node_modules/fbjs": { 686 | "version": "0.8.16", 687 | "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz", 688 | "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=", 689 | "dependencies": { 690 | "core-js": "^1.0.0", 691 | "isomorphic-fetch": "^2.1.1", 692 | "loose-envify": "^1.0.0", 693 | "object-assign": "^4.1.0", 694 | "promise": "^7.1.1", 695 | "setimmediate": "^1.0.5", 696 | "ua-parser-js": "^0.7.9" 697 | } 698 | }, 699 | "node_modules/fbjs/node_modules/core-js": { 700 | "version": "1.2.7", 701 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", 702 | "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=", 703 | "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js." 704 | }, 705 | "node_modules/finalhandler": { 706 | "version": "1.3.1", 707 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", 708 | "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", 709 | "dependencies": { 710 | "debug": "2.6.9", 711 | "encodeurl": "~2.0.0", 712 | "escape-html": "~1.0.3", 713 | "on-finished": "2.4.1", 714 | "parseurl": "~1.3.3", 715 | "statuses": "2.0.1", 716 | "unpipe": "~1.0.0" 717 | }, 718 | "engines": { 719 | "node": ">= 0.8" 720 | } 721 | }, 722 | "node_modules/finalhandler/node_modules/debug": { 723 | "version": "2.6.9", 724 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 725 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 726 | "dependencies": { 727 | "ms": "2.0.0" 728 | } 729 | }, 730 | "node_modules/foreach": { 731 | "version": "2.0.5", 732 | "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", 733 | "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" 734 | }, 735 | "node_modules/forwarded": { 736 | "version": "0.2.0", 737 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 738 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 739 | "engines": { 740 | "node": ">= 0.6" 741 | } 742 | }, 743 | "node_modules/fresh": { 744 | "version": "0.5.2", 745 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 746 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 747 | "engines": { 748 | "node": ">= 0.6" 749 | } 750 | }, 751 | "node_modules/function-bind": { 752 | "version": "1.1.1", 753 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 754 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 755 | }, 756 | "node_modules/get-intrinsic": { 757 | "version": "1.2.6", 758 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", 759 | "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", 760 | "dependencies": { 761 | "call-bind-apply-helpers": "^1.0.1", 762 | "dunder-proto": "^1.0.0", 763 | "es-define-property": "^1.0.1", 764 | "es-errors": "^1.3.0", 765 | "es-object-atoms": "^1.0.0", 766 | "function-bind": "^1.1.2", 767 | "gopd": "^1.2.0", 768 | "has-symbols": "^1.1.0", 769 | "hasown": "^2.0.2", 770 | "math-intrinsics": "^1.0.0" 771 | }, 772 | "engines": { 773 | "node": ">= 0.4" 774 | }, 775 | "funding": { 776 | "url": "https://github.com/sponsors/ljharb" 777 | } 778 | }, 779 | "node_modules/get-intrinsic/node_modules/function-bind": { 780 | "version": "1.1.2", 781 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 782 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 783 | "funding": { 784 | "url": "https://github.com/sponsors/ljharb" 785 | } 786 | }, 787 | "node_modules/get-intrinsic/node_modules/has-symbols": { 788 | "version": "1.1.0", 789 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 790 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 791 | "engines": { 792 | "node": ">= 0.4" 793 | }, 794 | "funding": { 795 | "url": "https://github.com/sponsors/ljharb" 796 | } 797 | }, 798 | "node_modules/gopd": { 799 | "version": "1.2.0", 800 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 801 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 802 | "engines": { 803 | "node": ">= 0.4" 804 | }, 805 | "funding": { 806 | "url": "https://github.com/sponsors/ljharb" 807 | } 808 | }, 809 | "node_modules/graceful-fs": { 810 | "version": "4.2.11", 811 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 812 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" 813 | }, 814 | "node_modules/has-property-descriptors": { 815 | "version": "1.0.2", 816 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", 817 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", 818 | "dependencies": { 819 | "es-define-property": "^1.0.0" 820 | }, 821 | "funding": { 822 | "url": "https://github.com/sponsors/ljharb" 823 | } 824 | }, 825 | "node_modules/has-symbols": { 826 | "version": "1.0.0", 827 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", 828 | "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", 829 | "engines": { 830 | "node": ">= 0.4" 831 | } 832 | }, 833 | "node_modules/hasown": { 834 | "version": "2.0.2", 835 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 836 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 837 | "dependencies": { 838 | "function-bind": "^1.1.2" 839 | }, 840 | "engines": { 841 | "node": ">= 0.4" 842 | } 843 | }, 844 | "node_modules/hasown/node_modules/function-bind": { 845 | "version": "1.1.2", 846 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 847 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 848 | "funding": { 849 | "url": "https://github.com/sponsors/ljharb" 850 | } 851 | }, 852 | "node_modules/http-errors": { 853 | "version": "2.0.0", 854 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 855 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 856 | "dependencies": { 857 | "depd": "2.0.0", 858 | "inherits": "2.0.4", 859 | "setprototypeof": "1.2.0", 860 | "statuses": "2.0.1", 861 | "toidentifier": "1.0.1" 862 | }, 863 | "engines": { 864 | "node": ">= 0.8" 865 | } 866 | }, 867 | "node_modules/iconv-lite": { 868 | "version": "0.4.19", 869 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", 870 | "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", 871 | "engines": { 872 | "node": ">=0.10.0" 873 | } 874 | }, 875 | "node_modules/inherits": { 876 | "version": "2.0.4", 877 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 878 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 879 | }, 880 | "node_modules/invariant": { 881 | "version": "2.2.4", 882 | "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", 883 | "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", 884 | "dependencies": { 885 | "loose-envify": "^1.0.0" 886 | } 887 | }, 888 | "node_modules/ipaddr.js": { 889 | "version": "1.9.1", 890 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 891 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 892 | "engines": { 893 | "node": ">= 0.10" 894 | } 895 | }, 896 | "node_modules/is-stream": { 897 | "version": "1.1.0", 898 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 899 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", 900 | "engines": { 901 | "node": ">=0.10.0" 902 | } 903 | }, 904 | "node_modules/isomorphic-fetch": { 905 | "version": "2.2.1", 906 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", 907 | "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", 908 | "dependencies": { 909 | "node-fetch": "^1.0.1", 910 | "whatwg-fetch": ">=0.10.0" 911 | } 912 | }, 913 | "node_modules/js-tokens": { 914 | "version": "3.0.2", 915 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", 916 | "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" 917 | }, 918 | "node_modules/jwt-decode": { 919 | "version": "2.2.0", 920 | "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz", 921 | "integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=" 922 | }, 923 | "node_modules/lodash": { 924 | "version": "4.17.21", 925 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 926 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 927 | }, 928 | "node_modules/lodash-es": { 929 | "version": "4.17.21", 930 | "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", 931 | "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", 932 | "license": "MIT" 933 | }, 934 | "node_modules/loose-envify": { 935 | "version": "1.3.1", 936 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", 937 | "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", 938 | "dependencies": { 939 | "js-tokens": "^3.0.0" 940 | }, 941 | "bin": { 942 | "loose-envify": "cli.js" 943 | } 944 | }, 945 | "node_modules/math-intrinsics": { 946 | "version": "1.0.0", 947 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", 948 | "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", 949 | "engines": { 950 | "node": ">= 0.4" 951 | } 952 | }, 953 | "node_modules/media-typer": { 954 | "version": "0.3.0", 955 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 956 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 957 | "engines": { 958 | "node": ">= 0.6" 959 | } 960 | }, 961 | "node_modules/merge-descriptors": { 962 | "version": "1.0.3", 963 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", 964 | "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", 965 | "funding": { 966 | "url": "https://github.com/sponsors/sindresorhus" 967 | } 968 | }, 969 | "node_modules/methods": { 970 | "version": "1.1.2", 971 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 972 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 973 | "engines": { 974 | "node": ">= 0.6" 975 | } 976 | }, 977 | "node_modules/mime": { 978 | "version": "1.6.0", 979 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 980 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 981 | "bin": { 982 | "mime": "cli.js" 983 | }, 984 | "engines": { 985 | "node": ">=4" 986 | } 987 | }, 988 | "node_modules/mime-db": { 989 | "version": "1.52.0", 990 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 991 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 992 | "engines": { 993 | "node": ">= 0.6" 994 | } 995 | }, 996 | "node_modules/mime-types": { 997 | "version": "2.1.35", 998 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 999 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1000 | "dependencies": { 1001 | "mime-db": "1.52.0" 1002 | }, 1003 | "engines": { 1004 | "node": ">= 0.6" 1005 | } 1006 | }, 1007 | "node_modules/ms": { 1008 | "version": "2.0.0", 1009 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1010 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1011 | }, 1012 | "node_modules/nanoid": { 1013 | "version": "3.3.8", 1014 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", 1015 | "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", 1016 | "funding": [ 1017 | { 1018 | "type": "github", 1019 | "url": "https://github.com/sponsors/ai" 1020 | } 1021 | ], 1022 | "bin": { 1023 | "nanoid": "bin/nanoid.cjs" 1024 | }, 1025 | "engines": { 1026 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1027 | } 1028 | }, 1029 | "node_modules/negotiator": { 1030 | "version": "0.6.3", 1031 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 1032 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 1033 | "engines": { 1034 | "node": ">= 0.6" 1035 | } 1036 | }, 1037 | "node_modules/next": { 1038 | "version": "14.2.21", 1039 | "resolved": "https://registry.npmjs.org/next/-/next-14.2.21.tgz", 1040 | "integrity": "sha512-rZmLwucLHr3/zfDMYbJXbw0ZeoBpirxkXuvsJbk7UPorvPYZhP7vq7aHbKnU7dQNCYIimRrbB2pp3xmf+wsYUg==", 1041 | "dependencies": { 1042 | "@next/env": "14.2.21", 1043 | "@swc/helpers": "0.5.5", 1044 | "busboy": "1.6.0", 1045 | "caniuse-lite": "^1.0.30001579", 1046 | "graceful-fs": "^4.2.11", 1047 | "postcss": "8.4.31", 1048 | "styled-jsx": "5.1.1" 1049 | }, 1050 | "bin": { 1051 | "next": "dist/bin/next" 1052 | }, 1053 | "engines": { 1054 | "node": ">=18.17.0" 1055 | }, 1056 | "optionalDependencies": { 1057 | "@next/swc-darwin-arm64": "14.2.21", 1058 | "@next/swc-darwin-x64": "14.2.21", 1059 | "@next/swc-linux-arm64-gnu": "14.2.21", 1060 | "@next/swc-linux-arm64-musl": "14.2.21", 1061 | "@next/swc-linux-x64-gnu": "14.2.21", 1062 | "@next/swc-linux-x64-musl": "14.2.21", 1063 | "@next/swc-win32-arm64-msvc": "14.2.21", 1064 | "@next/swc-win32-ia32-msvc": "14.2.21", 1065 | "@next/swc-win32-x64-msvc": "14.2.21" 1066 | }, 1067 | "peerDependencies": { 1068 | "@opentelemetry/api": "^1.1.0", 1069 | "@playwright/test": "^1.41.2", 1070 | "react": "^18.2.0", 1071 | "react-dom": "^18.2.0", 1072 | "sass": "^1.3.0" 1073 | }, 1074 | "peerDependenciesMeta": { 1075 | "@opentelemetry/api": { 1076 | "optional": true 1077 | }, 1078 | "@playwright/test": { 1079 | "optional": true 1080 | }, 1081 | "sass": { 1082 | "optional": true 1083 | } 1084 | } 1085 | }, 1086 | "node_modules/next-redux-wrapper": { 1087 | "version": "1.3.5", 1088 | "resolved": "https://registry.npmjs.org/next-redux-wrapper/-/next-redux-wrapper-1.3.5.tgz", 1089 | "integrity": "sha512-VZFl/CIIujxCGDlBM99W8pDYkSr5o10UMUt5/pZHZVgnaEqpFSsRsKz9yAkx/8C3RYFhGK4MHiNaf9+LtUUemA==", 1090 | "dependencies": { 1091 | "object.assign": "^4.0.4" 1092 | }, 1093 | "engines": { 1094 | "node": ">=0.10.36" 1095 | }, 1096 | "peerDependencies": { 1097 | "react": "*", 1098 | "react-redux": "*" 1099 | } 1100 | }, 1101 | "node_modules/node-fetch": { 1102 | "version": "1.7.3", 1103 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", 1104 | "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", 1105 | "dependencies": { 1106 | "encoding": "^0.1.11", 1107 | "is-stream": "^1.0.1" 1108 | } 1109 | }, 1110 | "node_modules/object-assign": { 1111 | "version": "4.1.1", 1112 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1113 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 1114 | "engines": { 1115 | "node": ">=0.10.0" 1116 | } 1117 | }, 1118 | "node_modules/object-inspect": { 1119 | "version": "1.13.3", 1120 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", 1121 | "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", 1122 | "engines": { 1123 | "node": ">= 0.4" 1124 | }, 1125 | "funding": { 1126 | "url": "https://github.com/sponsors/ljharb" 1127 | } 1128 | }, 1129 | "node_modules/object-keys": { 1130 | "version": "1.0.11", 1131 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", 1132 | "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", 1133 | "engines": { 1134 | "node": ">= 0.4" 1135 | } 1136 | }, 1137 | "node_modules/object.assign": { 1138 | "version": "4.1.0", 1139 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", 1140 | "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", 1141 | "dependencies": { 1142 | "define-properties": "^1.1.2", 1143 | "function-bind": "^1.1.1", 1144 | "has-symbols": "^1.0.0", 1145 | "object-keys": "^1.0.11" 1146 | }, 1147 | "engines": { 1148 | "node": ">= 0.4" 1149 | } 1150 | }, 1151 | "node_modules/on-finished": { 1152 | "version": "2.4.1", 1153 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1154 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1155 | "dependencies": { 1156 | "ee-first": "1.1.1" 1157 | }, 1158 | "engines": { 1159 | "node": ">= 0.8" 1160 | } 1161 | }, 1162 | "node_modules/parseurl": { 1163 | "version": "1.3.3", 1164 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1165 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1166 | "engines": { 1167 | "node": ">= 0.8" 1168 | } 1169 | }, 1170 | "node_modules/path-to-regexp": { 1171 | "version": "0.1.12", 1172 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", 1173 | "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" 1174 | }, 1175 | "node_modules/picocolors": { 1176 | "version": "1.1.1", 1177 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 1178 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" 1179 | }, 1180 | "node_modules/postcss": { 1181 | "version": "8.4.31", 1182 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", 1183 | "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", 1184 | "funding": [ 1185 | { 1186 | "type": "opencollective", 1187 | "url": "https://opencollective.com/postcss/" 1188 | }, 1189 | { 1190 | "type": "tidelift", 1191 | "url": "https://tidelift.com/funding/github/npm/postcss" 1192 | }, 1193 | { 1194 | "type": "github", 1195 | "url": "https://github.com/sponsors/ai" 1196 | } 1197 | ], 1198 | "dependencies": { 1199 | "nanoid": "^3.3.6", 1200 | "picocolors": "^1.0.0", 1201 | "source-map-js": "^1.0.2" 1202 | }, 1203 | "engines": { 1204 | "node": "^10 || ^12 || >=14" 1205 | } 1206 | }, 1207 | "node_modules/promise": { 1208 | "version": "7.3.1", 1209 | "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", 1210 | "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", 1211 | "dependencies": { 1212 | "asap": "~2.0.3" 1213 | } 1214 | }, 1215 | "node_modules/prop-types": { 1216 | "version": "15.6.0", 1217 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz", 1218 | "integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=", 1219 | "dependencies": { 1220 | "fbjs": "^0.8.16", 1221 | "loose-envify": "^1.3.1", 1222 | "object-assign": "^4.1.1" 1223 | } 1224 | }, 1225 | "node_modules/proxy-addr": { 1226 | "version": "2.0.7", 1227 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1228 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1229 | "dependencies": { 1230 | "forwarded": "0.2.0", 1231 | "ipaddr.js": "1.9.1" 1232 | }, 1233 | "engines": { 1234 | "node": ">= 0.10" 1235 | } 1236 | }, 1237 | "node_modules/qs": { 1238 | "version": "6.13.0", 1239 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", 1240 | "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", 1241 | "dependencies": { 1242 | "side-channel": "^1.0.6" 1243 | }, 1244 | "engines": { 1245 | "node": ">=0.6" 1246 | }, 1247 | "funding": { 1248 | "url": "https://github.com/sponsors/ljharb" 1249 | } 1250 | }, 1251 | "node_modules/radix-router": { 1252 | "version": "3.0.1", 1253 | "resolved": "https://registry.npmjs.org/radix-router/-/radix-router-3.0.1.tgz", 1254 | "integrity": "sha512-jpHXHgP+ZmVzEfmZ7WVRSvc/EqMoAqYuMtBsHd9s47Hs9Iy8FDJhkweMrDH0wmdxanLzVIWhq0UpomLXNpW8tg==" 1255 | }, 1256 | "node_modules/range-parser": { 1257 | "version": "1.2.1", 1258 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1259 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1260 | "engines": { 1261 | "node": ">= 0.6" 1262 | } 1263 | }, 1264 | "node_modules/raw-body": { 1265 | "version": "2.5.2", 1266 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", 1267 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 1268 | "dependencies": { 1269 | "bytes": "3.1.2", 1270 | "http-errors": "2.0.0", 1271 | "iconv-lite": "0.4.24", 1272 | "unpipe": "1.0.0" 1273 | }, 1274 | "engines": { 1275 | "node": ">= 0.8" 1276 | } 1277 | }, 1278 | "node_modules/raw-body/node_modules/iconv-lite": { 1279 | "version": "0.4.24", 1280 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 1281 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 1282 | "dependencies": { 1283 | "safer-buffer": ">= 2.1.2 < 3" 1284 | }, 1285 | "engines": { 1286 | "node": ">=0.10.0" 1287 | } 1288 | }, 1289 | "node_modules/react": { 1290 | "version": "16.3.2", 1291 | "resolved": "https://registry.npmjs.org/react/-/react-16.3.2.tgz", 1292 | "integrity": "sha512-o5GPdkhciQ3cEph6qgvYB7LTOHw/GB0qRI6ZFNugj49qJCFfgHwVNjZ5u+b7nif4vOeMIOuYj3CeYe2IBD74lg==", 1293 | "dependencies": { 1294 | "fbjs": "^0.8.16", 1295 | "loose-envify": "^1.1.0", 1296 | "object-assign": "^4.1.1", 1297 | "prop-types": "^15.6.0" 1298 | }, 1299 | "engines": { 1300 | "node": ">=0.10.0" 1301 | } 1302 | }, 1303 | "node_modules/react-dom": { 1304 | "version": "16.3.3", 1305 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.3.3.tgz", 1306 | "integrity": "sha512-ALCp7ZbSGkqRDtQoZozKVNgwXMxbxf/IGOUMC2A0yF6JHeZrS8e2cOotPT87Vf4b7PKCuUVKU4/RDEXxToA/yA==", 1307 | "dependencies": { 1308 | "fbjs": "^0.8.16", 1309 | "loose-envify": "^1.1.0", 1310 | "object-assign": "^4.1.1", 1311 | "prop-types": "^15.6.0" 1312 | }, 1313 | "peerDependencies": { 1314 | "react": "^16.0.0" 1315 | } 1316 | }, 1317 | "node_modules/react-redux": { 1318 | "version": "5.0.7", 1319 | "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz", 1320 | "integrity": "sha512-5VI8EV5hdgNgyjfmWzBbdrqUkrVRKlyTKk1sGH3jzM2M2Mhj/seQgPXaz6gVAj2lz/nz688AdTqMO18Lr24Zhg==", 1321 | "dependencies": { 1322 | "hoist-non-react-statics": "^2.5.0", 1323 | "invariant": "^2.0.0", 1324 | "lodash": "^4.17.5", 1325 | "lodash-es": "^4.17.5", 1326 | "loose-envify": "^1.1.0", 1327 | "prop-types": "^15.6.0" 1328 | }, 1329 | "peerDependencies": { 1330 | "react": "^0.14.0 || ^15.0.0-0 || ^16.0.0-0", 1331 | "redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0" 1332 | } 1333 | }, 1334 | "node_modules/react-redux/node_modules/hoist-non-react-statics": { 1335 | "version": "2.5.0", 1336 | "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz", 1337 | "integrity": "sha512-6Bl6XsDT1ntE0lHbIhr4Kp2PGcleGZ66qu5Jqk8lc0Xc/IeG6gVLmwUGs/K0Us+L8VWoKgj0uWdPMataOsm31w==" 1338 | }, 1339 | "node_modules/redux": { 1340 | "version": "3.7.2", 1341 | "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", 1342 | "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", 1343 | "dependencies": { 1344 | "lodash": "^4.2.1", 1345 | "lodash-es": "^4.2.1", 1346 | "loose-envify": "^1.1.0", 1347 | "symbol-observable": "^1.0.3" 1348 | } 1349 | }, 1350 | "node_modules/redux-devtools-extension": { 1351 | "version": "2.13.2", 1352 | "resolved": "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.2.tgz", 1353 | "integrity": "sha1-4Pmo6N/KfBe+kscSSVijuU6ykR0=", 1354 | "deprecated": "Package moved to @redux-devtools/extension.", 1355 | "peerDependencies": { 1356 | "redux": "^3.1.0" 1357 | } 1358 | }, 1359 | "node_modules/redux-thunk": { 1360 | "version": "2.2.0", 1361 | "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.2.0.tgz", 1362 | "integrity": "sha1-5hWhbha0ehmlFXZhM9Hj6Zt4UuU=" 1363 | }, 1364 | "node_modules/safe-buffer": { 1365 | "version": "5.2.1", 1366 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1367 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1368 | "funding": [ 1369 | { 1370 | "type": "github", 1371 | "url": "https://github.com/sponsors/feross" 1372 | }, 1373 | { 1374 | "type": "patreon", 1375 | "url": "https://www.patreon.com/feross" 1376 | }, 1377 | { 1378 | "type": "consulting", 1379 | "url": "https://feross.org/support" 1380 | } 1381 | ] 1382 | }, 1383 | "node_modules/safer-buffer": { 1384 | "version": "2.1.2", 1385 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1386 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1387 | }, 1388 | "node_modules/send": { 1389 | "version": "0.19.0", 1390 | "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", 1391 | "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", 1392 | "dependencies": { 1393 | "debug": "2.6.9", 1394 | "depd": "2.0.0", 1395 | "destroy": "1.2.0", 1396 | "encodeurl": "~1.0.2", 1397 | "escape-html": "~1.0.3", 1398 | "etag": "~1.8.1", 1399 | "fresh": "0.5.2", 1400 | "http-errors": "2.0.0", 1401 | "mime": "1.6.0", 1402 | "ms": "2.1.3", 1403 | "on-finished": "2.4.1", 1404 | "range-parser": "~1.2.1", 1405 | "statuses": "2.0.1" 1406 | }, 1407 | "engines": { 1408 | "node": ">= 0.8.0" 1409 | } 1410 | }, 1411 | "node_modules/send/node_modules/debug": { 1412 | "version": "2.6.9", 1413 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1414 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1415 | "dependencies": { 1416 | "ms": "2.0.0" 1417 | } 1418 | }, 1419 | "node_modules/send/node_modules/debug/node_modules/ms": { 1420 | "version": "2.0.0", 1421 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1422 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 1423 | }, 1424 | "node_modules/send/node_modules/encodeurl": { 1425 | "version": "1.0.2", 1426 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 1427 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 1428 | "engines": { 1429 | "node": ">= 0.8" 1430 | } 1431 | }, 1432 | "node_modules/send/node_modules/ms": { 1433 | "version": "2.1.3", 1434 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1435 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1436 | }, 1437 | "node_modules/serve-static": { 1438 | "version": "1.16.2", 1439 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", 1440 | "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", 1441 | "dependencies": { 1442 | "encodeurl": "~2.0.0", 1443 | "escape-html": "~1.0.3", 1444 | "parseurl": "~1.3.3", 1445 | "send": "0.19.0" 1446 | }, 1447 | "engines": { 1448 | "node": ">= 0.8.0" 1449 | } 1450 | }, 1451 | "node_modules/set-function-length": { 1452 | "version": "1.2.2", 1453 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", 1454 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", 1455 | "dependencies": { 1456 | "define-data-property": "^1.1.4", 1457 | "es-errors": "^1.3.0", 1458 | "function-bind": "^1.1.2", 1459 | "get-intrinsic": "^1.2.4", 1460 | "gopd": "^1.0.1", 1461 | "has-property-descriptors": "^1.0.2" 1462 | }, 1463 | "engines": { 1464 | "node": ">= 0.4" 1465 | } 1466 | }, 1467 | "node_modules/set-function-length/node_modules/function-bind": { 1468 | "version": "1.1.2", 1469 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 1470 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 1471 | "funding": { 1472 | "url": "https://github.com/sponsors/ljharb" 1473 | } 1474 | }, 1475 | "node_modules/setimmediate": { 1476 | "version": "1.0.5", 1477 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 1478 | "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" 1479 | }, 1480 | "node_modules/setprototypeof": { 1481 | "version": "1.2.0", 1482 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1483 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 1484 | }, 1485 | "node_modules/side-channel": { 1486 | "version": "1.1.0", 1487 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 1488 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 1489 | "dependencies": { 1490 | "es-errors": "^1.3.0", 1491 | "object-inspect": "^1.13.3", 1492 | "side-channel-list": "^1.0.0", 1493 | "side-channel-map": "^1.0.1", 1494 | "side-channel-weakmap": "^1.0.2" 1495 | }, 1496 | "engines": { 1497 | "node": ">= 0.4" 1498 | }, 1499 | "funding": { 1500 | "url": "https://github.com/sponsors/ljharb" 1501 | } 1502 | }, 1503 | "node_modules/side-channel-list": { 1504 | "version": "1.0.0", 1505 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 1506 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 1507 | "dependencies": { 1508 | "es-errors": "^1.3.0", 1509 | "object-inspect": "^1.13.3" 1510 | }, 1511 | "engines": { 1512 | "node": ">= 0.4" 1513 | }, 1514 | "funding": { 1515 | "url": "https://github.com/sponsors/ljharb" 1516 | } 1517 | }, 1518 | "node_modules/side-channel-map": { 1519 | "version": "1.0.1", 1520 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 1521 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 1522 | "dependencies": { 1523 | "call-bound": "^1.0.2", 1524 | "es-errors": "^1.3.0", 1525 | "get-intrinsic": "^1.2.5", 1526 | "object-inspect": "^1.13.3" 1527 | }, 1528 | "engines": { 1529 | "node": ">= 0.4" 1530 | }, 1531 | "funding": { 1532 | "url": "https://github.com/sponsors/ljharb" 1533 | } 1534 | }, 1535 | "node_modules/side-channel-weakmap": { 1536 | "version": "1.0.2", 1537 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 1538 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 1539 | "dependencies": { 1540 | "call-bound": "^1.0.2", 1541 | "es-errors": "^1.3.0", 1542 | "get-intrinsic": "^1.2.5", 1543 | "object-inspect": "^1.13.3", 1544 | "side-channel-map": "^1.0.1" 1545 | }, 1546 | "engines": { 1547 | "node": ">= 0.4" 1548 | }, 1549 | "funding": { 1550 | "url": "https://github.com/sponsors/ljharb" 1551 | } 1552 | }, 1553 | "node_modules/socket.io-client": { 1554 | "version": "4.7.5", 1555 | "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", 1556 | "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", 1557 | "dependencies": { 1558 | "@socket.io/component-emitter": "~3.1.0", 1559 | "debug": "~4.3.2", 1560 | "engine.io-client": "~6.5.2", 1561 | "socket.io-parser": "~4.2.4" 1562 | }, 1563 | "engines": { 1564 | "node": ">=10.0.0" 1565 | } 1566 | }, 1567 | "node_modules/socket.io-client/node_modules/debug": { 1568 | "version": "4.3.5", 1569 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", 1570 | "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", 1571 | "dependencies": { 1572 | "ms": "2.1.2" 1573 | }, 1574 | "engines": { 1575 | "node": ">=6.0" 1576 | }, 1577 | "peerDependenciesMeta": { 1578 | "supports-color": { 1579 | "optional": true 1580 | } 1581 | } 1582 | }, 1583 | "node_modules/socket.io-client/node_modules/ms": { 1584 | "version": "2.1.2", 1585 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1586 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1587 | }, 1588 | "node_modules/socket.io-parser": { 1589 | "version": "4.2.4", 1590 | "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", 1591 | "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", 1592 | "dependencies": { 1593 | "@socket.io/component-emitter": "~3.1.0", 1594 | "debug": "~4.3.1" 1595 | }, 1596 | "engines": { 1597 | "node": ">=10.0.0" 1598 | } 1599 | }, 1600 | "node_modules/socket.io-parser/node_modules/debug": { 1601 | "version": "4.3.5", 1602 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", 1603 | "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", 1604 | "dependencies": { 1605 | "ms": "2.1.2" 1606 | }, 1607 | "engines": { 1608 | "node": ">=6.0" 1609 | }, 1610 | "peerDependenciesMeta": { 1611 | "supports-color": { 1612 | "optional": true 1613 | } 1614 | } 1615 | }, 1616 | "node_modules/socket.io-parser/node_modules/ms": { 1617 | "version": "2.1.2", 1618 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1619 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1620 | }, 1621 | "node_modules/source-map-js": { 1622 | "version": "1.2.1", 1623 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 1624 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1625 | "engines": { 1626 | "node": ">=0.10.0" 1627 | } 1628 | }, 1629 | "node_modules/statuses": { 1630 | "version": "2.0.1", 1631 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1632 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1633 | "engines": { 1634 | "node": ">= 0.8" 1635 | } 1636 | }, 1637 | "node_modules/streamsearch": { 1638 | "version": "1.1.0", 1639 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", 1640 | "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", 1641 | "engines": { 1642 | "node": ">=10.0.0" 1643 | } 1644 | }, 1645 | "node_modules/styled-jsx": { 1646 | "version": "5.1.1", 1647 | "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", 1648 | "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", 1649 | "dependencies": { 1650 | "client-only": "0.0.1" 1651 | }, 1652 | "engines": { 1653 | "node": ">= 12.0.0" 1654 | }, 1655 | "peerDependencies": { 1656 | "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" 1657 | }, 1658 | "peerDependenciesMeta": { 1659 | "@babel/core": { 1660 | "optional": true 1661 | }, 1662 | "babel-plugin-macros": { 1663 | "optional": true 1664 | } 1665 | } 1666 | }, 1667 | "node_modules/symbol-observable": { 1668 | "version": "1.2.0", 1669 | "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", 1670 | "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", 1671 | "engines": { 1672 | "node": ">=0.10.0" 1673 | } 1674 | }, 1675 | "node_modules/toidentifier": { 1676 | "version": "1.0.1", 1677 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1678 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1679 | "engines": { 1680 | "node": ">=0.6" 1681 | } 1682 | }, 1683 | "node_modules/tslib": { 1684 | "version": "2.8.1", 1685 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 1686 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" 1687 | }, 1688 | "node_modules/type-is": { 1689 | "version": "1.6.18", 1690 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1691 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1692 | "dependencies": { 1693 | "media-typer": "0.3.0", 1694 | "mime-types": "~2.1.24" 1695 | }, 1696 | "engines": { 1697 | "node": ">= 0.6" 1698 | } 1699 | }, 1700 | "node_modules/ua-parser-js": { 1701 | "version": "0.7.17", 1702 | "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz", 1703 | "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==", 1704 | "engines": { 1705 | "node": "*" 1706 | } 1707 | }, 1708 | "node_modules/uberproto": { 1709 | "version": "1.2.0", 1710 | "resolved": "https://registry.npmjs.org/uberproto/-/uberproto-1.2.0.tgz", 1711 | "integrity": "sha1-YdTqsCT5CcTm6lK+hnxIlKS+63Y=", 1712 | "engines": { 1713 | "node": "*" 1714 | } 1715 | }, 1716 | "node_modules/unpipe": { 1717 | "version": "1.0.0", 1718 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1719 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1720 | "engines": { 1721 | "node": ">= 0.8" 1722 | } 1723 | }, 1724 | "node_modules/utils-merge": { 1725 | "version": "1.0.1", 1726 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1727 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 1728 | "engines": { 1729 | "node": ">= 0.4.0" 1730 | } 1731 | }, 1732 | "node_modules/vary": { 1733 | "version": "1.1.2", 1734 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1735 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1736 | "engines": { 1737 | "node": ">= 0.8" 1738 | } 1739 | }, 1740 | "node_modules/whatwg-fetch": { 1741 | "version": "2.0.4", 1742 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", 1743 | "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" 1744 | }, 1745 | "node_modules/ws": { 1746 | "version": "8.17.1", 1747 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", 1748 | "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", 1749 | "engines": { 1750 | "node": ">=10.0.0" 1751 | }, 1752 | "peerDependencies": { 1753 | "bufferutil": "^4.0.1", 1754 | "utf-8-validate": ">=5.0.2" 1755 | }, 1756 | "peerDependenciesMeta": { 1757 | "bufferutil": { 1758 | "optional": true 1759 | }, 1760 | "utf-8-validate": { 1761 | "optional": true 1762 | } 1763 | } 1764 | }, 1765 | "node_modules/xmlhttprequest-ssl": { 1766 | "version": "2.0.0", 1767 | "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", 1768 | "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", 1769 | "engines": { 1770 | "node": ">=0.4.0" 1771 | } 1772 | } 1773 | } 1774 | } 1775 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feathers-next-client", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "node src/server.js", 9 | "build": "next build src", 10 | "start": "NODE_ENV=production node src/server.js" 11 | }, 12 | "dependencies": { 13 | "@feathersjs/authentication-client": "^1.0.2", 14 | "@feathersjs/feathers": "^3.1.0", 15 | "@feathersjs/socketio-client": "^1.0.2", 16 | "cookie": "^0.7.0", 17 | "express": "^4.21.2", 18 | "next": "^14.2.21", 19 | "next-redux-wrapper": "^1.0.0", 20 | "react": "^16.2.0", 21 | "react-dom": "^16.3.3", 22 | "react-redux": "^5.0.1", 23 | "redux": "^3.6.0", 24 | "redux-devtools-extension": "^2.13.2", 25 | "redux-thunk": "^2.1.0", 26 | "socket.io-client": "^4.7.5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/src/api/auth.js: -------------------------------------------------------------------------------- 1 | import client from './client' 2 | 3 | const auth = { 4 | 5 | fetchUser (accessToken) { 6 | 7 | return client.passport.verifyJWT(accessToken) 8 | .then(payload => { 9 | return client.service('users').get(payload.userId) 10 | }) 11 | .then(user => { 12 | return Promise.resolve(user) 13 | }) 14 | }, 15 | 16 | authenticate (jwtFromCookie = null, doAuthenticate = false) { 17 | console.log('authenticate, cookie: ' + (jwtFromCookie ? 'yes' : 'no') + ' , doAuthenticate: ' + doAuthenticate) 18 | 19 | client.authenticated = false 20 | 21 | // If there is no JWT then we don't try to authenticate (because strategy 'jwt' normally needs a JWT), *unless* the 22 | // parameter "doAuthenticate" is true - this is the case when we logged in just now and need to obtain a (new) JWT 23 | if (!jwtFromCookie && !doAuthenticate) { 24 | return Promise.resolve({user: null, jwt: null}) 25 | } 26 | 27 | let jwt = null 28 | 29 | return client.authenticate({ 30 | strategy: 'jwt', 31 | accessToken: jwtFromCookie 32 | }) 33 | .then((response) => { 34 | console.log('authenticate successful') 35 | 36 | jwt = response.accessToken 37 | // set client.authenticated flag TRUE 38 | client.authenticated = true 39 | 40 | return this.fetchUser(jwt) 41 | }) 42 | .then(user => { 43 | console.log('authenticate, got user') 44 | 45 | return Promise.resolve({user, jwt}) 46 | }) 47 | .catch((err) => { 48 | console.log('authenticate failed', err) 49 | 50 | return Promise.resolve({user: null, jwt: null}) 51 | }) 52 | }, 53 | 54 | signout () { 55 | console.log('signout') 56 | 57 | return client.logout() 58 | .then(() => { 59 | // set client.authenticated flag FALSE 60 | client.authenticated = false 61 | 62 | console.log('signout successful') 63 | }) 64 | .catch((err) => { 65 | console.log('signout failed', err) 66 | 67 | return Promise.reject(err) 68 | }) 69 | }, 70 | 71 | register (email, password) { 72 | return client.service('users').create({ 73 | email: email, 74 | password: password 75 | }) 76 | }, 77 | 78 | login (email, password) { 79 | return client.authenticate({ 80 | strategy: 'local', 81 | email: email, 82 | password: password 83 | }) 84 | } 85 | 86 | } 87 | 88 | export default auth 89 | -------------------------------------------------------------------------------- /client/src/api/client.js: -------------------------------------------------------------------------------- 1 | import feathers from '@feathersjs/feathers' 2 | import socketio from '@feathersjs/socketio-client' 3 | import auth from '@feathersjs/authentication-client' 4 | import io from 'socket.io-client' 5 | 6 | // 7 | // TODO get rid of this hardcoded API_ENDPOINT, make it configurable ! 8 | // 9 | // Note: the standard Feathers config approach doesn't work because this is next.js, so it has to work both on client and server 10 | // (that's "universal Javascript" biting us again - really complicating a lot of things!) - an approach we could use is this: 11 | // 12 | // https://github.com/zeit/next.js/tree/canary/examples/with-universal-configuration-runtime 13 | // 14 | const API_ENDPOINT = 'http://localhost:3030' 15 | 16 | // "forceNew": https://github.com/feathersjs/authentication/issues/662 17 | const socket = io(API_ENDPOINT, {transports: ['websocket'], forceNew: true}) 18 | 19 | // We don't configure JWT storage, the next.js app (which is separate from the Feathers backend) manages the JWT via a cookie 20 | const options = {} 21 | 22 | const client = feathers() 23 | .configure(socketio(socket)) 24 | .configure(auth(options)) 25 | 26 | client.service('/users') 27 | client.service('/counters') 28 | 29 | export default client -------------------------------------------------------------------------------- /client/src/components/authForm.js: -------------------------------------------------------------------------------- 1 | export default ({username, password, errorMessage, onChange, onSubmit}) => { 2 | 3 | return ( 4 |
5 |
6 | 7 | 8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 |
16 | {errorMessage} 17 |
18 | ) 19 | } -------------------------------------------------------------------------------- /client/src/components/withAuth.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Router from 'next/router' 3 | import { authenticate, FEATHERS_COOKIE, getServerCookie, setServerCookie, clearServerCookie } from '../store' 4 | import Error from 'next/error' 5 | import apiClient from '../api/client' 6 | 7 | export const PUBLIC = 'PUBLIC' 8 | 9 | /** 10 | * Higher order component for Next.js `pages` components. 11 | * 12 | * NOTE: depends of redux store. So you must use the `withRedux` HOC before this one. 13 | * 14 | * Example: 15 | * 16 | * ``` 17 | * export default withRedux(initStore, mapStateToProps)( 18 | * withAuth(PUBLIC)(MyPage) 19 | * ) 20 | * ``` 21 | * 22 | * Or using redux compose function: 23 | * 24 | * ``` 25 | * export default compose( 26 | * withRedux(initStore, mapStateToProps), 27 | * withAuth() 28 | * )(Private) 29 | * ``` 30 | * 31 | * It reads the user from the redux store or calls whoami API to verify current logged in user. 32 | * 33 | * To make a page public you have to pass PUBLIC as the `permission` parameter. 34 | * This is required to be able to show current logged in user from the first server render. 35 | * 36 | * @param permission: permission required to render this page. Use PUBLIC to make the page public. 37 | * @returns function(ChildComponent) React component to be wrapped. Must be a `page` component. 38 | */ 39 | export default (permission = null) => ChildComponent => class withAuth extends Component { 40 | 41 | static redirectToLogin (context) { 42 | const { isServer, req, res } = context 43 | 44 | if (isServer) { 45 | res.writeHead(302, { Location: `/login?next=${req.originalUrl}` }) 46 | res.end() 47 | } else { 48 | Router.push(`/login?next=${context.asPath}`) 49 | } 50 | } 51 | 52 | static userHasPermission (user) { 53 | const userGroups = user.groups || [] 54 | let userHasPerm = true 55 | 56 | // go here only if we have specific permission requirements 57 | if (permission) { 58 | // for instance if the permission is "admin" and the user name starts with admin 59 | userHasPerm = user.email.toLowerCase().startsWith(permission.toLowerCase()) 60 | } 61 | return userHasPerm 62 | } 63 | 64 | static async getInitialProps (context) { 65 | // public page passes the permission `PUBLIC` to this function 66 | const isPublicPage = permission == PUBLIC 67 | const { isServer, store, req, res } = context 68 | 69 | if (isServer) { 70 | // Authenticate, happens on page first load 71 | 72 | const jwtFromCookie = getServerCookie(req, FEATHERS_COOKIE) 73 | const result = await store.dispatch(authenticate(jwtFromCookie)) 74 | 75 | const newJwt = result.auth.jwt 76 | 77 | if (newJwt) { 78 | setServerCookie(res, FEATHERS_COOKIE, newJwt) 79 | } else { 80 | clearServerCookie(res, FEATHERS_COOKIE) 81 | } 82 | 83 | // client side - check if the Feathers API client is already authenticated 84 | } else if (!apiClient.authenticated) { 85 | console.log('Need to authenticate client-side') 86 | 87 | // get the JWT (from cookie - set by previous login or server-side authentication) and use it to auth the API client 88 | const jwt = store.getState().auth.jwt 89 | await store.dispatch(authenticate(jwt)) 90 | } 91 | 92 | return this.getInitProps(context, store.getState().auth.user, isPublicPage) 93 | } 94 | 95 | static async getInitProps (context, user, isPublicPage) { 96 | 97 | let proceedToPage = true 98 | let initProps = {} 99 | 100 | if (user) { 101 | // means the user is logged in so we verify permission 102 | if (!isPublicPage) { 103 | 104 | if (!this.userHasPermission(user)) { 105 | proceedToPage = false 106 | 107 | // Show a 404 page (see using next.js' built-in Error page) - TODO does this also work server-side? 108 | const statusCode = 404 109 | initProps = { statusCode } 110 | } 111 | } 112 | } else { 113 | 114 | // anonymous user 115 | if (!isPublicPage) { 116 | proceedToPage = false 117 | 118 | this.redirectToLogin(context) 119 | } 120 | } 121 | 122 | if (proceedToPage && typeof ChildComponent.getInitialProps === 'function') { 123 | initProps = await ChildComponent.getInitialProps(context) 124 | } 125 | 126 | return initProps 127 | } 128 | 129 | render () { 130 | // Use next's built-in error page 131 | if (this.props.statusCode) { 132 | return 133 | } 134 | 135 | return 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /client/src/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | webpack: (config) => { 3 | // Fixes npm packages that depend on `fs` module 4 | config.node = { 5 | fs: 'empty' 6 | } 7 | 8 | // https://github.com/zeit/next.js/issues/1582#issuecomment-291025361 9 | config.plugins = config.plugins.filter(plugin => { 10 | if (plugin.constructor.name === 'UglifyJsPlugin') { 11 | return false 12 | } else { 13 | return true 14 | } 15 | }) 16 | 17 | return config 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'next/link' 3 | import withRedux from 'next-redux-wrapper' 4 | import { compose } from 'redux' 5 | import { initStore, logout } from '../store' 6 | import withAuth, { PUBLIC } from '../components/withAuth' 7 | 8 | class Index extends React.Component { 9 | 10 | handleLogout = (e) => { 11 | e.preventDefault() 12 | this.props.dispatch(logout()) 13 | } 14 | 15 | render () { 16 | const { user } = this.props 17 | const name = user ? `${user.email}` : 'Anonymous' 18 | 19 | return ( 20 |
21 |

Hello {name}!

22 |
23 | 24 | Link to a private page 25 | 26 |
27 |
28 | 29 | Link to a private page with specific permission requirement 30 | 31 |
32 | { user === null 33 | ?
34 |
35 | 36 | Login 37 | 38 |
39 |
40 | 41 | Register 42 | 43 |
44 |
45 | : Logout } 46 |
47 | ) 48 | } 49 | } 50 | 51 | const mapStateToProps = (state) => { 52 | return { 53 | user: state.auth.user 54 | } 55 | } 56 | 57 | export default compose( 58 | withRedux(initStore, mapStateToProps), 59 | withAuth(PUBLIC) 60 | )(Index) 61 | -------------------------------------------------------------------------------- /client/src/pages/login.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import withRedux from 'next-redux-wrapper' 3 | import { initStore, login } from '../store' 4 | import AuthForm from '../components/authForm' 5 | 6 | class Login extends Component { 7 | 8 | state = { 9 | username: '', 10 | password: '', 11 | errorMessage: '' 12 | } 13 | 14 | handleOnChange = (e) => { 15 | this.setState({ 16 | [e.target.name]: e.target.value 17 | }) 18 | } 19 | 20 | handleLoginSubmit = (e) => { 21 | e.preventDefault() 22 | const { dispatch } = this.props 23 | const payload = { 24 | username: this.state.username, 25 | password: this.state.password 26 | } 27 | dispatch(login(payload)) 28 | .catch(err => { 29 | console.log('Login failed: ', err) 30 | this.setState({errorMessage: err.message}) 31 | }) 32 | } 33 | 34 | render () { 35 | const {username, password, errorMessage} = this.state; 36 | 37 | return ( 38 |
39 | Log in please: 40 | 41 |
42 | ) 43 | } 44 | } 45 | 46 | export default withRedux(initStore)(Login) 47 | -------------------------------------------------------------------------------- /client/src/pages/private-perm-required.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'next/link' 3 | import withRedux from 'next-redux-wrapper' 4 | import { compose } from 'redux' 5 | import { initStore } from '../store' 6 | import withAuth from '../components/withAuth' 7 | 8 | class PrivatePermRequired extends React.Component { 9 | 10 | render () { 11 | const { user } = this.props 12 | const name = user ? `${user.email}` : 'Anonymous' 13 | 14 | return ( 15 |
16 |
17 |

Hello {name}!

18 |

This content is for "admin" users only.

19 |
20 |
21 | 22 | Link to the home page 23 | 24 |
25 |
26 | ) 27 | } 28 | } 29 | 30 | const mapStateToProps = (state) => { 31 | return { 32 | user: state.auth.user 33 | } 34 | } 35 | 36 | export default compose( 37 | withRedux(initStore, mapStateToProps), 38 | withAuth('admin') 39 | )(PrivatePermRequired) 40 | -------------------------------------------------------------------------------- /client/src/pages/private.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'next/link' 3 | import withRedux from 'next-redux-wrapper' 4 | import { compose } from 'redux' 5 | import { initStore } from '../store' 6 | import withAuth from '../components/withAuth' 7 | import client from '../api/client' 8 | 9 | class Private extends React.Component { 10 | 11 | static async getInitialProps (context) { 12 | const counters = await client.service('counters').find() 13 | return { counterCount: counters.length } 14 | } 15 | 16 | render () { 17 | const { user } = this.props 18 | const name = user ? `${user.email}` : 'Anonymous' 19 | 20 | return ( 21 |
22 |
23 |

Hello {name}!

24 |

You have {this.props.counterCount} counters.

25 |

This content is available for logged in users only.

26 |
27 |
28 | 29 | Link to the home page 30 | 31 |
32 |
33 | ) 34 | } 35 | } 36 | 37 | const mapStateToProps = (state) => { 38 | return { 39 | user: state.auth.user 40 | } 41 | } 42 | 43 | export default compose( 44 | withRedux(initStore, mapStateToProps), 45 | withAuth() 46 | )(Private) 47 | -------------------------------------------------------------------------------- /client/src/pages/register.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import withRedux from 'next-redux-wrapper' 3 | import { initStore, register } from '../store' 4 | import AuthForm from '../components/authForm' 5 | 6 | class Register extends Component { 7 | 8 | state = { 9 | username: '', 10 | password: '', 11 | errorMessage: '' 12 | } 13 | 14 | handleOnChange = (e) => { 15 | this.setState({ 16 | [e.target.name]: e.target.value 17 | }) 18 | } 19 | 20 | handleRegisterSubmit = (e) => { 21 | e.preventDefault() 22 | const { dispatch } = this.props 23 | const payload = { 24 | username: this.state.username, 25 | password: this.state.password 26 | } 27 | dispatch(register(payload)) 28 | .catch(err => { 29 | console.log('Register failed: ', err) 30 | this.setState({errorMessage: err.message}) 31 | }) 32 | } 33 | 34 | render () { 35 | const {username, password, errorMessage} = this.state; 36 | 37 | return ( 38 |
39 | Register please: 40 | 41 |
42 | ) 43 | } 44 | } 45 | 46 | export default withRedux(initStore)(Register) 47 | -------------------------------------------------------------------------------- /client/src/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const next = require('next') 3 | 4 | const dev = process.env.NODE_ENV !== 'production' 5 | 6 | // Property "dir: './src'": see https://github.com/zeit/next.js/issues/819#issuecomment-308098780 7 | const app = next({ 8 | dir: './src', 9 | dev 10 | }) 11 | 12 | const handle = app.getRequestHandler() 13 | 14 | app.prepare() 15 | .then(() => { 16 | const server = express() 17 | 18 | server.get('*', (req, res) => { 19 | return handle(req, res) 20 | }) 21 | 22 | server.listen(3000, (err) => { 23 | if (err) throw err 24 | console.log('> Ready on http://localhost:3000') 25 | }) 26 | }) 27 | .catch((ex) => { 28 | console.error(ex.stack) 29 | process.exit(1) 30 | }) -------------------------------------------------------------------------------- /client/src/store.js: -------------------------------------------------------------------------------- 1 | import Router from 'next/router' 2 | import { createStore, applyMiddleware } from 'redux' 3 | import { composeWithDevTools } from 'redux-devtools-extension' 4 | import thunkMiddleware from 'redux-thunk' 5 | import auth from './api/auth' 6 | 7 | // Cookie parser used for extracting the JWT in an SSR scenario 8 | import cookieParser from 'cookie' 9 | 10 | // -- CONSTANTS 11 | export const FEATHERS_COOKIE = 'feathers-jwt' 12 | 13 | // -- INITIAL STORE 14 | const exampleInitialState = { 15 | auth: { 16 | user: null, 17 | jwt: null 18 | } 19 | } 20 | 21 | export const initStore = (initialState = exampleInitialState) => { 22 | return createStore(reducer, initialState, composeWithDevTools(applyMiddleware(thunkMiddleware))) 23 | } 24 | 25 | // -- ACTION TYPES 26 | export const actionTypes = { 27 | SET_AUTH: 'SET_AUTH' 28 | } 29 | 30 | // -- REDUCERS 31 | export const reducer = (state = exampleInitialState, action) => { 32 | switch (action.type) { 33 | case actionTypes.SET_AUTH: { 34 | return {...state, auth: action.auth} 35 | } 36 | 37 | default: return state 38 | } 39 | } 40 | 41 | // -- ACTIONS 42 | export function login (payload) { 43 | return (dispatch) => { 44 | return auth.login(payload.username, payload.password) 45 | .then(_ => { 46 | return auth.authenticate(null, true) 47 | }) 48 | .then(({user, jwt}) => { 49 | const result = dispatch({ 50 | type: actionTypes.SET_AUTH, 51 | auth: { 52 | user, 53 | jwt 54 | } 55 | }) 56 | 57 | setClientCookie(FEATHERS_COOKIE, jwt) 58 | Router.push('/') 59 | 60 | return result 61 | }) 62 | } 63 | } 64 | 65 | export function logout () { 66 | return (dispatch) => { 67 | return auth.signout() 68 | .then(() => { 69 | const result = dispatch({ 70 | type: actionTypes.SET_AUTH, 71 | auth: { 72 | user: null, 73 | jwt: null 74 | } 75 | }) 76 | 77 | clearClientCookie(FEATHERS_COOKIE) 78 | 79 | return result 80 | }) 81 | } 82 | } 83 | 84 | export function register (payload) { 85 | return (dispatch) => { 86 | return auth.register(payload.username, payload.password) 87 | .then(_ => { 88 | return auth.login(payload.username, payload.password) 89 | }) 90 | .then(_ => { 91 | return auth.authenticate() 92 | }) 93 | .then(({user, jwt}) => { 94 | const result = dispatch({ 95 | type: actionTypes.SET_AUTH, 96 | auth: { 97 | user, 98 | jwt 99 | } 100 | }) 101 | 102 | setClientCookie(FEATHERS_COOKIE, jwt) 103 | Router.push('/') 104 | 105 | return result 106 | }) 107 | } 108 | } 109 | 110 | export function authenticate (jwtFromCookie = null) { 111 | return (dispatch) => { 112 | return auth.authenticate(jwtFromCookie) 113 | .then(({user,jwt}) => { 114 | const result = dispatch({ 115 | type: actionTypes.SET_AUTH, 116 | auth: { 117 | user, 118 | jwt 119 | } 120 | }) 121 | 122 | return result 123 | }) 124 | } 125 | } 126 | 127 | // UTILS 128 | export function setClientCookie(name, value) { 129 | document.cookie = name + '=' + value 130 | } 131 | 132 | export function clearClientCookie(name) { 133 | document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:01 GMT;' 134 | } 135 | 136 | export function setServerCookie(res, name, value) { 137 | res.cookie(name, value, {}) // maxAge: 900000, httpOnly: true }) 138 | } 139 | 140 | export function clearServerCookie(res, name) { 141 | res.clearCookie(name); 142 | } 143 | 144 | export function getServerCookie(req, name) { 145 | const cookies = extractCookies(req) 146 | const cookie = cookies ? cookies[name] : null 147 | 148 | return cookie 149 | } 150 | 151 | function extractCookies(req) { 152 | const cookies = req.headers.cookie 153 | if (!cookies) return null 154 | 155 | return cookieParser.parse(cookies) 156 | } 157 | -------------------------------------------------------------------------------- /server/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "localhost", 3 | "port": 3030, 4 | "public": "../public/", 5 | "paginate": { 6 | "default": 10, 7 | "max": 50 8 | }, 9 | "authentication": { 10 | "secret": "28170cbaad6cde47d69352b2be4610e3264885309fcf177ebda7f1fb5cb19225b76ee4908c7890bc0bd29548acbf39ebbe493aafe666ac5ad9a859495c9e369b6bc9626057cc51b57893fdf03b8a769a19fb26bf30f251a101f828423e8572f43bd5e23c47fe453ce6f7c84090762f913827d14e2fa747ea3c2bafa1a59aaa4e0fad7636bf0617748e101c165b3f7dbd774329a172b24acc4a9ef16e40b6efba5e5e864721cbb0b78cb9a5c671a46e92dcd8d571b5919338afb4c71c5c11d8dad6163f53fccd934bdf8aa3114e109790c3115fc1c32803555a93e1f2f9336a758547d7b2df1941f2447892e0408c8eccd911083c5a523c296e748ae5a4a68a72", 11 | "strategies": [ 12 | "jwt", 13 | "local" 14 | ], 15 | "path": "/authentication", 16 | "service": "users", 17 | "jwt": { 18 | "header": { 19 | "type": "access" 20 | }, 21 | "audience": "https://yourdomain.com", 22 | "subject": "anonymous", 23 | "issuer": "feathers", 24 | "algorithm": "HS256", 25 | "expiresIn": "1d" 26 | }, 27 | "local": { 28 | "entity": "user", 29 | "usernameField": "email", 30 | "passwordField": "password" 31 | }, 32 | "github": { 33 | "clientID": "your github client id", 34 | "clientSecret": "your github client secret", 35 | "successRedirect": "/" 36 | }, 37 | "cookie": { 38 | "enabled": false, 39 | "name": "feathers-jwt", 40 | "httpOnly": false, 41 | "secure": false 42 | } 43 | }, 44 | "nedb": "../data" 45 | } 46 | -------------------------------------------------------------------------------- /server/config/production.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "next-server-app.feathersjs.com", 3 | "port": "PORT" 4 | } 5 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feathers-next-server", 3 | "description": "", 4 | "version": "0.0.0", 5 | "homepage": "", 6 | "main": "src", 7 | "keywords": [ 8 | "feathers" 9 | ], 10 | "bugs": {}, 11 | "directories": { 12 | "lib": "src", 13 | "test": "test/" 14 | }, 15 | "engines": { 16 | "node": ">= 6.0.0", 17 | "yarn": ">= 0.18.0" 18 | }, 19 | "scripts": { 20 | "dev": "nodemon --watch src/ --exec npm run debug", 21 | "test": "npm run eslint && npm run mocha", 22 | "eslint": "eslint src/. test/. --config .eslintrc.json", 23 | "start": "nodemon src/", 24 | "startdebug": "DEBUG=* nodemon src/", 25 | "mocha": "mocha test/ --recursive" 26 | }, 27 | "dependencies": { 28 | "@feathersjs/authentication": "^4.5.16", 29 | "@feathersjs/authentication-jwt": "^2.0.10", 30 | "@feathersjs/authentication-local": "^1.0.4", 31 | "@feathersjs/authentication-oauth2": "^1.0.3", 32 | "@feathersjs/configuration": "^4.5.15", 33 | "@feathersjs/errors": "^3.2.1", 34 | "@feathersjs/express": "^1.1.2", 35 | "@feathersjs/feathers": "^3.0.5", 36 | "@feathersjs/socketio": "^5.0.31", 37 | "body-parser": "^1.20.3", 38 | "compression": "^1.7.0", 39 | "cors": "^2.8.4", 40 | "feathers-authentication-hooks": "^0.1.4", 41 | "feathers-hooks-common": "^6.1.5", 42 | "feathers-nedb": "^2.7.0", 43 | "helmet": "^3.8.1", 44 | "nedb": "^1.8.0", 45 | "passport-github": "^1.1.0", 46 | "serve-favicon": "^2.4.3", 47 | "winston": "^2.3.1" 48 | }, 49 | "devDependencies": { 50 | "eslint": "^8.28.0", 51 | "mocha": "^10.8.2", 52 | "request": "^2.88.2", 53 | "request-promise": "^4.2.1" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /server/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leob/feathers-next/3dace5f9befab8f1c80853baec2d6fa2587da2d0/server/public/favicon.ico -------------------------------------------------------------------------------- /server/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Welcome to Feathers 4 | 62 | 63 | 64 |
65 | 66 |

A REST and realtime API layer for modern applications.

67 | 68 | 71 |
72 | 73 | 74 | -------------------------------------------------------------------------------- /server/src/app.hooks.js: -------------------------------------------------------------------------------- 1 | // Application hooks that run for every service 2 | const logger = require('./hooks/logger'); 3 | 4 | module.exports = { 5 | before: { 6 | all: [], 7 | find: [], 8 | get: [], 9 | create: [], 10 | update: [], 11 | patch: [], 12 | remove: [] 13 | }, 14 | 15 | after: { 16 | all: [ logger() ], 17 | find: [], 18 | get: [], 19 | create: [], 20 | update: [], 21 | patch: [], 22 | remove: [] 23 | }, 24 | 25 | error: { 26 | all: [ logger() ], 27 | find: [], 28 | get: [], 29 | create: [], 30 | update: [], 31 | patch: [], 32 | remove: [] 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /server/src/app.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const favicon = require('serve-favicon'); 3 | const compress = require('compression'); 4 | const cors = require('cors'); 5 | const helmet = require('helmet'); 6 | const bodyParser = require('body-parser'); 7 | 8 | const feathers = require('@feathersjs/feathers'); 9 | const express = require('@feathersjs/express'); 10 | const configuration = require('@feathersjs/configuration'); 11 | const rest = require('@feathersjs/express/rest'); 12 | const socketio = require('@feathersjs/socketio'); 13 | 14 | const handler = require('@feathersjs/express/errors'); 15 | const notFound = require('feathers-errors/not-found'); 16 | 17 | const middleware = require('./middleware'); 18 | const services = require('./services'); 19 | const channels = require('./channels'); 20 | const appHooks = require('./app.hooks'); 21 | 22 | const authentication = require('./authentication'); 23 | 24 | const app = express(feathers()); 25 | 26 | // Load app configuration 27 | app.configure(configuration()); 28 | // Enable CORS, security, compression, favicon and body parsing 29 | app.use(cors()); 30 | app.use(helmet()); 31 | app.use(compress()); 32 | app.use(bodyParser.json()); 33 | app.use(bodyParser.urlencoded({ extended: true })); 34 | app.use(favicon(path.join(app.get('public'), 'favicon.ico'))); 35 | // Host the public folder 36 | app.use('/', express.static(app.get('public'))); 37 | 38 | app.configure(rest()); 39 | app.configure(socketio()); 40 | 41 | // Configure other middleware (see `middleware/index.js`) 42 | app.configure(middleware); 43 | app.configure(authentication); 44 | // Set up our services (see `services/index.js`) and channels 45 | app.configure(services); 46 | app.configure(channels); 47 | // Configure a middleware for 404s and the error handler 48 | app.use(notFound()); 49 | app.use(handler()); 50 | 51 | app.hooks(appHooks); 52 | 53 | module.exports = app; 54 | -------------------------------------------------------------------------------- /server/src/authentication.js: -------------------------------------------------------------------------------- 1 | const authentication = require('@feathersjs/authentication'); 2 | const jwt = require('@feathersjs/authentication-jwt'); 3 | const local = require('@feathersjs/authentication-local'); 4 | const oauth2 = require('@feathersjs/authentication-oauth2'); 5 | const GithubStrategy = require('passport-github'); 6 | 7 | module.exports = function () { 8 | const app = this; 9 | const config = app.get('authentication'); 10 | 11 | // Set up authentication with the secret 12 | app.configure(authentication(config)); 13 | 14 | app.configure(jwt()); 15 | app.configure(local(config.local)); 16 | 17 | // app.configure(oauth2(Object.assign({ 18 | // name: 'github', 19 | // Strategy: GithubStrategy 20 | // }, config.github))); 21 | 22 | // The `authentication` service is used to create a JWT. 23 | // The before `create` hook registers strategies that can be used 24 | // to create a new valid JWT (e.g. local or oauth2) 25 | app.service('authentication').hooks({ 26 | before: { 27 | create: [ 28 | authentication.hooks.authenticate(config.strategies) 29 | ], 30 | remove: [ 31 | authentication.hooks.authenticate('jwt') 32 | ] 33 | } 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /server/src/channels.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app) { 2 | if(typeof app.channel !== 'function') { 3 | // If no real-time functionality has been configured just return 4 | return; 5 | } 6 | 7 | app.on('connection', connection => { 8 | // On a new real-time connection, add it to the anonymous channel 9 | app.channel('anonymous').join(connection); 10 | }); 11 | 12 | app.on('login', (authResult, { connection }) => { 13 | // connection can be undefined if there is no 14 | // real-time connection, e.g. when logging in via REST 15 | if(connection) { 16 | // Obtain the logged in user from the connection 17 | // const user = connection.user; 18 | 19 | // The connection is no longer anonymous, remove it 20 | app.channel('anonymous').leave(connection); 21 | 22 | // Add it to the authenticated user channel 23 | app.channel('authenticated').join(connection); 24 | 25 | // Channels can be named anything and joined on any condition 26 | 27 | // E.g. to send real-time events only to admins use 28 | // if(user.isAdmin) { app.channel('admins').join(connection); } 29 | 30 | // If the user has joined e.g. chat rooms 31 | // if(Array.isArray(user.rooms)) user.rooms.forEach(room => app.channel(`rooms/${room.id}`).join(channel)); 32 | 33 | // Easily organize users by email and userid for things like messaging 34 | // app.channel(`emails/${user.email}`).join(channel); 35 | // app.channel(`userIds/$(user.id}`).join(channel); 36 | } 37 | }); 38 | 39 | app.publish((data, hook) => { // eslint-disable-line no-unused-vars 40 | // Here you can add event publishers to channels set up in `channels.js` 41 | // To publish only for a specific event use `app.publish(eventname, () => {})` 42 | 43 | // e.g. to publish all service events to all authenticated users use 44 | return app.channel('authenticated'); 45 | }); 46 | 47 | // Here you can also add service specific event publishers 48 | // e..g the publish the `users` service `created` event to the `admins` channel 49 | // app.service('users').publish('created', () => app.channel('admins')); 50 | 51 | // With the userid and email organization from above you can easily select involved users 52 | // app.service('messages').publish(() => { 53 | // return [ 54 | // app.channel(`userIds/${data.createdBy}`), 55 | // app.channel(`emails/${data.recipientEmail}`) 56 | // ]; 57 | // }); 58 | }; 59 | -------------------------------------------------------------------------------- /server/src/hooks/logger.js: -------------------------------------------------------------------------------- 1 | // A hook that logs service method before, after and error 2 | const logger = require('winston'); 3 | 4 | module.exports = function () { 5 | return function (hook) { 6 | let message = `${hook.type}: ${hook.path} - Method: ${hook.method}`; 7 | 8 | if (hook.type === 'error') { 9 | message += `: ${hook.error.message}`; 10 | } 11 | 12 | logger.info(message); 13 | logger.debug('hook.data', hook.data); 14 | logger.debug('hook.params', hook.params); 15 | 16 | if (hook.result) { 17 | logger.debug('hook.result', hook.result); 18 | } 19 | 20 | if (hook.error) { 21 | logger.error(hook.error); 22 | } 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /server/src/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const logger = require('winston'); 3 | const app = require('./app'); 4 | const port = app.get('port'); 5 | const server = app.listen(port); 6 | 7 | process.on('unhandledRejection', (reason, p) => 8 | logger.error('Unhandled Rejection at: Promise ', p, reason) 9 | ); 10 | 11 | server.on('listening', () => 12 | logger.info(`Feathers application started on ${app.get('host')}:${port}`) 13 | ); 14 | -------------------------------------------------------------------------------- /server/src/middleware/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | // Add your custom middleware here. Remember, that 3 | // in Express the order matters 4 | const app = this; // eslint-disable-line no-unused-vars 5 | }; 6 | -------------------------------------------------------------------------------- /server/src/models/users.model.js: -------------------------------------------------------------------------------- 1 | const NeDB = require('nedb'); 2 | const path = require('path'); 3 | 4 | module.exports = function (app) { 5 | const dbPath = app.get('nedb'); 6 | const Model = new NeDB({ 7 | filename: path.join(dbPath, 'users.db'), 8 | autoload: true 9 | }); 10 | 11 | Model.ensureIndex({ fieldName: 'email', unique: true }); 12 | 13 | return Model; 14 | }; 15 | -------------------------------------------------------------------------------- /server/src/services/counters/counters.class.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | class Service { 3 | constructor (options) { 4 | this.options = options || {}; 5 | } 6 | 7 | async find (params) { 8 | return [1, 2, 3]; 9 | } 10 | } 11 | 12 | module.exports = function (options) { 13 | return new Service(options); 14 | }; 15 | 16 | module.exports.Service = Service; 17 | -------------------------------------------------------------------------------- /server/src/services/counters/counters.hooks.js: -------------------------------------------------------------------------------- 1 | const { authenticate } = require('@feathersjs/authentication').hooks; 2 | 3 | module.exports = { 4 | before: { 5 | all: [ authenticate('jwt') ], 6 | find: [], 7 | get: [], 8 | create: [], 9 | update: [], 10 | patch: [], 11 | remove: [] 12 | }, 13 | 14 | after: { 15 | all: [], 16 | find: [], 17 | get: [], 18 | create: [], 19 | update: [], 20 | patch: [], 21 | remove: [] 22 | }, 23 | 24 | error: { 25 | all: [], 26 | find: [], 27 | get: [], 28 | create: [], 29 | update: [], 30 | patch: [], 31 | remove: [] 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /server/src/services/counters/counters.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `counters` service on path `/counters` 2 | const createService = require('./counters.class.js'); 3 | const hooks = require('./counters.hooks'); 4 | 5 | module.exports = function (app) { 6 | 7 | const options = { 8 | name: 'counters', 9 | }; 10 | 11 | // Initialize our service with any options it requires 12 | app.use('/counters', createService(options)); 13 | 14 | // Get our initialized service so that we can register hooks and filters 15 | const service = app.service('counters'); 16 | 17 | service.hooks(hooks); 18 | }; 19 | -------------------------------------------------------------------------------- /server/src/services/index.js: -------------------------------------------------------------------------------- 1 | const users = require('./users/users.service.js'); 2 | const counters = require('./counters/counters.service.js'); 3 | 4 | module.exports = function () { 5 | const app = this; // eslint-disable-line no-unused-vars 6 | 7 | app.configure(users); 8 | app.configure(counters); 9 | }; 10 | -------------------------------------------------------------------------------- /server/src/services/users/users.hooks.js: -------------------------------------------------------------------------------- 1 | const { hashPassword } = require('@feathersjs/authentication-local').hooks; 2 | const { restrictToOwner } = require('feathers-authentication-hooks'); 3 | const { protect } = require('@feathersjs/authentication-local').hooks; 4 | const { authenticate } = require('@feathersjs/authentication').hooks; 5 | 6 | const restrict = [ 7 | authenticate('jwt'), 8 | restrictToOwner({ 9 | idField: '_id', 10 | ownerField: '_id' 11 | }) 12 | ]; 13 | 14 | module.exports = { 15 | before: { 16 | all: [], 17 | find: [ authenticate('jwt') ], 18 | get: [ ...restrict ], 19 | create: [ hashPassword() ], 20 | update: [ ...restrict, hashPassword() ], 21 | patch: [ ...restrict, hashPassword() ], 22 | remove: [ ...restrict ] 23 | }, 24 | 25 | after: { 26 | all: [ protect('password') ], 27 | find: [], 28 | get: [], 29 | create: [], 30 | update: [], 31 | patch: [], 32 | remove: [] 33 | }, 34 | 35 | error: { 36 | all: [], 37 | find: [], 38 | get: [], 39 | create: [], 40 | update: [], 41 | patch: [], 42 | remove: [] 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /server/src/services/users/users.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `users` service on path `/users` 2 | const createService = require('feathers-nedb'); 3 | const createModel = require('../../models/users.model'); 4 | const hooks = require('./users.hooks'); 5 | 6 | module.exports = function () { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate'); 10 | 11 | const options = { 12 | name: 'users', 13 | Model, 14 | paginate 15 | }; 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/users', createService(options)); 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('users'); 22 | 23 | service.hooks(hooks); 24 | }; 25 | -------------------------------------------------------------------------------- /server/test/app.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const rp = require('request-promise'); 3 | const app = require('../src/app'); 4 | 5 | describe('Feathers application tests', () => { 6 | before(function(done) { 7 | this.server = app.listen(3030); 8 | this.server.once('listening', () => done()); 9 | }); 10 | 11 | after(function(done) { 12 | this.server.close(done); 13 | }); 14 | 15 | it('starts and shows the index page', () => { 16 | return rp('http://localhost:3030').then(body => 17 | assert.ok(body.indexOf('') !== -1) 18 | ); 19 | }); 20 | 21 | describe('404', function() { 22 | it('shows a 404 HTML page', () => { 23 | return rp({ 24 | url: 'http://localhost:3030/path/to/nowhere', 25 | headers: { 26 | 'Accept': 'text/html' 27 | } 28 | }).catch(res => { 29 | assert.equal(res.statusCode, 404); 30 | assert.ok(res.error.indexOf('') !== -1); 31 | }); 32 | }); 33 | 34 | it('shows a 404 JSON error without stack trace', () => { 35 | return rp({ 36 | url: 'http://localhost:3030/path/to/nowhere', 37 | json: true 38 | }).catch(res => { 39 | assert.equal(res.statusCode, 404); 40 | assert.equal(res.error.code, 404); 41 | assert.equal(res.error.message, 'Page not found'); 42 | assert.equal(res.error.name, 'NotFound'); 43 | }); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /server/test/services/counters.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../src/app'); 3 | 4 | describe('\'counters\' service', () => { 5 | it('registered the service', () => { 6 | const service = app.service('counters'); 7 | 8 | assert.ok(service, 'Registered the service'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /server/test/services/users.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../src/app'); 3 | 4 | describe('\'users\' service', () => { 5 | it('registered the service', () => { 6 | const service = app.service('users'); 7 | 8 | assert.ok(service, 'Registered the service'); 9 | }); 10 | }); 11 | --------------------------------------------------------------------------------