├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── package-lock.json ├── package.json └── server.js /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18.15.0-slim 2 | MAINTAINER Jonathan Gros-Dubois 3 | 4 | LABEL version="9.2.0" 5 | LABEL description="Docker file for SCC State Server" 6 | 7 | RUN mkdir -p /usr/src/ 8 | WORKDIR /usr/src/ 9 | COPY . /usr/src/ 10 | 11 | RUN npm install . 12 | 13 | EXPOSE 7777 14 | 15 | CMD ["npm", "start"] 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2013-2023 SocketCluster.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scc-state 2 | Cluster state tracking and notification engine for SCC. 3 | 4 | ### Usage 5 | 6 | ```js 7 | SCC_STATE_LOG_LEVEL=1 node server.js 8 | ``` 9 | 10 | ### Log levels 11 | 12 | * 3 - log everything 13 | * 2 - warnings and errors 14 | * 1 - errors only 15 | * 0 - log nothing 16 | 17 | ### Build and deploy to DockerHub 18 | 19 | Replace `x.x.x` with the version number. 20 | 21 | ``` 22 | docker build -t socketcluster/scc-state:vx.x.x . 23 | ``` 24 | 25 | ``` 26 | docker push socketcluster/scc-state:vx.x.x 27 | ``` 28 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scc-state", 3 | "version": "9.2.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "scc-state", 9 | "version": "9.2.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "eetase": "^7.0.0", 13 | "socketcluster-server": "^19.0.1" 14 | } 15 | }, 16 | "node_modules/ag-auth": { 17 | "version": "2.0.0", 18 | "resolved": "https://registry.npmjs.org/ag-auth/-/ag-auth-2.0.0.tgz", 19 | "integrity": "sha512-gk5BgWzijPUaCMDYFtuggIlUDWGMlMSOdF/tdQa5sM7Nh0KowohFh3P3sx06gegufKWzWF97dwP/7hLg3CkqWg==", 20 | "dependencies": { 21 | "jsonwebtoken": "^9.0.0", 22 | "sc-errors": "^2.0.0" 23 | } 24 | }, 25 | "node_modules/ag-channel": { 26 | "version": "5.0.0", 27 | "resolved": "https://registry.npmjs.org/ag-channel/-/ag-channel-5.0.0.tgz", 28 | "integrity": "sha512-bArHkdqQxynim981t8FLZM5TfA0v7p081OlFdOxs6clB79GSGcGlOQMDa31DT9F5VMjzqNiJmhfGwinvfU/3Zg==", 29 | "dependencies": { 30 | "consumable-stream": "^2.0.0" 31 | } 32 | }, 33 | "node_modules/ag-request": { 34 | "version": "1.0.1", 35 | "resolved": "https://registry.npmjs.org/ag-request/-/ag-request-1.0.1.tgz", 36 | "integrity": "sha512-3F4pDpLy9mxOXop7LoWE78J5g2jmiEJ0gJfzcECOsf/NaCfyeNmOdNLDVM5dS4Hvbi9T+HENL4DmXq5XSotPaA==", 37 | "dependencies": { 38 | "sc-errors": "^2.0.2" 39 | } 40 | }, 41 | "node_modules/ag-simple-broker": { 42 | "version": "6.0.0", 43 | "resolved": "https://registry.npmjs.org/ag-simple-broker/-/ag-simple-broker-6.0.0.tgz", 44 | "integrity": "sha512-6rjPCPrik3QReJKxUzASSzoYLhOmVshVqZXTIWTqB5vKogI1d0s50PcZsOGov14V0EJstpXWdLMaPtnZDQH76w==", 45 | "dependencies": { 46 | "ag-channel": "^5.0.0", 47 | "async-stream-emitter": "^7.0.0", 48 | "stream-demux": "^10.0.0" 49 | } 50 | }, 51 | "node_modules/async-stream-emitter": { 52 | "version": "7.0.1", 53 | "resolved": "https://registry.npmjs.org/async-stream-emitter/-/async-stream-emitter-7.0.1.tgz", 54 | "integrity": "sha512-1bgA3iZ80rCBX2LocvsyZPy0QB3/xM+CsXBze2HDHLmshOqx2JlAANGq23djaJ48e9fpcKzTzS1QM0hAKKI0UQ==", 55 | "dependencies": { 56 | "stream-demux": "^10.0.1" 57 | } 58 | }, 59 | "node_modules/base64id": { 60 | "version": "2.0.0", 61 | "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", 62 | "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", 63 | "engines": { 64 | "node": "^4.5.0 || >= 5.9" 65 | } 66 | }, 67 | "node_modules/buffer-equal-constant-time": { 68 | "version": "1.0.1", 69 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 70 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" 71 | }, 72 | "node_modules/clone-deep": { 73 | "version": "4.0.1", 74 | "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", 75 | "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", 76 | "dependencies": { 77 | "is-plain-object": "^2.0.4", 78 | "kind-of": "^6.0.2", 79 | "shallow-clone": "^3.0.0" 80 | }, 81 | "engines": { 82 | "node": ">=6" 83 | } 84 | }, 85 | "node_modules/consumable-stream": { 86 | "version": "2.0.0", 87 | "resolved": "https://registry.npmjs.org/consumable-stream/-/consumable-stream-2.0.0.tgz", 88 | "integrity": "sha512-I6WA2JVYXs/68rEvi1ie3rZjP6qusTVFEQkbzR+WC+fY56TpwiGTIDJETsrnlxv5CsnmK69ps6CkYvIbpEEqBA==" 89 | }, 90 | "node_modules/ecdsa-sig-formatter": { 91 | "version": "1.0.11", 92 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 93 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 94 | "dependencies": { 95 | "safe-buffer": "^5.0.1" 96 | } 97 | }, 98 | "node_modules/eetase": { 99 | "version": "7.0.0", 100 | "resolved": "https://registry.npmjs.org/eetase/-/eetase-7.0.0.tgz", 101 | "integrity": "sha512-GX4FLu3Q3I4pnteJX/cL1KzVIa907xRJnAXmF1YzjPwApxPYkFoK/8U6k+fSktBzSERBp7N4c51t6d2t+a0sLg==", 102 | "dependencies": { 103 | "async-stream-emitter": "^7.0.1" 104 | } 105 | }, 106 | "node_modules/is-plain-object": { 107 | "version": "2.0.4", 108 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", 109 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", 110 | "dependencies": { 111 | "isobject": "^3.0.1" 112 | }, 113 | "engines": { 114 | "node": ">=0.10.0" 115 | } 116 | }, 117 | "node_modules/isobject": { 118 | "version": "3.0.1", 119 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", 120 | "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", 121 | "engines": { 122 | "node": ">=0.10.0" 123 | } 124 | }, 125 | "node_modules/jsonwebtoken": { 126 | "version": "9.0.0", 127 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", 128 | "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", 129 | "dependencies": { 130 | "jws": "^3.2.2", 131 | "lodash": "^4.17.21", 132 | "ms": "^2.1.1", 133 | "semver": "^7.3.8" 134 | }, 135 | "engines": { 136 | "node": ">=12", 137 | "npm": ">=6" 138 | } 139 | }, 140 | "node_modules/jwa": { 141 | "version": "1.4.1", 142 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 143 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 144 | "dependencies": { 145 | "buffer-equal-constant-time": "1.0.1", 146 | "ecdsa-sig-formatter": "1.0.11", 147 | "safe-buffer": "^5.0.1" 148 | } 149 | }, 150 | "node_modules/jws": { 151 | "version": "3.2.2", 152 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 153 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 154 | "dependencies": { 155 | "jwa": "^1.4.1", 156 | "safe-buffer": "^5.0.1" 157 | } 158 | }, 159 | "node_modules/kind-of": { 160 | "version": "6.0.3", 161 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", 162 | "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", 163 | "engines": { 164 | "node": ">=0.10.0" 165 | } 166 | }, 167 | "node_modules/lodash": { 168 | "version": "4.17.21", 169 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 170 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 171 | }, 172 | "node_modules/lru-cache": { 173 | "version": "6.0.0", 174 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 175 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 176 | "dependencies": { 177 | "yallist": "^4.0.0" 178 | }, 179 | "engines": { 180 | "node": ">=10" 181 | } 182 | }, 183 | "node_modules/ms": { 184 | "version": "2.1.3", 185 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 186 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 187 | }, 188 | "node_modules/safe-buffer": { 189 | "version": "5.2.1", 190 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 191 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 192 | "funding": [ 193 | { 194 | "type": "github", 195 | "url": "https://github.com/sponsors/feross" 196 | }, 197 | { 198 | "type": "patreon", 199 | "url": "https://www.patreon.com/feross" 200 | }, 201 | { 202 | "type": "consulting", 203 | "url": "https://feross.org/support" 204 | } 205 | ] 206 | }, 207 | "node_modules/sc-errors": { 208 | "version": "2.0.3", 209 | "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-2.0.3.tgz", 210 | "integrity": "sha512-HNpClBWpo7zxLBnhH0U/FbC19Gl3OJlVyPxo9Q2eomfdWgYfd84uhqe0LRgybc+nSpcYjtF08+/dKPLugLMMeQ==" 211 | }, 212 | "node_modules/sc-formatter": { 213 | "version": "4.0.0", 214 | "resolved": "https://registry.npmjs.org/sc-formatter/-/sc-formatter-4.0.0.tgz", 215 | "integrity": "sha512-MgUIvuca+90fBrCWY5LdlU9YUWjlkPFwdpvmomcwQEu3t2id/6YHdG2nhB6o7nhRp4ocfmcXQTh00r/tJtynSg==" 216 | }, 217 | "node_modules/semver": { 218 | "version": "7.3.8", 219 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", 220 | "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", 221 | "dependencies": { 222 | "lru-cache": "^6.0.0" 223 | }, 224 | "bin": { 225 | "semver": "bin/semver.js" 226 | }, 227 | "engines": { 228 | "node": ">=10" 229 | } 230 | }, 231 | "node_modules/shallow-clone": { 232 | "version": "3.0.1", 233 | "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", 234 | "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", 235 | "dependencies": { 236 | "kind-of": "^6.0.2" 237 | }, 238 | "engines": { 239 | "node": ">=8" 240 | } 241 | }, 242 | "node_modules/socketcluster-server": { 243 | "version": "19.0.1", 244 | "resolved": "https://registry.npmjs.org/socketcluster-server/-/socketcluster-server-19.0.1.tgz", 245 | "integrity": "sha512-IfMZxTkzvqOUExqiVxkxE2DjN/tap6WpbplatptsHKa58wfN6SdfcYCYeR3hlUBOx+cG09/hPxlN+R5tYZF0Mg==", 246 | "dependencies": { 247 | "ag-auth": "^2.0.0", 248 | "ag-request": "^1.0.1", 249 | "ag-simple-broker": "^6.0.0", 250 | "async-stream-emitter": "^7.0.1", 251 | "base64id": "^2.0.0", 252 | "clone-deep": "^4.0.1", 253 | "sc-errors": "^2.0.3", 254 | "sc-formatter": "^4.0.0", 255 | "stream-demux": "^10.0.1", 256 | "writable-consumable-stream": "^4.1.0", 257 | "ws": "^8.9.0" 258 | } 259 | }, 260 | "node_modules/stream-demux": { 261 | "version": "10.0.1", 262 | "resolved": "https://registry.npmjs.org/stream-demux/-/stream-demux-10.0.1.tgz", 263 | "integrity": "sha512-QjTYLJWpZxZ6uL5R1JzgOzjvao8zDx78ec+uOjHNeVc/9TuasYLldoVrYARZeT1xI1hFYuiKf13IM8b4wamhHg==", 264 | "dependencies": { 265 | "consumable-stream": "^3.0.0", 266 | "writable-consumable-stream": "^4.1.0" 267 | } 268 | }, 269 | "node_modules/stream-demux/node_modules/consumable-stream": { 270 | "version": "3.0.0", 271 | "resolved": "https://registry.npmjs.org/consumable-stream/-/consumable-stream-3.0.0.tgz", 272 | "integrity": "sha512-CnnsJ9OG9ouxAjt3pc63/DaerezRo/WudqU71pc5epaIUi7NHu2T4v+3f0nKbbCY7icS/TfQ1Satr9rwZ7Jwsg==" 273 | }, 274 | "node_modules/writable-consumable-stream": { 275 | "version": "4.1.0", 276 | "resolved": "https://registry.npmjs.org/writable-consumable-stream/-/writable-consumable-stream-4.1.0.tgz", 277 | "integrity": "sha512-4cjCPd4Ayfbix0qqPCzMbnPPZKRh/cKeNCj05unybP3/sRkRAOxh7rSwbhxs3YB6G4/Z2p/2FRBEIQcTeB4jyw==", 278 | "dependencies": { 279 | "consumable-stream": "^3.0.0" 280 | } 281 | }, 282 | "node_modules/writable-consumable-stream/node_modules/consumable-stream": { 283 | "version": "3.0.0", 284 | "resolved": "https://registry.npmjs.org/consumable-stream/-/consumable-stream-3.0.0.tgz", 285 | "integrity": "sha512-CnnsJ9OG9ouxAjt3pc63/DaerezRo/WudqU71pc5epaIUi7NHu2T4v+3f0nKbbCY7icS/TfQ1Satr9rwZ7Jwsg==" 286 | }, 287 | "node_modules/ws": { 288 | "version": "8.13.0", 289 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", 290 | "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", 291 | "engines": { 292 | "node": ">=10.0.0" 293 | }, 294 | "peerDependencies": { 295 | "bufferutil": "^4.0.1", 296 | "utf-8-validate": ">=5.0.2" 297 | }, 298 | "peerDependenciesMeta": { 299 | "bufferutil": { 300 | "optional": true 301 | }, 302 | "utf-8-validate": { 303 | "optional": true 304 | } 305 | } 306 | }, 307 | "node_modules/yallist": { 308 | "version": "4.0.0", 309 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 310 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 311 | } 312 | }, 313 | "dependencies": { 314 | "ag-auth": { 315 | "version": "2.0.0", 316 | "resolved": "https://registry.npmjs.org/ag-auth/-/ag-auth-2.0.0.tgz", 317 | "integrity": "sha512-gk5BgWzijPUaCMDYFtuggIlUDWGMlMSOdF/tdQa5sM7Nh0KowohFh3P3sx06gegufKWzWF97dwP/7hLg3CkqWg==", 318 | "requires": { 319 | "jsonwebtoken": "^9.0.0", 320 | "sc-errors": "^2.0.0" 321 | } 322 | }, 323 | "ag-channel": { 324 | "version": "5.0.0", 325 | "resolved": "https://registry.npmjs.org/ag-channel/-/ag-channel-5.0.0.tgz", 326 | "integrity": "sha512-bArHkdqQxynim981t8FLZM5TfA0v7p081OlFdOxs6clB79GSGcGlOQMDa31DT9F5VMjzqNiJmhfGwinvfU/3Zg==", 327 | "requires": { 328 | "consumable-stream": "^2.0.0" 329 | } 330 | }, 331 | "ag-request": { 332 | "version": "1.0.1", 333 | "resolved": "https://registry.npmjs.org/ag-request/-/ag-request-1.0.1.tgz", 334 | "integrity": "sha512-3F4pDpLy9mxOXop7LoWE78J5g2jmiEJ0gJfzcECOsf/NaCfyeNmOdNLDVM5dS4Hvbi9T+HENL4DmXq5XSotPaA==", 335 | "requires": { 336 | "sc-errors": "^2.0.2" 337 | } 338 | }, 339 | "ag-simple-broker": { 340 | "version": "6.0.0", 341 | "resolved": "https://registry.npmjs.org/ag-simple-broker/-/ag-simple-broker-6.0.0.tgz", 342 | "integrity": "sha512-6rjPCPrik3QReJKxUzASSzoYLhOmVshVqZXTIWTqB5vKogI1d0s50PcZsOGov14V0EJstpXWdLMaPtnZDQH76w==", 343 | "requires": { 344 | "ag-channel": "^5.0.0", 345 | "async-stream-emitter": "^7.0.0", 346 | "stream-demux": "^10.0.0" 347 | } 348 | }, 349 | "async-stream-emitter": { 350 | "version": "7.0.1", 351 | "resolved": "https://registry.npmjs.org/async-stream-emitter/-/async-stream-emitter-7.0.1.tgz", 352 | "integrity": "sha512-1bgA3iZ80rCBX2LocvsyZPy0QB3/xM+CsXBze2HDHLmshOqx2JlAANGq23djaJ48e9fpcKzTzS1QM0hAKKI0UQ==", 353 | "requires": { 354 | "stream-demux": "^10.0.1" 355 | } 356 | }, 357 | "base64id": { 358 | "version": "2.0.0", 359 | "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", 360 | "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" 361 | }, 362 | "buffer-equal-constant-time": { 363 | "version": "1.0.1", 364 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 365 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" 366 | }, 367 | "clone-deep": { 368 | "version": "4.0.1", 369 | "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", 370 | "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", 371 | "requires": { 372 | "is-plain-object": "^2.0.4", 373 | "kind-of": "^6.0.2", 374 | "shallow-clone": "^3.0.0" 375 | } 376 | }, 377 | "consumable-stream": { 378 | "version": "2.0.0", 379 | "resolved": "https://registry.npmjs.org/consumable-stream/-/consumable-stream-2.0.0.tgz", 380 | "integrity": "sha512-I6WA2JVYXs/68rEvi1ie3rZjP6qusTVFEQkbzR+WC+fY56TpwiGTIDJETsrnlxv5CsnmK69ps6CkYvIbpEEqBA==" 381 | }, 382 | "ecdsa-sig-formatter": { 383 | "version": "1.0.11", 384 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 385 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 386 | "requires": { 387 | "safe-buffer": "^5.0.1" 388 | } 389 | }, 390 | "eetase": { 391 | "version": "7.0.0", 392 | "resolved": "https://registry.npmjs.org/eetase/-/eetase-7.0.0.tgz", 393 | "integrity": "sha512-GX4FLu3Q3I4pnteJX/cL1KzVIa907xRJnAXmF1YzjPwApxPYkFoK/8U6k+fSktBzSERBp7N4c51t6d2t+a0sLg==", 394 | "requires": { 395 | "async-stream-emitter": "^7.0.1" 396 | } 397 | }, 398 | "is-plain-object": { 399 | "version": "2.0.4", 400 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", 401 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", 402 | "requires": { 403 | "isobject": "^3.0.1" 404 | } 405 | }, 406 | "isobject": { 407 | "version": "3.0.1", 408 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", 409 | "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" 410 | }, 411 | "jsonwebtoken": { 412 | "version": "9.0.0", 413 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", 414 | "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", 415 | "requires": { 416 | "jws": "^3.2.2", 417 | "lodash": "^4.17.21", 418 | "ms": "^2.1.1", 419 | "semver": "^7.3.8" 420 | } 421 | }, 422 | "jwa": { 423 | "version": "1.4.1", 424 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 425 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 426 | "requires": { 427 | "buffer-equal-constant-time": "1.0.1", 428 | "ecdsa-sig-formatter": "1.0.11", 429 | "safe-buffer": "^5.0.1" 430 | } 431 | }, 432 | "jws": { 433 | "version": "3.2.2", 434 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 435 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 436 | "requires": { 437 | "jwa": "^1.4.1", 438 | "safe-buffer": "^5.0.1" 439 | } 440 | }, 441 | "kind-of": { 442 | "version": "6.0.3", 443 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", 444 | "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" 445 | }, 446 | "lodash": { 447 | "version": "4.17.21", 448 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 449 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 450 | }, 451 | "lru-cache": { 452 | "version": "6.0.0", 453 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 454 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 455 | "requires": { 456 | "yallist": "^4.0.0" 457 | } 458 | }, 459 | "ms": { 460 | "version": "2.1.3", 461 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 462 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 463 | }, 464 | "safe-buffer": { 465 | "version": "5.2.1", 466 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 467 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 468 | }, 469 | "sc-errors": { 470 | "version": "2.0.3", 471 | "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-2.0.3.tgz", 472 | "integrity": "sha512-HNpClBWpo7zxLBnhH0U/FbC19Gl3OJlVyPxo9Q2eomfdWgYfd84uhqe0LRgybc+nSpcYjtF08+/dKPLugLMMeQ==" 473 | }, 474 | "sc-formatter": { 475 | "version": "4.0.0", 476 | "resolved": "https://registry.npmjs.org/sc-formatter/-/sc-formatter-4.0.0.tgz", 477 | "integrity": "sha512-MgUIvuca+90fBrCWY5LdlU9YUWjlkPFwdpvmomcwQEu3t2id/6YHdG2nhB6o7nhRp4ocfmcXQTh00r/tJtynSg==" 478 | }, 479 | "semver": { 480 | "version": "7.3.8", 481 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", 482 | "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", 483 | "requires": { 484 | "lru-cache": "^6.0.0" 485 | } 486 | }, 487 | "shallow-clone": { 488 | "version": "3.0.1", 489 | "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", 490 | "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", 491 | "requires": { 492 | "kind-of": "^6.0.2" 493 | } 494 | }, 495 | "socketcluster-server": { 496 | "version": "19.0.1", 497 | "resolved": "https://registry.npmjs.org/socketcluster-server/-/socketcluster-server-19.0.1.tgz", 498 | "integrity": "sha512-IfMZxTkzvqOUExqiVxkxE2DjN/tap6WpbplatptsHKa58wfN6SdfcYCYeR3hlUBOx+cG09/hPxlN+R5tYZF0Mg==", 499 | "requires": { 500 | "ag-auth": "^2.0.0", 501 | "ag-request": "^1.0.1", 502 | "ag-simple-broker": "^6.0.0", 503 | "async-stream-emitter": "^7.0.1", 504 | "base64id": "^2.0.0", 505 | "clone-deep": "^4.0.1", 506 | "sc-errors": "^2.0.3", 507 | "sc-formatter": "^4.0.0", 508 | "stream-demux": "^10.0.1", 509 | "writable-consumable-stream": "^4.1.0", 510 | "ws": "^8.9.0" 511 | } 512 | }, 513 | "stream-demux": { 514 | "version": "10.0.1", 515 | "resolved": "https://registry.npmjs.org/stream-demux/-/stream-demux-10.0.1.tgz", 516 | "integrity": "sha512-QjTYLJWpZxZ6uL5R1JzgOzjvao8zDx78ec+uOjHNeVc/9TuasYLldoVrYARZeT1xI1hFYuiKf13IM8b4wamhHg==", 517 | "requires": { 518 | "consumable-stream": "^3.0.0", 519 | "writable-consumable-stream": "^4.1.0" 520 | }, 521 | "dependencies": { 522 | "consumable-stream": { 523 | "version": "3.0.0", 524 | "resolved": "https://registry.npmjs.org/consumable-stream/-/consumable-stream-3.0.0.tgz", 525 | "integrity": "sha512-CnnsJ9OG9ouxAjt3pc63/DaerezRo/WudqU71pc5epaIUi7NHu2T4v+3f0nKbbCY7icS/TfQ1Satr9rwZ7Jwsg==" 526 | } 527 | } 528 | }, 529 | "writable-consumable-stream": { 530 | "version": "4.1.0", 531 | "resolved": "https://registry.npmjs.org/writable-consumable-stream/-/writable-consumable-stream-4.1.0.tgz", 532 | "integrity": "sha512-4cjCPd4Ayfbix0qqPCzMbnPPZKRh/cKeNCj05unybP3/sRkRAOxh7rSwbhxs3YB6G4/Z2p/2FRBEIQcTeB4jyw==", 533 | "requires": { 534 | "consumable-stream": "^3.0.0" 535 | }, 536 | "dependencies": { 537 | "consumable-stream": { 538 | "version": "3.0.0", 539 | "resolved": "https://registry.npmjs.org/consumable-stream/-/consumable-stream-3.0.0.tgz", 540 | "integrity": "sha512-CnnsJ9OG9ouxAjt3pc63/DaerezRo/WudqU71pc5epaIUi7NHu2T4v+3f0nKbbCY7icS/TfQ1Satr9rwZ7Jwsg==" 541 | } 542 | } 543 | }, 544 | "ws": { 545 | "version": "8.13.0", 546 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", 547 | "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", 548 | "requires": {} 549 | }, 550 | "yallist": { 551 | "version": "4.0.0", 552 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 553 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 554 | } 555 | } 556 | } 557 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scc-state", 3 | "version": "9.2.0", 4 | "description": "State tracking and notification engine for SCC.", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/SocketCluster/scc-state.git" 12 | }, 13 | "keywords": [ 14 | "cluster", 15 | "state", 16 | "distributed", 17 | "engine" 18 | ], 19 | "author": "Jonathan Gros-Dubois", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/SocketCluster/scc-state/issues" 23 | }, 24 | "homepage": "https://github.com/SocketCluster/scc-state", 25 | "dependencies": { 26 | "socketcluster-server": "^19.0.1", 27 | "eetase": "^7.0.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const socketClusterServer = require('socketcluster-server'); 3 | const Action = require('socketcluster-server/action'); 4 | const url = require('url'); 5 | const semverRegex = /\d+\.\d+\.\d+/; 6 | const eetase = require('eetase'); 7 | const packageVersion = require(`./package.json`).version; 8 | const requiredMajorSemver = getMajorSemver(packageVersion); 9 | 10 | const DEFAULT_PORT = 7777; 11 | const DEFAULT_CLUSTER_SCALE_OUT_DELAY = 5000; 12 | const DEFAULT_CLUSTER_SCALE_BACK_DELAY = 1000; 13 | const DEFAULT_CLUSTER_STARTUP_DELAY = 5000; 14 | 15 | const PORT = Number(process.env.SCC_STATE_SERVER_PORT) || DEFAULT_PORT; 16 | const SCC_AUTH_KEY = process.env.SCC_AUTH_KEY || null; 17 | const FORWARDED_FOR_HEADER = process.env.FORWARDED_FOR_HEADER || null; 18 | const RETRY_DELAY = Number(process.env.SCC_STATE_SERVER_RETRY_DELAY) || 2000; 19 | const CLUSTER_SCALE_OUT_DELAY = selectNumericArgument([process.env.SCC_STATE_SERVER_SCALE_OUT_DELAY, DEFAULT_CLUSTER_SCALE_OUT_DELAY]); 20 | const CLUSTER_SCALE_BACK_DELAY = selectNumericArgument([process.env.SCC_STATE_SERVER_SCALE_BACK_DELAY, DEFAULT_CLUSTER_SCALE_BACK_DELAY]); 21 | const STARTUP_DELAY = selectNumericArgument([process.env.SCC_STATE_SERVER_STARTUP_DELAY, DEFAULT_CLUSTER_STARTUP_DELAY]); 22 | 23 | function selectNumericArgument(args) { 24 | let lastIndex = args.length - 1; 25 | for (let i = 0; i < lastIndex; i++) { 26 | let current = Number(args[i]); 27 | if (!isNaN(current) && args[i] != null) { 28 | return current; 29 | } 30 | } 31 | return Number(args[lastIndex]); 32 | }; 33 | 34 | /** 35 | * Log levels: 36 | * 3 - log everything 37 | * 2 - warnings and errors 38 | * 1 - errors only 39 | * 0 - log nothing 40 | */ 41 | let LOG_LEVEL; 42 | if (typeof process.env.SCC_STATE_LOG_LEVEL !== 'undefined') { 43 | LOG_LEVEL = Number(process.env.SCC_STATE_LOG_LEVEL); 44 | } else { 45 | LOG_LEVEL = 3; 46 | } 47 | 48 | let httpServer = eetase(http.createServer()); 49 | let agServer = socketClusterServer.attach(httpServer); 50 | 51 | (async () => { 52 | for await (let [req, res] of httpServer.listener('request')) { 53 | if (req.url === '/health-check') { 54 | res.writeHead(200, {'Content-Type': 'text/html'}); 55 | res.end('OK'); 56 | } else { 57 | res.writeHead(404, {'Content-Type': 'text/html'}); 58 | res.end('Not found'); 59 | } 60 | } 61 | })(); 62 | 63 | let sccBrokerSockets = {}; 64 | let sccWorkerSockets = {}; 65 | let serverReady = STARTUP_DELAY > 0 ? false : true; 66 | if (!serverReady) { 67 | logInfo(`Waiting ${STARTUP_DELAY}ms for initial scc-broker instances before allowing scc-worker instances to join`); 68 | setTimeout(function() { 69 | logInfo('State server is now allowing scc-worker instances to join the cluster'); 70 | serverReady = true; 71 | }, STARTUP_DELAY); 72 | } 73 | 74 | let getInstanceURI = function (socket) { 75 | let targetProtocol = socket.instanceSecure ? 'wss' : 'ws'; 76 | let instanceIp; 77 | if (socket.instanceIpFamily === 'IPv4') { 78 | instanceIp = socket.instanceIp; 79 | } else { 80 | instanceIp = `[${socket.instanceIp}]`; 81 | } 82 | return `${targetProtocol}://${instanceIp}:${socket.instancePort}`; 83 | }; 84 | 85 | let getSCCWorkerClusterState = function (time) { 86 | let sccWorkerURILookup = {}; 87 | Object.keys(sccWorkerSockets).forEach((socketId) => { 88 | let socket = sccWorkerSockets[socketId]; 89 | let instanceURI = getInstanceURI(socket); 90 | sccWorkerURILookup[instanceURI] = true; 91 | }); 92 | return { 93 | sccWorkerURIs: Object.keys(sccWorkerURILookup), 94 | time: time == null ? Date.now() : time 95 | }; 96 | }; 97 | 98 | let getSCCBrokerClusterState = function (time) { 99 | let sccBrokerURILookup = {}; 100 | Object.keys(sccBrokerSockets).forEach((socketId) => { 101 | let socket = sccBrokerSockets[socketId]; 102 | let instanceURI = getInstanceURI(socket); 103 | sccBrokerURILookup[instanceURI] = true; 104 | }); 105 | return { 106 | sccBrokerURIs: Object.keys(sccBrokerURILookup), 107 | time: time == null ? Date.now() : time 108 | }; 109 | }; 110 | 111 | let getSCCClusterState = function () { 112 | let time = Date.now(); 113 | let workerClusterState = getSCCWorkerClusterState(time); 114 | let brokerClusterState = getSCCBrokerClusterState(time); 115 | return { 116 | sccWorkerURIs: workerClusterState.sccWorkerURIs, 117 | sccBrokerURIs: brokerClusterState.sccBrokerURIs, 118 | time 119 | }; 120 | }; 121 | 122 | let brokerClusterResizeTimeout; 123 | 124 | let setBrokerClusterScaleTimeout = function (callback, delay) { 125 | // Only the latest scale request counts. 126 | if (brokerClusterResizeTimeout) { 127 | clearTimeout(brokerClusterResizeTimeout); 128 | } 129 | brokerClusterResizeTimeout = setTimeout(callback, delay); 130 | }; 131 | 132 | let workerClusterResizeTimeout; 133 | 134 | let setWorkerClusterScaleTimeout = function (callback, delay) { 135 | // Only the latest scale request counts. 136 | if (workerClusterResizeTimeout) { 137 | clearTimeout(workerClusterResizeTimeout); 138 | } 139 | workerClusterResizeTimeout = setTimeout(callback, delay); 140 | }; 141 | 142 | let sccBrokerLeaveCluster = function (socket, req) { 143 | delete sccBrokerSockets[socket.id]; 144 | setBrokerClusterScaleTimeout(() => { 145 | invokeRPCOnAllInstances(sccWorkerSockets, 'sccBrokerLeaveCluster', getSCCBrokerClusterState()); 146 | }, CLUSTER_SCALE_BACK_DELAY); 147 | 148 | if (req) { 149 | req.end(); 150 | } 151 | logInfo(`The scc-broker instance ${socket.instanceId} at address ${socket.instanceIp} on port ${socket.instancePort} left the cluster on socket ${socket.id}`); 152 | }; 153 | 154 | let sccWorkerLeaveCluster = function (socket, req) { 155 | delete sccWorkerSockets[socket.id]; 156 | setWorkerClusterScaleTimeout(() => { 157 | invokeRPCOnAllInstances(sccWorkerSockets, 'sccWorkerLeaveCluster', getSCCWorkerClusterState()); 158 | }, CLUSTER_SCALE_BACK_DELAY); 159 | 160 | if (req) { 161 | req.end(); 162 | } 163 | logInfo(`The scc-worker instance ${socket.instanceId} at address ${socket.instanceIp} left the cluster on socket ${socket.id}`); 164 | }; 165 | 166 | let invokeRPCOnInstance = async function (socket, procedureName, data) { 167 | if (data) { 168 | data.sccSourceWorkerURI = getInstanceURI(socket); 169 | } 170 | try { 171 | await socket.invoke(procedureName, data); 172 | } catch (err) { 173 | logError(err); 174 | if (socket.state === 'open') { 175 | setTimeout(invokeRPCOnInstance.bind(null, socket, procedureName, data), RETRY_DELAY); 176 | } 177 | } 178 | }; 179 | 180 | let invokeRPCOnAllInstances = function (instances, procedureName, data) { 181 | Object.keys(instances).forEach((socketId) => { 182 | let socket = instances[socketId]; 183 | invokeRPCOnInstance(socket, procedureName, data); 184 | }); 185 | }; 186 | 187 | let getRemoteIp = function (socket, data) { 188 | let forwardedAddress = FORWARDED_FOR_HEADER ? (socket.request.headers[FORWARDED_FOR_HEADER] || '').split(',')[0] : null; 189 | return data.instanceIp || forwardedAddress || socket.remoteAddress; 190 | }; 191 | 192 | (async () => { 193 | for await (let {error} of agServer.listener('error')) { 194 | logError(error); 195 | } 196 | })(); 197 | 198 | (async () => { 199 | for await (let {warning} of agServer.listener('warning')) { 200 | logWarning(warning); 201 | } 202 | })(); 203 | 204 | agServer.setMiddleware(agServer.MIDDLEWARE_HANDSHAKE, async (middlewareStream) => { 205 | for await (let action of middlewareStream) { 206 | if (action.type === Action.HANDSHAKE_WS) { 207 | if (SCC_AUTH_KEY) { 208 | let urlParts = url.parse(action.request.url, true); 209 | if (!urlParts.query || urlParts.query.authKey !== SCC_AUTH_KEY) { 210 | let err = new Error('Cannot connect to the scc-state instance without providing a valid authKey as a URL query argument.'); 211 | err.name = 'BadClusterAuthError'; 212 | action.block(err); 213 | 214 | continue; 215 | } 216 | } 217 | } 218 | 219 | if (action.type === Action.HANDSHAKE_SC) { 220 | let remoteAddress = action.socket.remoteAddress; 221 | let urlParts = url.parse(action.socket.request.url, true); 222 | let { version, instanceType, instancePort } = urlParts.query; 223 | 224 | action.socket.instanceType = instanceType; 225 | action.socket.instancePort = instancePort; 226 | 227 | let reportedMajorSemver = getMajorSemver(version); 228 | let sccComponentIsObsolete = (!instanceType || Number.isNaN(reportedMajorSemver)); 229 | 230 | if (reportedMajorSemver !== requiredMajorSemver) { 231 | let err; 232 | if (sccComponentIsObsolete) { 233 | err = new Error(`An obsolete SCC component at address ${remoteAddress} is incompatible with the scc-state@^${packageVersion}. Please, update the SCC component up to version ^${requiredMajorSemver}.0.0`); 234 | } else if (reportedMajorSemver > requiredMajorSemver) { 235 | err = new Error(`The scc-state@${packageVersion} is incompatible with the ${instanceType}@${version}. Please, update the scc-state up to version ^${reportedMajorSemver}.0.0`); 236 | } else { 237 | err = new Error(`The ${instanceType}@${version} at address ${remoteAddress}:${instancePort} is incompatible with the scc-state@^${packageVersion}. Please, update the ${instanceType} up to version ^${requiredMajorSemver}.0.0`); 238 | } 239 | err.name = 'CompatibilityError'; 240 | action.block(err); 241 | 242 | continue; 243 | } 244 | } 245 | 246 | action.allow(); 247 | } 248 | }); 249 | 250 | (async () => { 251 | for await (let {socket} of agServer.listener('connection')) { 252 | 253 | (async () => { 254 | for await (let req of socket.procedure('sccBrokerJoinCluster')) { 255 | let data = req.data || {}; 256 | socket.instanceId = data.instanceId; 257 | socket.instanceIp = getRemoteIp(socket, data); 258 | // Only set instanceIpFamily if data.instanceIp is provided. 259 | if (data.instanceIp) { 260 | socket.instanceIpFamily = data.instanceIpFamily; 261 | } 262 | socket.instanceSecure = data.instanceSecure; 263 | sccBrokerSockets[socket.id] = socket; 264 | 265 | setBrokerClusterScaleTimeout(() => { 266 | invokeRPCOnAllInstances(sccWorkerSockets, 'sccBrokerJoinCluster', getSCCBrokerClusterState()); 267 | }, CLUSTER_SCALE_OUT_DELAY); 268 | 269 | req.end(); 270 | logInfo(`The scc-broker instance ${data.instanceId} at address ${socket.instanceIp} on port ${socket.instancePort} joined the cluster on socket ${socket.id}`); 271 | } 272 | })(); 273 | 274 | (async () => { 275 | for await (let req of socket.procedure('sccBrokerLeaveCluster')) { 276 | sccBrokerLeaveCluster(socket, req); 277 | } 278 | })(); 279 | 280 | (async () => { 281 | for await (let req of socket.procedure('sccWorkerJoinCluster')) { 282 | let socketId = socket.id; 283 | let data = req.data || {}; 284 | socket.instanceId = data.instanceId; 285 | socket.instanceIp = getRemoteIp(socket, data); 286 | // Only set instanceIpFamily if data.instanceIp is provided. 287 | if (data.instanceIp) { 288 | socket.instanceIpFamily = data.instanceIpFamily; 289 | } 290 | 291 | if (!serverReady) { 292 | logWarning(`The scc-worker instance ${data.instanceId} at address ${socket.instanceIp} on socket ${socketId} was not allowed to join the cluster because the server is waiting for initial brokers`); 293 | req.error( 294 | new Error('The server is waiting for initial broker connections') 295 | ); 296 | continue; 297 | } 298 | 299 | sccWorkerSockets[socketId] = socket; 300 | 301 | setWorkerClusterScaleTimeout(() => { 302 | let workerClusterState = getSCCWorkerClusterState(); 303 | Object.keys(sccWorkerSockets).forEach((targetSocketId) => { 304 | if (targetSocketId === socketId) { 305 | return; 306 | } 307 | let targetSocket = sccWorkerSockets[targetSocketId]; 308 | invokeRPCOnInstance(targetSocket, 'sccWorkerJoinCluster', workerClusterState); 309 | }); 310 | }, CLUSTER_SCALE_OUT_DELAY); 311 | 312 | let clusterState = getSCCClusterState(); 313 | clusterState.sccSourceWorkerURI = getInstanceURI(socket); 314 | req.end(clusterState); 315 | logInfo(`The scc-worker instance ${data.instanceId} at address ${socket.instanceIp} joined the cluster on socket ${socketId}`); 316 | } 317 | })(); 318 | 319 | (async () => { 320 | for await (let req of socket.procedure('sccWorkerLeaveCluster')) { 321 | sccWorkerLeaveCluster(socket, req); 322 | } 323 | })(); 324 | 325 | (async () => { 326 | for await (let event of socket.listener('disconnect')) { 327 | if (socket.instanceType === 'scc-broker') { 328 | sccBrokerLeaveCluster(socket); 329 | } else if (socket.instanceType === 'scc-worker') { 330 | sccWorkerLeaveCluster(socket); 331 | } 332 | } 333 | })(); 334 | 335 | } 336 | })(); 337 | 338 | (async () => { 339 | await httpServer.listener('listening').once(); 340 | logInfo(`The scc-state instance is listening on port ${PORT}`); 341 | })(); 342 | 343 | httpServer.listen(PORT); 344 | 345 | function logError(err) { 346 | if (LOG_LEVEL > 0) { 347 | console.error(err); 348 | } 349 | } 350 | 351 | function logWarning(warn) { 352 | if (LOG_LEVEL >= 2) { 353 | console.warn(warn); 354 | } 355 | } 356 | 357 | function logInfo(info) { 358 | if (LOG_LEVEL >= 3) { 359 | console.info(info); 360 | } 361 | } 362 | 363 | function getMajorSemver(semver) { 364 | let semverIsValid = typeof semver === 'string' && semver.match(semverRegex); 365 | 366 | if (semverIsValid) { 367 | let majorSemver = semver.split('.')[0]; 368 | return parseInt(majorSemver); 369 | } else { 370 | return NaN; 371 | } 372 | } 373 | --------------------------------------------------------------------------------