diff --git a/package-lock.json b/package-lock.json index d9fdb53074..51d6f13e05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "devDependencies": { "@types/mocha": "^9.0.0", "expect.js": "0.3.1", - "mocha": "^3.5.3", + "mocha": "^10.0.0", "nyc": "^15.1.0", "prettier": "^2.3.2", "rimraf": "^3.0.2", @@ -530,6 +530,12 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, "node_modules/accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -582,6 +588,15 @@ "node": ">=8" } }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -630,6 +645,19 @@ "node": ">=4" } }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/append-transform": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", @@ -722,6 +750,15 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/blob": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", @@ -751,9 +788,9 @@ } }, "node_modules/browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, "node_modules/caching-transform": { @@ -811,6 +848,33 @@ "node": ">=4" } }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -858,18 +922,6 @@ "node": ">= 0.8" } }, - "node_modules/commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "dependencies": { - "graceful-readlink": ">= 1.0.0" - }, - "engines": { - "node": ">= 0.6.x" - } - }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -955,9 +1007,9 @@ } }, "node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { "ms": "2.1.2" }, @@ -1023,9 +1075,9 @@ } }, "node_modules/diff": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", - "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true, "engines": { "node": ">=0.3.1" @@ -1158,6 +1210,15 @@ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -1357,6 +1418,15 @@ "node": ">=8" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, "node_modules/foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -1420,6 +1490,20 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -1454,9 +1538,9 @@ } }, "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -1520,18 +1604,6 @@ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, - "node_modules/graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true - }, - "node_modules/growl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", - "dev": true - }, "node_modules/hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -1594,9 +1666,9 @@ } }, "node_modules/he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, "bin": { "he": "bin/he" @@ -1684,6 +1756,18 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-core-module": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz", @@ -1963,13 +2047,6 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, - "node_modules/json3": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", - "deprecated": "Please use the native JSON object instead of JSON 3", - "dev": true - }, "node_modules/json5": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", @@ -2018,80 +2095,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "node_modules/lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "dev": true, - "dependencies": { - "lodash._basecopy": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "node_modules/lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "node_modules/lodash._basecreate": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", - "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", - "dev": true - }, - "node_modules/lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "node_modules/lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "node_modules/lodash.create": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", - "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", - "dev": true, - "dependencies": { - "lodash._baseassign": "^3.0.0", - "lodash._basecreate": "^3.0.0", - "lodash._isiterateecall": "^3.0.0" - } - }, "node_modules/lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "node_modules/lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "node_modules/lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "dependencies": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -2363,9 +2372,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "node_modules/minimist-options": { @@ -2382,104 +2391,284 @@ "node": ">= 6" } }, - "node_modules/mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "node_modules/mocha": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz", + "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", + "dev": true, + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "minimist": "0.0.8" + "color-convert": "^2.0.1" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/mkdirp/node_modules/minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/mocha": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", - "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", - "dev": true, - "dependencies": { - "browser-stdout": "1.3.0", - "commander": "2.9.0", - "debug": "2.6.8", - "diff": "3.2.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.1", - "growl": "1.9.2", - "he": "1.1.1", - "json3": "3.3.2", - "lodash.create": "3.1.1", - "mkdirp": "0.5.1", - "supports-color": "3.1.2" + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/mocha/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/mocha/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">= 0.10.x", - "npm": ">= 1.4.x" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mocha/node_modules/debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "node_modules/mocha/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { - "ms": "2.0.0" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/mocha/node_modules/glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", - "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.2", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "p-locate": "^5.0.0" }, "engines": { - "node": "*" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mocha/node_modules/has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, "node_modules/mocha/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mocha/node_modules/supports-color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", - "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "dependencies": { - "has-flag": "^1.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/mocha/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" } }, "node_modules/ms": { @@ -2487,6 +2676,18 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -2562,6 +2763,15 @@ "node": ">=10" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -2868,6 +3078,15 @@ "node": ">=8" } }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -2950,6 +3169,18 @@ "node": ">= 6" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -3069,6 +3300,15 @@ "node": ">=10" } }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -3375,6 +3615,18 @@ "node": ">=8" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/superagent": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", @@ -3682,6 +3934,12 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -3808,20 +4066,68 @@ "yargs-parser": "^18.1.2" }, "engines": { - "node": ">=8" + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, "engines": { - "node": ">=6" + "node": ">=8" } }, "node_modules/yeast": { @@ -3838,6 +4144,18 @@ "engines": { "node": ">=6" } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } }, "dependencies": { @@ -4294,6 +4612,12 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -4331,6 +4655,12 @@ "indent-string": "^4.0.0" } }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -4363,6 +4693,16 @@ "color-convert": "^1.9.0" } }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, "append-transform": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", @@ -4440,6 +4780,12 @@ "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, "blob": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", @@ -4466,9 +4812,9 @@ } }, "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, "caching-transform": { @@ -4511,6 +4857,22 @@ "supports-color": "^5.3.0" } }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -4552,15 +4914,6 @@ "delayed-stream": "~1.0.0" } }, - "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "requires": { - "graceful-readlink": ">= 1.0.0" - } - }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -4637,9 +4990,9 @@ } }, "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { "ms": "2.1.2" } @@ -4684,9 +5037,9 @@ "dev": true }, "diff": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", - "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, "dir-glob": { @@ -4780,6 +5133,12 @@ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -4929,6 +5288,12 @@ "path-exists": "^4.0.0" } }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, "foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -4968,6 +5333,13 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -4993,9 +5365,9 @@ "dev": true }, "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -5041,18 +5413,6 @@ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true - }, - "growl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", - "dev": true - }, "hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -5100,9 +5460,9 @@ } }, "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, "hosted-git-info": { @@ -5172,6 +5532,15 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, "is-core-module": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz", @@ -5385,12 +5754,6 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, - "json3": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", - "dev": true - }, "json5": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", @@ -5427,80 +5790,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "dev": true, - "requires": { - "lodash._basecopy": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basecreate": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", - "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "lodash.create": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", - "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", - "dev": true, - "requires": { - "lodash._baseassign": "^3.0.0", - "lodash._basecreate": "^3.0.0", - "lodash._isiterateecall": "^3.0.0" - } - }, "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -5691,9 +5986,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "minimist-options": { @@ -5707,86 +6002,205 @@ "kind-of": "^6.0.3" } }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - } - } - }, "mocha": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", - "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", - "dev": true, - "requires": { - "browser-stdout": "1.3.0", - "commander": "2.9.0", - "debug": "2.6.8", - "diff": "3.2.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.1", - "growl": "1.9.2", - "he": "1.1.1", - "json3": "3.3.2", - "lodash.create": "3.1.1", - "mkdirp": "0.5.1", - "supports-color": "3.1.2" + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz", + "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" }, "dependencies": { - "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "ms": "2.0.0" + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" } }, - "glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", - "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.2", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" } }, "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, "supports-color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", - "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true } } }, @@ -5795,6 +6209,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -5851,6 +6271,12 @@ } } }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, "nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -6064,6 +6490,15 @@ "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "dev": true }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, "read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -6130,6 +6565,15 @@ "util-deprecate": "^1.0.1" } }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, "redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -6207,6 +6651,15 @@ "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -6468,6 +6921,12 @@ "min-indent": "^1.0.0" } }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, "superagent": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", @@ -6690,6 +7149,12 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -6799,6 +7264,38 @@ "decamelize": "^1.2.0" } }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + } + } + }, "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", @@ -6810,6 +7307,12 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/package.json b/package.json index 5932497dda..db50f631da 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "compile": "rimraf ./dist && tsc", "test": "npm run format:check && npm run compile && npm run test:types && npm run test:unit", "test:types": "tsd", - "test:unit": "nyc mocha --require ts-node/register --reporter spec --slow 200 --bail --timeout 10000 test/socket.io.ts", + "test:unit": "nyc mocha --require ts-node/register --reporter spec --slow 200 --bail --timeout 10000 test/index.ts", "format:check": "prettier --check \"lib/**/*.ts\" \"test/**/*.ts\"", "format:fix": "prettier --write \"lib/**/*.ts\" \"test/**/*.ts\"", "prepack": "npm run compile" @@ -56,7 +56,7 @@ "devDependencies": { "@types/mocha": "^9.0.0", "expect.js": "0.3.1", - "mocha": "^3.5.3", + "mocha": "^10.0.0", "nyc": "^15.1.0", "prettier": "^2.3.2", "rimraf": "^3.0.2", diff --git a/test/close.ts b/test/close.ts new file mode 100644 index 0000000000..a48368f9d1 --- /dev/null +++ b/test/close.ts @@ -0,0 +1,181 @@ +import { createServer } from "http"; +import { io as ioc } from "socket.io-client"; +import { join } from "path"; +import { exec } from "child_process"; +import { Server } from ".."; +import expect from "expect.js"; +import { createClient, getPort } from "./support/util"; +import request from "supertest"; + +// TODO: update superagent as latest release now supports promises +const eioHandshake = (httpServer): Promise => { + return new Promise((resolve) => { + request(httpServer) + .get("/socket.io/") + .query({ transport: "polling", EIO: 4 }) + .end((err, res) => { + const sid = JSON.parse(res.text.substring(1)).sid; + resolve(sid); + }); + }); +}; + +const eioPush = (httpServer, sid: string, body: string): Promise => { + return new Promise((resolve) => { + request(httpServer) + .post("/socket.io/") + .send(body) + .query({ transport: "polling", EIO: 4, sid }) + .expect(200) + .end(() => { + resolve(); + }); + }); +}; + +const eioPoll = (httpServer, sid): Promise => { + return new Promise((resolve) => { + request(httpServer) + .get("/socket.io/") + .query({ transport: "polling", EIO: 4, sid }) + .expect(200) + .end((err, res) => { + resolve(res.text); + }); + }); +}; + +describe("close", () => { + it("should be able to close sio sending a srv", (done) => { + const httpServer = createServer().listen(0); + const io = new Server(httpServer); + const port = getPort(io); + const net = require("net"); + const server = net.createServer(); + + const clientSocket = createClient(io, "/", { reconnection: false }); + + clientSocket.on("disconnect", () => { + expect(io.sockets.sockets.size).to.equal(0); + server.listen(port); + }); + + clientSocket.on("connect", () => { + expect(io.sockets.sockets.size).to.equal(1); + io.close(); + }); + + server.once("listening", () => { + // PORT should be free + server.close((error) => { + expect(error).to.be(undefined); + done(); + }); + }); + }); + + it("should be able to close sio sending a srv", (done) => { + const io = new Server(0); + const port = getPort(io); + const net = require("net"); + const server = net.createServer(); + + const clientSocket = ioc("ws://0.0.0.0:" + port, { + reconnection: false, + }); + + clientSocket.on("disconnect", () => { + expect(io.sockets.sockets.size).to.equal(0); + server.listen(port); + }); + + clientSocket.on("connect", () => { + expect(io.sockets.sockets.size).to.equal(1); + io.close(); + }); + + server.once("listening", () => { + // PORT should be free + server.close((error) => { + expect(error).to.be(undefined); + done(); + }); + }); + }); + + describe("graceful close", () => { + function fixture(filename) { + return ( + '"' + + process.execPath + + '" "' + + join(__dirname, "fixtures", filename) + + '"' + ); + } + + it("should stop socket and timers", (done) => { + exec(fixture("server-close.ts"), done); + }); + }); + + describe("protocol violations", () => { + it("should close the connection when receiving several CONNECT packets", async () => { + const httpServer = createServer(); + const io = new Server(httpServer); + + httpServer.listen(0); + + const sid = await eioHandshake(httpServer); + // send a first CONNECT packet + await eioPush(httpServer, sid, "40"); + // send another CONNECT packet + await eioPush(httpServer, sid, "40"); + // session is cleanly closed (not discarded, see 'client.close()') + // first, we receive the Socket.IO handshake response + await eioPoll(httpServer, sid); + // then a close packet + const body = await eioPoll(httpServer, sid); + expect(body).to.be("6\u001e1"); + + io.close(); + }); + + it("should close the connection when receiving an EVENT packet while not connected", async () => { + const httpServer = createServer(); + const io = new Server(httpServer); + + httpServer.listen(0); + + const sid = await eioHandshake(httpServer); + // send an EVENT packet + await eioPush(httpServer, sid, '42["some event"]'); + // session is cleanly closed, we receive a close packet + const body = await eioPoll(httpServer, sid); + expect(body).to.be("6\u001e1"); + + io.close(); + }); + + it("should close the connection when receiving an invalid packet", async () => { + const httpServer = createServer(); + const io = new Server(httpServer); + + httpServer.listen(0); + + const sid = await eioHandshake(httpServer); + // send a CONNECT packet + await eioPush(httpServer, sid, "40"); + // send an invalid packet + await eioPush(httpServer, sid, "4abc"); + // session is cleanly closed (not discarded, see 'client.close()') + // first, we receive the Socket.IO handshake response + await eioPoll(httpServer, sid); + // then a close packet + const body = await eioPoll(httpServer, sid); + expect(body).to.be("6\u001e1"); + + io.close(); + }); + }); +}); diff --git a/test/handshake.ts b/test/handshake.ts new file mode 100644 index 0000000000..27553eca01 --- /dev/null +++ b/test/handshake.ts @@ -0,0 +1,87 @@ +import { Server } from ".."; +import expect from "expect.js"; +import { getPort, success } from "./support/util"; + +describe("handshake", () => { + const request = require("superagent"); + + it("should send the Access-Control-Allow-xxx headers on OPTIONS request", (done) => { + const io = new Server(0, { + cors: { + origin: "http://localhost:54023", + methods: ["GET", "POST"], + allowedHeaders: ["content-type"], + credentials: true, + }, + }); + request + .options(`http://localhost:${getPort(io)}/socket.io/default/`) + .query({ transport: "polling", EIO: 4 }) + .set("Origin", "http://localhost:54023") + .end((err, res) => { + expect(res.status).to.be(204); + + expect(res.headers["access-control-allow-origin"]).to.be( + "http://localhost:54023" + ); + expect(res.headers["access-control-allow-methods"]).to.be("GET,POST"); + expect(res.headers["access-control-allow-headers"]).to.be( + "content-type" + ); + expect(res.headers["access-control-allow-credentials"]).to.be("true"); + success(done, io); + }); + }); + + it("should send the Access-Control-Allow-xxx headers on GET request", (done) => { + const io = new Server(0, { + cors: { + origin: "http://localhost:54024", + methods: ["GET", "POST"], + allowedHeaders: ["content-type"], + credentials: true, + }, + }); + request + .get(`http://localhost:${getPort(io)}/socket.io/default/`) + .query({ transport: "polling", EIO: 4 }) + .set("Origin", "http://localhost:54024") + .end((err, res) => { + expect(res.status).to.be(200); + + expect(res.headers["access-control-allow-origin"]).to.be( + "http://localhost:54024" + ); + expect(res.headers["access-control-allow-credentials"]).to.be("true"); + success(done, io); + }); + }); + + it("should allow request if custom function in opts.allowRequest returns true", (done) => { + const io = new Server(0, { + allowRequest: (req, callback) => callback(null, true), + }); + + request + .get(`http://localhost:${getPort(io)}/socket.io/default/`) + .query({ transport: "polling", EIO: 4 }) + .end((err, res) => { + expect(res.status).to.be(200); + success(done, io); + }); + }); + + it("should disallow request if custom function in opts.allowRequest returns false", (done) => { + const io = new Server(0, { + allowRequest: (req, callback) => callback(null, false), + }); + request + .get(`http://localhost:${getPort(io)}/socket.io/default/`) + .set("origin", "http://foo.example") + .query({ transport: "polling", EIO: 4 }) + .end((err, res) => { + expect(res.status).to.be(403); + success(done, io); + }); + }); +}); diff --git a/test/index.ts b/test/index.ts new file mode 100644 index 0000000000..880b81e52d --- /dev/null +++ b/test/index.ts @@ -0,0 +1,23 @@ +"use strict"; + +import expect from "expect.js"; + +describe("socket.io", () => { + it("should be the same version as client", () => { + const version = require("../package").version; + expect(version).to.be(require("socket.io-client/package.json").version); + }); + + require("./server-attachment"); + require("./handshake"); + require("./close"); + require("./namespaces"); + require("./socket"); + require("./messaging-many"); + require("./middleware"); + require("./socket-middleware"); + require("./v2-compatibility"); + require("./socket-timeout"); + require("./uws"); + require("./utility-methods"); +}); diff --git a/test/messaging-many.ts b/test/messaging-many.ts new file mode 100644 index 0000000000..6d12dea0aa --- /dev/null +++ b/test/messaging-many.ts @@ -0,0 +1,501 @@ +import { Server } from ".."; +import expect from "expect.js"; +import { + createClient, + createPartialDone, + success, + successFn, + waitFor, +} from "./support/util"; + +describe("messaging many", () => { + it("emits to a namespace", (done) => { + const io = new Server(0); + const socket1 = createClient(io, "/", { multiplex: false }); + const socket2 = createClient(io, "/", { multiplex: false }); + const socket3 = createClient(io, "/test"); + + const partialDone = createPartialDone( + 2, + successFn(done, io, socket1, socket2, socket3) + ); + + socket1.on("a", (a) => { + expect(a).to.be("b"); + partialDone(); + }); + socket2.on("a", (a) => { + expect(a).to.be("b"); + partialDone(); + }); + socket3.on("a", () => { + done(new Error("not")); + }); + + let sockets = 3; + io.on("connection", () => { + --sockets || emit(); + }); + io.of("/test", () => { + --sockets || emit(); + }); + + function emit() { + io.emit("a", "b"); + } + }); + + it("emits binary data to a namespace", (done) => { + const io = new Server(0); + const socket1 = createClient(io, "/", { multiplex: false }); + const socket2 = createClient(io, "/", { multiplex: false }); + const socket3 = createClient(io, "/test"); + + const partialDone = createPartialDone( + 2, + successFn(done, io, socket1, socket2, socket3) + ); + + socket1.on("bin", (a) => { + expect(Buffer.isBuffer(a)).to.be(true); + partialDone(); + }); + socket2.on("bin", (a) => { + expect(Buffer.isBuffer(a)).to.be(true); + partialDone(); + }); + socket3.on("bin", () => { + done(new Error("not")); + }); + + let sockets = 3; + io.on("connection", () => { + --sockets || emit(); + }); + io.of("/test", () => { + --sockets || emit(); + }); + + function emit() { + io.emit("bin", Buffer.alloc(10)); + } + }); + + it("emits to the rest", (done) => { + const io = new Server(0); + const socket1 = createClient(io, "/", { multiplex: false }); + const socket2 = createClient(io, "/", { multiplex: false }); + const socket3 = createClient(io, "/test"); + + socket1.on("a", (a) => { + expect(a).to.be("b"); + socket1.emit("finish"); + }); + socket2.emit("broadcast"); + socket2.on("a", () => { + done(new Error("done")); + }); + socket3.on("a", () => { + done(new Error("not")); + }); + + io.on("connection", (socket) => { + socket.on("broadcast", () => { + socket.broadcast.emit("a", "b"); + }); + socket.on("finish", () => { + success(done, io, socket1, socket2, socket3); + }); + }); + }); + + it("emits to rooms", (done) => { + const io = new Server(0); + const socket1 = createClient(io, "/", { multiplex: false }); + const socket2 = createClient(io, "/", { multiplex: false }); + + socket2.on("a", () => { + done(new Error("not")); + }); + socket1.on("a", () => { + success(done, io, socket1, socket2); + }); + socket1.emit("join", "woot"); + socket1.emit("emit", "woot"); + + io.on("connection", (socket) => { + socket.on("join", (room, fn) => { + socket.join(room); + fn && fn(); + }); + + socket.on("emit", (room) => { + io.in(room).emit("a"); + }); + }); + }); + + it("emits to rooms avoiding dupes", (done) => { + const io = new Server(0); + const socket1 = createClient(io, "/", { multiplex: false }); + const socket2 = createClient(io, "/", { multiplex: false }); + + const partialDone = createPartialDone( + 2, + successFn(done, io, socket1, socket2) + ); + + socket2.on("a", () => { + done(new Error("not")); + }); + socket1.on("a", partialDone); + socket2.on("b", partialDone); + + socket1.emit("join", "woot"); + socket1.emit("join", "test"); + socket2.emit("join", "third", () => { + socket2.emit("emit"); + }); + + io.on("connection", (socket) => { + socket.on("join", (room, fn) => { + socket.join(room); + fn && fn(); + }); + + socket.on("emit", () => { + io.in("woot").in("test").emit("a"); + io.in("third").emit("b"); + }); + }); + }); + + it("broadcasts to rooms", (done) => { + const io = new Server(0); + const socket1 = createClient(io, "/", { multiplex: false }); + const socket2 = createClient(io, "/", { multiplex: false }); + const socket3 = createClient(io, "/", { multiplex: false }); + + const partialDone = createPartialDone( + 2, + successFn(done, io, socket1, socket2, socket3) + ); + + socket1.emit("join", "woot"); + socket2.emit("join", "test"); + socket3.emit("join", "test", () => { + socket3.emit("broadcast"); + }); + + socket1.on("a", () => { + done(new Error("not")); + }); + socket2.on("a", () => { + partialDone(); + }); + socket3.on("a", () => { + done(new Error("not")); + }); + socket3.on("b", () => { + partialDone(); + }); + + io.on("connection", (socket) => { + socket.on("join", (room, fn) => { + socket.join(room); + fn && fn(); + }); + + socket.on("broadcast", () => { + socket.broadcast.to("test").emit("a"); + socket.emit("b"); + }); + }); + }); + + it("broadcasts binary data to rooms", (done) => { + const io = new Server(0); + const socket1 = createClient(io, "/", { multiplex: false }); + const socket2 = createClient(io, "/", { multiplex: false }); + const socket3 = createClient(io, "/", { multiplex: false }); + + const partialDone = createPartialDone( + 2, + successFn(done, io, socket1, socket2, socket3) + ); + + socket1.emit("join", "woot"); + socket2.emit("join", "test"); + socket3.emit("join", "test", () => { + socket3.emit("broadcast"); + }); + + socket1.on("bin", (data) => { + throw new Error("got bin in socket1"); + }); + socket2.on("bin", (data) => { + expect(Buffer.isBuffer(data)).to.be(true); + partialDone(); + }); + socket2.on("bin2", (data) => { + throw new Error("socket2 got bin2"); + }); + socket3.on("bin", (data) => { + throw new Error("socket3 got bin"); + }); + socket3.on("bin2", (data) => { + expect(Buffer.isBuffer(data)).to.be(true); + partialDone(); + }); + + io.on("connection", (socket) => { + socket.on("join", (room, fn) => { + socket.join(room); + fn && fn(); + }); + socket.on("broadcast", () => { + socket.broadcast.to("test").emit("bin", Buffer.alloc(5)); + socket.emit("bin2", Buffer.alloc(5)); + }); + }); + }); + + it("keeps track of rooms", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + s.join("a"); + expect(s.rooms).to.contain(s.id, "a"); + s.join("b"); + expect(s.rooms).to.contain(s.id, "a", "b"); + s.join("c"); + expect(s.rooms).to.contain(s.id, "a", "b", "c"); + s.leave("b"); + expect(s.rooms).to.contain(s.id, "a", "c"); + (s as any).leaveAll(); + expect(s.rooms.size).to.eql(0); + + success(done, io, socket); + }); + }); + + it("deletes empty rooms", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + s.join("a"); + expect(s.nsp.adapter.rooms).to.contain("a"); + s.leave("a"); + expect(s.nsp.adapter.rooms).to.not.contain("a"); + + success(done, io, socket); + }); + }); + + it("should properly cleanup left rooms", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + s.join("a"); + expect(s.rooms).to.contain(s.id, "a"); + s.join("b"); + expect(s.rooms).to.contain(s.id, "a", "b"); + s.leave("unknown"); + expect(s.rooms).to.contain(s.id, "a", "b"); + (s as any).leaveAll(); + expect(s.rooms.size).to.eql(0); + + success(done, io, socket); + }); + }); + + it("allows to join several rooms at once", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + s.join(["a", "b", "c"]); + expect(s.rooms).to.contain(s.id, "a", "b", "c"); + success(done, io, socket); + }); + }); + + it("should exclude specific sockets when broadcasting", (done) => { + const io = new Server(0); + const socket1 = createClient(io, "/", { multiplex: false }); + const socket2 = createClient(io, "/", { multiplex: false }); + const socket3 = createClient(io, "/", { multiplex: false }); + + socket2.on("a", () => { + done(new Error("not")); + }); + socket3.on("a", () => { + done(new Error("not")); + }); + socket1.on("a", successFn(done, io, socket1, socket2, socket3)); + + io.on("connection", (socket) => { + socket.on("exclude", (id) => { + socket.broadcast.except(id).emit("a"); + }); + }); + + socket2.on("connect", () => { + socket3.emit("exclude", socket2.id); + }); + }); + + it("should exclude a specific room when broadcasting", (done) => { + const io = new Server(0); + const socket1 = createClient(io, "/", { multiplex: false }); + const socket2 = createClient(io, "/", { multiplex: false }); + const socket3 = createClient(io, "/", { multiplex: false }); + + socket2.on("a", () => { + done(new Error("not")); + }); + socket3.on("a", () => { + done(new Error("not")); + }); + socket1.on("a", successFn(done, io, socket1, socket2, socket3)); + + io.on("connection", (socket) => { + socket.on("join", (room, cb) => { + socket.join(room); + cb(); + }); + socket.on("broadcast", () => { + socket.broadcast.except("room1").emit("a"); + }); + }); + + socket2.emit("join", "room1", () => { + socket3.emit("broadcast"); + }); + }); + + it("should return an immutable broadcast operator", (done) => { + const io = new Server(0); + const clientSocket = createClient(io); + + io.on("connection", (socket) => { + const operator = socket.local + .compress(false) + .to(["room1", "room2"]) + .except("room3"); + operator.compress(true).emit("hello"); + operator.volatile.emit("hello"); + operator.to("room4").emit("hello"); + operator.except("room5").emit("hello"); + socket.emit("hello"); + socket.to("room6").emit("hello"); + // @ts-ignore + expect(operator.rooms).to.contain("room1", "room2"); + // @ts-ignore + expect(operator.rooms).to.not.contain("room4", "room5", "room6"); + // @ts-ignore + expect(operator.exceptRooms).to.contain("room3"); + // @ts-ignore + expect(operator.flags).to.eql({ local: true, compress: false }); + + success(done, io, clientSocket); + }); + }); + + it("should broadcast and expect multiple acknowledgements", (done) => { + const io = new Server(0); + const socket1 = createClient(io, "/", { multiplex: false }); + const socket2 = createClient(io, "/", { multiplex: false }); + const socket3 = createClient(io, "/", { multiplex: false }); + + socket1.on("some event", (cb) => { + cb(1); + }); + + socket2.on("some event", (cb) => { + cb(2); + }); + + socket3.on("some event", (cb) => { + cb(3); + }); + + Promise.all([ + waitFor(socket1, "connect"), + waitFor(socket2, "connect"), + waitFor(socket3, "connect"), + ]).then(() => { + io.timeout(2000).emit("some event", (err, responses) => { + expect(err).to.be(null); + expect(responses).to.have.length(3); + expect(responses).to.contain(1, 2, 3); + + success(done, io, socket1, socket2, socket3); + }); + }); + }); + + it("should fail when a client does not acknowledge the event in the given delay", (done) => { + const io = new Server(0); + const socket1 = createClient(io, "/", { multiplex: false }); + const socket2 = createClient(io, "/", { multiplex: false }); + const socket3 = createClient(io, "/", { multiplex: false }); + + socket1.on("some event", (cb) => { + cb(1); + }); + + socket2.on("some event", (cb) => { + cb(2); + }); + + socket3.on("some event", () => { + // timeout + }); + + Promise.all([ + waitFor(socket1, "connect"), + waitFor(socket2, "connect"), + waitFor(socket3, "connect"), + ]).then(() => { + io.timeout(200).emit("some event", (err, responses) => { + expect(err).to.be.an(Error); + expect(responses).to.have.length(2); + expect(responses).to.contain(1, 2); + + success(done, io, socket1, socket2, socket3); + }); + }); + }); + + it("should broadcast and return if the packet is sent to 0 client", (done) => { + const io = new Server(0); + const socket1 = createClient(io, "/", { multiplex: false }); + const socket2 = createClient(io, "/", { multiplex: false }); + const socket3 = createClient(io, "/", { multiplex: false }); + + socket1.on("some event", () => { + done(new Error("should not happen")); + }); + + socket2.on("some event", () => { + done(new Error("should not happen")); + }); + + socket3.on("some event", () => { + done(new Error("should not happen")); + }); + + io.to("room123") + .timeout(200) + .emit("some event", (err, responses) => { + expect(err).to.be(null); + expect(responses).to.have.length(0); + + success(done, io, socket1, socket2, socket3); + }); + }); +}); diff --git a/test/middleware.ts b/test/middleware.ts new file mode 100644 index 0000000000..46455a73e1 --- /dev/null +++ b/test/middleware.ts @@ -0,0 +1,210 @@ +import { Server, Socket } from ".."; +import expect from "expect.js"; +import { + success, + createClient, + successFn, + createPartialDone, +} from "./support/util"; + +describe("middleware", () => { + it("should call functions", (done) => { + const io = new Server(0); + + let run = 0; + io.use((socket, next) => { + expect(socket).to.be.a(Socket); + run++; + next(); + }); + io.use((socket, next) => { + expect(socket).to.be.a(Socket); + run++; + next(); + }); + + const socket = createClient(io); + socket.on("connect", () => { + expect(run).to.be(2); + + success(done, io, socket); + }); + }); + + it("should pass errors", (done) => { + const io = new Server(0); + + io.use((socket, next) => { + next(new Error("Authentication error")); + }); + io.use((socket, next) => { + done(new Error("nope")); + }); + + const socket = createClient(io); + socket.on("connect", () => { + done(new Error("nope")); + }); + socket.on("connect_error", (err) => { + expect(err.message).to.be("Authentication error"); + + success(done, io, socket); + }); + }); + + it("should pass an object", (done) => { + const io = new Server(0); + + io.use((socket, next) => { + const err = new Error("Authentication error"); + // @ts-ignore + err.data = { a: "b", c: 3 }; + next(err); + }); + + const socket = createClient(io); + socket.on("connect", () => { + done(new Error("nope")); + }); + socket.on("connect_error", (err) => { + expect(err).to.be.an(Error); + expect(err.message).to.eql("Authentication error"); + // @ts-ignore + expect(err.data).to.eql({ a: "b", c: 3 }); + + success(done, io, socket); + }); + }); + + it("should only call connection after fns", (done) => { + const io = new Server(0); + + io.use((socket: any, next) => { + socket.name = "guillermo"; + next(); + }); + + const clientSocket = createClient(io); + io.on("connection", (socket) => { + expect((socket as any).name).to.be("guillermo"); + + success(done, io, clientSocket); + }); + }); + + it("should only call connection after (lengthy) fns", (done) => { + const io = new Server(0); + + let authenticated = false; + + io.use((socket, next) => { + setTimeout(() => { + authenticated = true; + next(); + }, 300); + }); + + const socket = createClient(io); + socket.on("connect", () => { + expect(authenticated).to.be(true); + + success(done, io, socket); + }); + }); + + it("should be ignored if socket gets closed", (done) => { + const io = new Server(0); + + let socket; + io.use((s, next) => { + socket.io.engine.close(); + s.client.conn.on("close", () => { + process.nextTick(next); + setTimeout(() => { + success(done, io, socket); + }, 50); + }); + }); + + socket = createClient(io); + io.on("connection", (socket) => { + done(new Error("should not fire")); + }); + }); + + it("should call functions in expected order", (done) => { + const io = new Server(0); + + const result: number[] = []; + + io.use(() => { + done(new Error("should not fire")); + }); + io.of("/chat").use((socket, next) => { + result.push(1); + setTimeout(next, 50); + }); + io.of("/chat").use((socket, next) => { + result.push(2); + setTimeout(next, 50); + }); + io.of("/chat").use((socket, next) => { + result.push(3); + setTimeout(next, 50); + }); + + const chat = createClient(io, "/chat"); + chat.on("connect", () => { + expect(result).to.eql([1, 2, 3]); + + success(done, io, chat); + }); + }); + + it("should disable the merge of handshake packets", (done) => { + const io = new Server(0); + + io.use((socket, next) => { + next(); + }); + + const socket = createClient(io); + socket.on("connect", successFn(done, io, socket)); + }); + + it("should work with a custom namespace", (done) => { + const io = new Server(0); + const socket1 = createClient(io, "/"); + const socket2 = createClient(io, "/chat"); + + const partialDone = createPartialDone( + 2, + successFn(done, io, socket1, socket2) + ); + + io.of("/chat").use((socket, next) => { + next(); + }); + + socket1.on("connect", partialDone); + socket2.on("connect", partialDone); + }); + + it("should only set `connected` to true after the middleware execution", (done) => { + const io = new Server(0); + const clientSocket = createClient(io, "/"); + + io.use((socket, next) => { + expect(socket.connected).to.be(false); + expect(socket.disconnected).to.be(true); + next(); + }); + + io.on("connection", (socket) => { + expect(socket.connected).to.be(true); + expect(socket.disconnected).to.be(false); + + success(done, io, clientSocket); + }); + }); +}); diff --git a/test/namespaces.ts b/test/namespaces.ts new file mode 100644 index 0000000000..7557e4a57b --- /dev/null +++ b/test/namespaces.ts @@ -0,0 +1,575 @@ +import type { SocketId } from "socket.io-adapter"; +import { Server, Namespace, Socket } from ".."; +import expect from "expect.js"; +import { + success, + createClient, + successFn, + createPartialDone, +} from "./support/util"; + +describe("namespaces", () => { + it("should be accessible through .sockets", () => { + const io = new Server(); + expect(io.sockets).to.be.a(Namespace); + }); + + it("should be aliased", () => { + const io = new Server(); + expect(io.use).to.be.a("function"); + expect(io.to).to.be.a("function"); + expect(io["in"]).to.be.a("function"); + expect(io.emit).to.be.a("function"); + expect(io.send).to.be.a("function"); + expect(io.write).to.be.a("function"); + expect(io.allSockets).to.be.a("function"); + expect(io.compress).to.be.a("function"); + }); + + it("should return an immutable broadcast operator", () => { + const io = new Server(); + const operator = io.local.to(["room1", "room2"]).except("room3"); + operator.compress(true).emit("hello"); + operator.volatile.emit("hello"); + operator.to("room4").emit("hello"); + operator.except("room5").emit("hello"); + io.to("room6").emit("hello"); + // @ts-ignore + expect(operator.rooms).to.contain("room1", "room2"); + // @ts-ignore + expect(operator.exceptRooms).to.contain("room3"); + // @ts-ignore + expect(operator.flags).to.eql({ local: true }); + }); + + it("should automatically connect", (done) => { + const io = new Server(0); + const socket = createClient(io); + socket.on("connect", successFn(done, io, socket)); + }); + + it("should fire a `connection` event", (done) => { + const io = new Server(0); + const clientSocket = createClient(io); + + io.on("connection", (socket) => { + expect(socket).to.be.a(Socket); + success(done, io, clientSocket); + }); + }); + + it("should fire a `connect` event", (done) => { + const io = new Server(0); + const clientSocket = createClient(io); + + io.on("connect", (socket) => { + expect(socket).to.be.a(Socket); + success(done, io, clientSocket); + }); + }); + + it("should work with many sockets", (done) => { + const io = new Server(0); + io.of("/chat"); + io.of("/news"); + const chat = createClient(io, "/chat"); + const news = createClient(io, "/news"); + + let total = 2; + chat.on("connect", () => { + --total || success(done, io, chat, news); + }); + news.on("connect", () => { + --total || success(done, io, chat, news); + }); + }); + + it('should be able to equivalently start with "" or "/" on server', (done) => { + const io = new Server(0); + const c1 = createClient(io, "/"); + const c2 = createClient(io, "/abc"); + + let total = 2; + io.of("").on("connection", () => { + --total || success(done, io, c1, c2); + }); + io.of("abc").on("connection", () => { + --total || success(done, io, c1, c2); + }); + }); + + it('should be equivalent for "" and "/" on client', (done) => { + const io = new Server(0); + const c1 = createClient(io, ""); + + io.of("/").on("connection", successFn(done, io, c1)); + }); + + it("should work with `of` and many sockets", (done) => { + const io = new Server(0); + const chat = createClient(io, "/chat"); + const news = createClient(io, "/news"); + + let total = 2; + io.of("/news").on("connection", (socket) => { + expect(socket).to.be.a(Socket); + --total || success(done, io, chat, news); + }); + io.of("/news").on("connection", (socket) => { + expect(socket).to.be.a(Socket); + --total || success(done, io, chat, news); + }); + }); + + it("should work with `of` second param", (done) => { + const io = new Server(0); + const chat = createClient(io, "/chat"); + const news = createClient(io, "/news"); + + let total = 2; + io.of("/news", (socket) => { + expect(socket).to.be.a(Socket); + --total || success(done, io, chat, news); + }); + io.of("/news", (socket) => { + expect(socket).to.be.a(Socket); + --total || success(done, io, chat, news); + }); + }); + + it("should disconnect upon transport disconnection", (done) => { + const io = new Server(0); + const chat = createClient(io, "/chat"); + const news = createClient(io, "/news"); + + let total = 2; + let totald = 2; + let s; + io.of("/news", (socket) => { + socket.on("disconnect", (reason) => { + --totald || success(done, io, chat, news); + }); + --total || close(); + }); + io.of("/chat", (socket) => { + s = socket; + socket.on("disconnect", (reason) => { + --totald || success(done, io, chat, news); + }); + --total || close(); + }); + function close() { + s.disconnect(true); + } + }); + + it("should fire a `disconnecting` event just before leaving all rooms", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + s.join("a"); + // FIXME not sure why process.nextTick() is needed here + process.nextTick(() => s.disconnect()); + + let total = 2; + s.on("disconnecting", (reason) => { + expect(s.rooms).to.contain(s.id, "a"); + total--; + }); + + s.on("disconnect", (reason) => { + expect(s.rooms.size).to.eql(0); + --total || success(done, io, socket); + }); + }); + }); + + it("should return error connecting to non-existent namespace", (done) => { + const io = new Server(0); + const socket = createClient(io, "/doesnotexist"); + + socket.on("connect_error", (err) => { + expect(err.message).to.be("Invalid namespace"); + success(done, io); + }); + }); + + it("should not reuse same-namespace connections", (done) => { + const io = new Server(0); + const clientSocket1 = createClient(io); + const clientSocket2 = createClient(io); + + let connections = 0; + io.on("connection", () => { + connections++; + if (connections === 2) { + success(done, io, clientSocket1, clientSocket2); + } + }); + }); + + it("should find all clients in a namespace", (done) => { + const io = new Server(0); + const chatSids: string[] = []; + let otherSid: SocketId | null = null; + + const c1 = createClient(io, "/chat"); + const c2 = createClient(io, "/chat", { forceNew: true }); + const c3 = createClient(io, "/other", { forceNew: true }); + + let total = 3; + io.of("/chat").on("connection", (socket) => { + chatSids.push(socket.id); + --total || getSockets(); + }); + io.of("/other").on("connection", (socket) => { + otherSid = socket.id; + --total || getSockets(); + }); + + async function getSockets() { + const sids = await io.of("/chat").allSockets(); + + expect(sids).to.contain(chatSids[0], chatSids[1]); + expect(sids).to.not.contain(otherSid); + success(done, io, c1, c2, c3); + } + }); + + it("should find all clients in a namespace room", (done) => { + const io = new Server(0); + let chatFooSid: SocketId | null = null; + let chatBarSid: SocketId | null = null; + let otherSid: SocketId | null = null; + + const c1 = createClient(io, "/chat"); + const c2 = createClient(io, "/chat", { forceNew: true }); + const c3 = createClient(io, "/other", { forceNew: true }); + + let chatIndex = 0; + let total = 3; + io.of("/chat").on("connection", (socket) => { + if (chatIndex++) { + socket.join("foo"); + chatFooSid = socket.id; + --total || getSockets(); + } else { + socket.join("bar"); + chatBarSid = socket.id; + --total || getSockets(); + } + }); + io.of("/other").on("connection", (socket) => { + socket.join("foo"); + otherSid = socket.id; + --total || getSockets(); + }); + + async function getSockets() { + const sids = await io.of("/chat").in("foo").allSockets(); + + expect(sids).to.contain(chatFooSid); + expect(sids).to.not.contain(chatBarSid); + expect(sids).to.not.contain(otherSid); + success(done, io, c1, c2, c3); + } + }); + + it("should find all clients across namespace rooms", (done) => { + const io = new Server(0); + let chatFooSid: SocketId | null = null; + let chatBarSid: SocketId | null = null; + let otherSid: SocketId | null = null; + + const c1 = createClient(io, "/chat"); + const c2 = createClient(io, "/chat", { forceNew: true }); + const c3 = createClient(io, "/other", { forceNew: true }); + + let chatIndex = 0; + let total = 3; + io.of("/chat").on("connection", (socket) => { + if (chatIndex++) { + socket.join("foo"); + chatFooSid = socket.id; + --total || getSockets(); + } else { + socket.join("bar"); + chatBarSid = socket.id; + --total || getSockets(); + } + }); + io.of("/other").on("connection", (socket) => { + socket.join("foo"); + otherSid = socket.id; + --total || getSockets(); + }); + + async function getSockets() { + const sids = await io.of("/chat").allSockets(); + expect(sids).to.contain(chatFooSid, chatBarSid); + expect(sids).to.not.contain(otherSid); + success(done, io, c1, c2, c3); + } + }); + + it("should not emit volatile event after regular event", (done) => { + const io = new Server(0); + + let counter = 0; + io.of("/chat").on("connection", (s) => { + // Wait to make sure there are no packets being sent for opening the connection + setTimeout(() => { + io.of("/chat").emit("ev", "data"); + io.of("/chat").volatile.emit("ev", "data"); + }, 50); + }); + + const socket = createClient(io, "/chat"); + socket.on("ev", () => { + counter++; + }); + + setTimeout(() => { + expect(counter).to.be(1); + success(done, io, socket); + }, 500); + }); + + it("should emit volatile event", (done) => { + const io = new Server(0); + + let counter = 0; + io.of("/chat").on("connection", (s) => { + // Wait to make sure there are no packets being sent for opening the connection + setTimeout(() => { + io.of("/chat").volatile.emit("ev", "data"); + }, 100); + }); + + const socket = createClient(io, "/chat"); + socket.on("ev", () => { + counter++; + }); + + setTimeout(() => { + expect(counter).to.be(1); + success(done, io, socket); + }, 500); + }); + + it("should enable compression by default", (done) => { + const io = new Server(0); + const socket = createClient(io, "/chat"); + + io.of("/chat").on("connection", (s) => { + s.conn.once("packetCreate", (packet) => { + expect(packet.options.compress).to.be(true); + success(done, io, socket); + }); + io.of("/chat").emit("woot", "hi"); + }); + }); + + it("should disable compression", (done) => { + const io = new Server(0); + const socket = createClient(io, "/chat"); + + io.of("/chat").on("connection", (s) => { + s.conn.once("packetCreate", (packet) => { + expect(packet.options.compress).to.be(false); + success(done, io, socket); + }); + io.of("/chat").compress(false).emit("woot", "hi"); + }); + }); + + it("should throw on reserved event", () => { + const io = new Server(); + + expect(() => io.emit("connect")).to.throwException( + /"connect" is a reserved event name/ + ); + }); + + it("should close a client without namespace", (done) => { + const io = new Server(0, { + connectTimeout: 10, + }); + + const socket = createClient(io); + + // @ts-ignore + socket.io.engine.write = () => {}; // prevent the client from sending a CONNECT packet + + socket.on("disconnect", successFn(done, io, socket)); + }); + + it("should exclude a specific socket when emitting", (done) => { + const io = new Server(0); + + const socket1 = createClient(io, "/"); + const socket2 = createClient(io, "/"); + + socket2.on("a", () => { + done(new Error("should not happen")); + }); + socket1.on("a", successFn(done, io, socket1, socket2)); + + socket2.on("connect", () => { + io.except(socket2.id).emit("a"); + }); + }); + + it("should exclude a specific socket when emitting (in a namespace)", (done) => { + const io = new Server(0); + + const nsp = io.of("/nsp"); + + const socket1 = createClient(io, "/nsp"); + const socket2 = createClient(io, "/nsp"); + + socket2.on("a", () => { + done(new Error("not")); + }); + socket1.on("a", successFn(done, io, socket1, socket2)); + + socket2.on("connect", () => { + nsp.except(socket2.id).emit("a"); + }); + }); + + it("should exclude a specific room when emitting", (done) => { + const io = new Server(0); + + const nsp = io.of("/nsp"); + + const socket1 = createClient(io, "/nsp"); + const socket2 = createClient(io, "/nsp"); + + socket1.on("a", successFn(done, io, socket1, socket2)); + socket2.on("a", () => { + done(new Error("not")); + }); + + nsp.on("connection", (socket) => { + socket.on("broadcast", () => { + socket.join("room1"); + nsp.except("room1").emit("a"); + }); + }); + + socket2.emit("broadcast"); + }); + + it("should emit an 'new_namespace' event", (done) => { + const io = new Server(); + + io.on("new_namespace", (namespace) => { + expect(namespace.name).to.eql("/nsp"); + done(); + }); + + io.of("/nsp"); + }); + + describe("dynamic namespaces", () => { + it("should allow connections to dynamic namespaces with a regex", (done) => { + const io = new Server(0); + const socket = createClient(io, "/dynamic-101"); + const partialDone = createPartialDone(4, successFn(done, io, socket)); + + let dynamicNsp = io + .of(/^\/dynamic-\d+$/) + .on("connect", (socket) => { + expect(socket.nsp.name).to.be("/dynamic-101"); + dynamicNsp.emit("hello", 1, "2", { 3: "4" }); + partialDone(); + }) + .use((socket, next) => { + next(); + partialDone(); + }); + socket.on("connect_error", (err) => { + expect().fail(); + }); + socket.on("connect", () => { + partialDone(); + }); + socket.on("hello", (a, b, c) => { + expect(a).to.eql(1); + expect(b).to.eql("2"); + expect(c).to.eql({ 3: "4" }); + partialDone(); + }); + }); + + it("should allow connections to dynamic namespaces with a function", (done) => { + const io = new Server(0); + const socket = createClient(io, "/dynamic-101"); + + io.of((name, query, next) => next(null, "/dynamic-101" === name)); + socket.on("connect", successFn(done, io, socket)); + }); + + it("should disallow connections when no dynamic namespace matches", (done) => { + const io = new Server(0); + const socket = createClient(io, "/abc"); + io.of(/^\/dynamic-\d+$/); + io.of((name, query, next) => next(null, "/dynamic-101" === name)); + + socket.on("connect_error", (err) => { + expect(err.message).to.be("Invalid namespace"); + success(done, io, socket); + }); + }); + + it("should emit an 'new_namespace' event for a dynamic namespace", (done) => { + const io = new Server(0); + io.of(/^\/dynamic-\d+$/); + const socket = createClient(io, "/dynamic-101"); + + io.on("new_namespace", (namespace) => { + expect(namespace.name).to.be("/dynamic-101"); + + success(done, io, socket); + }); + }); + + it("should handle race conditions with dynamic namespaces (#4136)", (done) => { + const io = new Server(0); + const counters = { + connected: 0, + created: 0, + events: 0, + }; + const buffer: Function[] = []; + io.on("new_namespace", (namespace) => { + counters.created++; + }); + + const handler = () => { + if (++counters.events === 2) { + expect(counters.created).to.equal(1); + success(done, io, one, two); + } + }; + + io.of((name, query, next) => { + buffer.push(next); + if (buffer.length === 2) { + buffer.forEach((next) => next(null, true)); + } + }).on("connection", (socket) => { + if (++counters.connected === 2) { + io.of("/dynamic-101").emit("message"); + } + }); + + let one = createClient(io, "/dynamic-101"); + let two = createClient(io, "/dynamic-101"); + one.on("message", handler); + two.on("message", handler); + }); + }); +}); diff --git a/test/server-attachment.ts b/test/server-attachment.ts new file mode 100644 index 0000000000..a10d4c7300 --- /dev/null +++ b/test/server-attachment.ts @@ -0,0 +1,170 @@ +import { Server } from ".."; +import { createServer } from "http"; +import request from "supertest"; +import expect from "expect.js"; +import { getPort, successFn } from "./support/util"; + +describe("server attachment", () => { + describe("http.Server", () => { + const clientVersion = require("socket.io-client/package.json").version; + + const testSource = (filename) => (done) => { + const srv = createServer(); + new Server(srv); + request(srv) + .get("/socket.io/" + filename) + .buffer(true) + .end((err, res) => { + if (err) return done(err); + expect(res.headers["content-type"]).to.be("application/javascript"); + expect(res.headers.etag).to.be('"' + clientVersion + '"'); + expect(res.headers["x-sourcemap"]).to.be(undefined); + expect(res.text).to.match(/engine\.io/); + expect(res.status).to.be(200); + done(); + }); + }; + + const testSourceMap = (filename) => (done) => { + const srv = createServer(); + new Server(srv); + request(srv) + .get("/socket.io/" + filename) + .buffer(true) + .end((err, res) => { + if (err) return done(err); + expect(res.headers["content-type"]).to.be("application/json"); + expect(res.headers.etag).to.be('"' + clientVersion + '"'); + expect(res.text).to.match(/engine\.io/); + expect(res.status).to.be(200); + done(); + }); + }; + + it("should serve client", testSource("socket.io.js")); + it( + "should serve client with query string", + testSource("socket.io.js?buster=" + Date.now()) + ); + it("should serve source map", testSourceMap("socket.io.js.map")); + it("should serve client (min)", testSource("socket.io.min.js")); + + it("should serve source map (min)", testSourceMap("socket.io.min.js.map")); + + it("should serve client (gzip)", (done) => { + const srv = createServer(); + new Server(srv); + request(srv) + .get("/socket.io/socket.io.js") + .set("accept-encoding", "gzip,br,deflate") + .buffer(true) + .end((err, res) => { + if (err) return done(err); + expect(res.headers["content-encoding"]).to.be("gzip"); + expect(res.status).to.be(200); + done(); + }); + }); + + it( + "should serve bundle with msgpack parser", + testSource("socket.io.msgpack.min.js") + ); + + it( + "should serve source map for bundle with msgpack parser", + testSourceMap("socket.io.msgpack.min.js.map") + ); + + it("should serve the ESM bundle", testSource("socket.io.esm.min.js")); + + it( + "should serve the source map for the ESM bundle", + testSourceMap("socket.io.esm.min.js.map") + ); + + it("should handle 304", (done) => { + const srv = createServer(); + new Server(srv); + request(srv) + .get("/socket.io/socket.io.js") + .set("If-None-Match", '"' + clientVersion + '"') + .end((err, res) => { + if (err) return done(err); + expect(res.statusCode).to.be(304); + done(); + }); + }); + + it("should handle 304", (done) => { + const srv = createServer(); + new Server(srv); + request(srv) + .get("/socket.io/socket.io.js") + .set("If-None-Match", 'W/"' + clientVersion + '"') + .end((err, res) => { + if (err) return done(err); + expect(res.statusCode).to.be(304); + done(); + }); + }); + + it("should not serve static files", (done) => { + const srv = createServer(); + new Server(srv, { serveClient: false }); + request(srv).get("/socket.io/socket.io.js").expect(400, done); + }); + + it("should work with #attach", (done) => { + const srv = createServer((req, res) => { + res.writeHead(404); + res.end(); + }); + const sockets = new Server(); + sockets.attach(srv); + request(srv) + .get("/socket.io/socket.io.js") + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.be(200); + done(); + }); + }); + + it("should work with #attach (and merge options)", () => { + const srv = createServer((req, res) => { + res.writeHead(404); + res.end(); + }); + const server = new Server({ + pingTimeout: 6000, + }); + server.attach(srv, { + pingInterval: 24000, + }); + // @ts-ignore + expect(server.eio.opts.pingTimeout).to.eql(6000); + // @ts-ignore + expect(server.eio.opts.pingInterval).to.eql(24000); + server.close(); + }); + }); + + describe("port", () => { + it("should be bound", (done) => { + const io = new Server(0); + + request(`http://localhost:${getPort(io)}`) + .get("/socket.io/socket.io.js") + .expect(200, successFn(done, io)); + }); + + it("with listen", (done) => { + const io = new Server().listen(0); + + request(`http://localhost:${getPort(io)}`) + .get("/socket.io/socket.io.js") + .expect(200, successFn(done, io)); + }); + }); +}); diff --git a/test/socket-middleware.ts b/test/socket-middleware.ts new file mode 100644 index 0000000000..5fd524e963 --- /dev/null +++ b/test/socket-middleware.ts @@ -0,0 +1,60 @@ +import { Server } from ".."; +import expect from "expect.js"; +import { success, createClient } from "./support/util"; + +describe("socket middleware", () => { + it("should call functions", (done) => { + const io = new Server(0); + const clientSocket = createClient(io, "/", { multiplex: false }); + + clientSocket.emit("join", "woot"); + + let run = 0; + + io.on("connection", (socket) => { + socket.use((event, next) => { + expect(event).to.eql(["join", "woot"]); + event.unshift("wrap"); + run++; + next(); + }); + socket.use((event, next) => { + expect(event).to.eql(["wrap", "join", "woot"]); + run++; + next(); + }); + socket.on("wrap", (data1, data2) => { + expect(data1).to.be("join"); + expect(data2).to.be("woot"); + expect(run).to.be(2); + + success(done, io, clientSocket); + }); + }); + }); + + it("should pass errors", (done) => { + const io = new Server(0); + const clientSocket = createClient(io, "/", { multiplex: false }); + + clientSocket.emit("join", "woot"); + + io.on("connection", (socket) => { + socket.use((event, next) => { + next(new Error("Authentication error")); + }); + socket.use((event, next) => { + done(new Error("should not happen")); + }); + socket.on("join", () => { + done(new Error("should not happen")); + }); + socket.on("error", (err) => { + expect(err).to.be.an(Error); + expect(err.message).to.eql("Authentication error"); + + success(done, io, clientSocket); + }); + }); + }); +}); diff --git a/test/socket.io.ts b/test/socket.io.ts deleted file mode 100644 index 209b51f1c6..0000000000 --- a/test/socket.io.ts +++ /dev/null @@ -1,3210 +0,0 @@ -"use strict"; - -import { Server, Socket, Namespace } from ".."; -import { createServer } from "http"; -import fs = require("fs"); -import { join } from "path"; -import { exec } from "child_process"; -import request from "supertest"; -import expect from "expect.js"; -import type { AddressInfo } from "net"; -import * as io_v2 from "socket.io-client-v2"; -import type { SocketId } from "socket.io-adapter"; -import { io as ioc, Socket as ClientSocket } from "socket.io-client"; -import { createClient } from "./support/util"; - -import "./support/util"; -import "./utility-methods"; -import "./uws"; - -type callback = (err: Error | null, success: boolean) => void; - -// Creates a socket.io client for the given server -function client(srv, nsp?: string | object, opts?: object): ClientSocket { - if ("object" == typeof nsp) { - opts = nsp; - nsp = undefined; - } - let addr = srv.address(); - if (!addr) addr = srv.listen().address(); - const url = "ws://localhost:" + addr.port + (nsp || ""); - return ioc(url, opts); -} - -const success = (sio, clientSocket, done) => { - sio.close(); - clientSocket.close(); - done(); -}; - -const waitFor = (emitter, event) => { - return new Promise((resolve) => { - emitter.once(event, resolve); - }); -}; - -const getPort = (io: Server): number => { - // @ts-ignore - return io.httpServer.address().port; -}; - -// TODO: update superagent as latest release now supports promises -const eioHandshake = (httpServer): Promise => { - return new Promise((resolve) => { - request(httpServer) - .get("/socket.io/") - .query({ transport: "polling", EIO: 4 }) - .end((err, res) => { - const sid = JSON.parse(res.text.substring(1)).sid; - resolve(sid); - }); - }); -}; - -const eioPush = (httpServer, sid: string, body: string): Promise => { - return new Promise((resolve) => { - request(httpServer) - .post("/socket.io/") - .send(body) - .query({ transport: "polling", EIO: 4, sid }) - .expect(200) - .end(() => { - resolve(); - }); - }); -}; - -const eioPoll = (httpServer, sid): Promise => { - return new Promise((resolve) => { - request(httpServer) - .get("/socket.io/") - .query({ transport: "polling", EIO: 4, sid }) - .expect(200) - .end((err, res) => { - resolve(res.text); - }); - }); -}; - -describe("socket.io", () => { - it("should be the same version as client", () => { - const version = require("../package").version; - expect(version).to.be(require("socket.io-client/package.json").version); - }); - - describe("server attachment", () => { - describe("http.Server", () => { - const clientVersion = require("socket.io-client/package.json").version; - - const testSource = (filename) => (done) => { - const srv = createServer(); - new Server(srv); - request(srv) - .get("/socket.io/" + filename) - .buffer(true) - .end((err, res) => { - if (err) return done(err); - expect(res.headers["content-type"]).to.be("application/javascript"); - expect(res.headers.etag).to.be('"' + clientVersion + '"'); - expect(res.headers["x-sourcemap"]).to.be(undefined); - expect(res.text).to.match(/engine\.io/); - expect(res.status).to.be(200); - done(); - }); - }; - - const testSourceMap = (filename) => (done) => { - const srv = createServer(); - new Server(srv); - request(srv) - .get("/socket.io/" + filename) - .buffer(true) - .end((err, res) => { - if (err) return done(err); - expect(res.headers["content-type"]).to.be("application/json"); - expect(res.headers.etag).to.be('"' + clientVersion + '"'); - expect(res.text).to.match(/engine\.io/); - expect(res.status).to.be(200); - done(); - }); - }; - - it("should serve client", testSource("socket.io.js")); - it( - "should serve client with query string", - testSource("socket.io.js?buster=" + Date.now()) - ); - it("should serve source map", testSourceMap("socket.io.js.map")); - it("should serve client (min)", testSource("socket.io.min.js")); - - it( - "should serve source map (min)", - testSourceMap("socket.io.min.js.map") - ); - - it("should serve client (gzip)", (done) => { - const srv = createServer(); - new Server(srv); - request(srv) - .get("/socket.io/socket.io.js") - .set("accept-encoding", "gzip,br,deflate") - .buffer(true) - .end((err, res) => { - if (err) return done(err); - expect(res.headers["content-encoding"]).to.be("gzip"); - expect(res.status).to.be(200); - done(); - }); - }); - - it( - "should serve bundle with msgpack parser", - testSource("socket.io.msgpack.min.js") - ); - - it( - "should serve source map for bundle with msgpack parser", - testSourceMap("socket.io.msgpack.min.js.map") - ); - - it("should serve the ESM bundle", testSource("socket.io.esm.min.js")); - - it( - "should serve the source map for the ESM bundle", - testSourceMap("socket.io.esm.min.js.map") - ); - - it("should handle 304", (done) => { - const srv = createServer(); - new Server(srv); - request(srv) - .get("/socket.io/socket.io.js") - .set("If-None-Match", '"' + clientVersion + '"') - .end((err, res) => { - if (err) return done(err); - expect(res.statusCode).to.be(304); - done(); - }); - }); - - it("should handle 304", (done) => { - const srv = createServer(); - new Server(srv); - request(srv) - .get("/socket.io/socket.io.js") - .set("If-None-Match", 'W/"' + clientVersion + '"') - .end((err, res) => { - if (err) return done(err); - expect(res.statusCode).to.be(304); - done(); - }); - }); - - it("should not serve static files", (done) => { - const srv = createServer(); - new Server(srv, { serveClient: false }); - request(srv).get("/socket.io/socket.io.js").expect(400, done); - }); - - it("should work with #attach", (done) => { - const srv = createServer((req, res) => { - res.writeHead(404); - res.end(); - }); - const sockets = new Server(); - sockets.attach(srv); - request(srv) - .get("/socket.io/socket.io.js") - .end((err, res) => { - if (err) return done(err); - expect(res.status).to.be(200); - done(); - }); - }); - - it("should work with #attach (and merge options)", () => { - const srv = createServer((req, res) => { - res.writeHead(404); - res.end(); - }); - const server = new Server({ - pingTimeout: 6000, - }); - server.attach(srv, { - pingInterval: 24000, - }); - // @ts-ignore - expect(server.eio.opts.pingTimeout).to.eql(6000); - // @ts-ignore - expect(server.eio.opts.pingInterval).to.eql(24000); - server.close(); - }); - }); - - describe("port", () => { - it("should be bound", (done) => { - const io = new Server(0); - - request(`http://localhost:${getPort(io)}`) - .get("/socket.io/socket.io.js") - .expect(200, done); - }); - - it("with listen", (done) => { - const io = new Server().listen(0); - - request(`http://localhost:${getPort(io)}`) - .get("/socket.io/socket.io.js") - .expect(200, done); - }); - }); - }); - - describe("handshake", () => { - const request = require("superagent"); - - it("should send the Access-Control-Allow-xxx headers on OPTIONS request", (done) => { - const io = new Server(0, { - cors: { - origin: "http://localhost:54023", - methods: ["GET", "POST"], - allowedHeaders: ["content-type"], - credentials: true, - }, - }); - request - .options(`http://localhost:${getPort(io)}/socket.io/default/`) - .query({ transport: "polling", EIO: 4 }) - .set("Origin", "http://localhost:54023") - .end((err, res) => { - expect(res.status).to.be(204); - - expect(res.headers["access-control-allow-origin"]).to.be( - "http://localhost:54023" - ); - expect(res.headers["access-control-allow-methods"]).to.be("GET,POST"); - expect(res.headers["access-control-allow-headers"]).to.be( - "content-type" - ); - expect(res.headers["access-control-allow-credentials"]).to.be("true"); - done(); - }); - }); - - it("should send the Access-Control-Allow-xxx headers on GET request", (done) => { - const io = new Server(0, { - cors: { - origin: "http://localhost:54024", - methods: ["GET", "POST"], - allowedHeaders: ["content-type"], - credentials: true, - }, - }); - request - .get(`http://localhost:${getPort(io)}/socket.io/default/`) - .query({ transport: "polling", EIO: 4 }) - .set("Origin", "http://localhost:54024") - .end((err, res) => { - expect(res.status).to.be(200); - - expect(res.headers["access-control-allow-origin"]).to.be( - "http://localhost:54024" - ); - expect(res.headers["access-control-allow-credentials"]).to.be("true"); - done(); - }); - }); - - it("should allow request if custom function in opts.allowRequest returns true", (done) => { - const io = new Server(0, { - allowRequest: (req, callback) => callback(null, true), - }); - - request - .get(`http://localhost:${getPort(io)}/socket.io/default/`) - .query({ transport: "polling", EIO: 4 }) - .end((err, res) => { - expect(res.status).to.be(200); - done(); - }); - }); - - it("should disallow request if custom function in opts.allowRequest returns false", (done) => { - const io = new Server(0, { - allowRequest: (req, callback) => callback(null, false), - }); - request - .get(`http://localhost:${getPort(io)}/socket.io/default/`) - .set("origin", "http://foo.example") - .query({ transport: "polling", EIO: 4 }) - .end((err, res) => { - expect(res.status).to.be(403); - done(); - }); - }); - }); - - describe("close", () => { - it("should be able to close sio sending a srv", (done) => { - const httpServer = createServer().listen(0); - const io = new Server(httpServer); - const port = getPort(io); - const net = require("net"); - const server = net.createServer(); - - const clientSocket = client(httpServer, { reconnection: false }); - - clientSocket.on("disconnect", () => { - expect(io.sockets.sockets.size).to.equal(0); - server.listen(port); - }); - - clientSocket.on("connect", () => { - expect(io.sockets.sockets.size).to.equal(1); - io.close(); - }); - - server.once("listening", () => { - // PORT should be free - server.close((error) => { - expect(error).to.be(undefined); - done(); - }); - }); - }); - - it("should be able to close sio sending a srv", (done) => { - const io = new Server(0); - const port = getPort(io); - const net = require("net"); - const server = net.createServer(); - - const clientSocket = ioc("ws://0.0.0.0:" + port, { - reconnection: false, - }); - - clientSocket.on("disconnect", () => { - expect(io.sockets.sockets.size).to.equal(0); - server.listen(port); - }); - - clientSocket.on("connect", () => { - expect(io.sockets.sockets.size).to.equal(1); - io.close(); - }); - - server.once("listening", () => { - // PORT should be free - server.close((error) => { - expect(error).to.be(undefined); - done(); - }); - }); - }); - - describe("graceful close", () => { - function fixture(filename) { - return ( - '"' + - process.execPath + - '" "' + - join(__dirname, "fixtures", filename) + - '"' - ); - } - - it("should stop socket and timers", (done) => { - exec(fixture("server-close.ts"), done); - }); - }); - - describe("protocol violations", () => { - it("should close the connection when receiving several CONNECT packets", async () => { - const httpServer = createServer(); - const io = new Server(httpServer); - - httpServer.listen(0); - - const sid = await eioHandshake(httpServer); - // send a first CONNECT packet - await eioPush(httpServer, sid, "40"); - // send another CONNECT packet - await eioPush(httpServer, sid, "40"); - // session is cleanly closed (not discarded, see 'client.close()') - // first, we receive the Socket.IO handshake response - await eioPoll(httpServer, sid); - // then a close packet - const body = await eioPoll(httpServer, sid); - expect(body).to.be("6\u001e1"); - - io.close(); - }); - - it("should close the connection when receiving an EVENT packet while not connected", async () => { - const httpServer = createServer(); - const io = new Server(httpServer); - - httpServer.listen(0); - - const sid = await eioHandshake(httpServer); - // send an EVENT packet - await eioPush(httpServer, sid, '42["some event"]'); - // session is cleanly closed, we receive a close packet - const body = await eioPoll(httpServer, sid); - expect(body).to.be("6\u001e1"); - - io.close(); - }); - - it("should close the connection when receiving an invalid packet", async () => { - const httpServer = createServer(); - const io = new Server(httpServer); - - httpServer.listen(0); - - const sid = await eioHandshake(httpServer); - // send a CONNECT packet - await eioPush(httpServer, sid, "40"); - // send an invalid packet - await eioPush(httpServer, sid, "4abc"); - // session is cleanly closed (not discarded, see 'client.close()') - // first, we receive the Socket.IO handshake response - await eioPoll(httpServer, sid); - // then a close packet - const body = await eioPoll(httpServer, sid); - expect(body).to.be("6\u001e1"); - - io.close(); - }); - }); - }); - - describe("namespaces", () => { - it("should be accessible through .sockets", () => { - const sio = new Server(); - expect(sio.sockets).to.be.a(Namespace); - }); - - it("should be aliased", () => { - const sio = new Server(); - expect(sio.use).to.be.a("function"); - expect(sio.to).to.be.a("function"); - expect(sio["in"]).to.be.a("function"); - expect(sio.emit).to.be.a("function"); - expect(sio.send).to.be.a("function"); - expect(sio.write).to.be.a("function"); - expect(sio.allSockets).to.be.a("function"); - expect(sio.compress).to.be.a("function"); - }); - - it("should return an immutable broadcast operator", () => { - const sio = new Server(); - const operator = sio.local.to(["room1", "room2"]).except("room3"); - operator.compress(true).emit("hello"); - operator.volatile.emit("hello"); - operator.to("room4").emit("hello"); - operator.except("room5").emit("hello"); - sio.to("room6").emit("hello"); - // @ts-ignore - expect(operator.rooms).to.contain("room1", "room2"); - // @ts-ignore - expect(operator.exceptRooms).to.contain("room3"); - // @ts-ignore - expect(operator.flags).to.eql({ local: true }); - }); - - it("should automatically connect", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - socket.on("connect", () => { - done(); - }); - }); - }); - - it("should fire a `connection` event", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (socket: Socket) => { - expect(socket).to.be.a(Socket); - done(); - }); - }); - }); - - it("should fire a `connect` event", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connect", (socket) => { - expect(socket).to.be.a(Socket); - done(); - }); - }); - }); - - it("should work with many sockets", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - sio.of("/chat"); - sio.of("/news"); - const chat = client(srv, "/chat"); - const news = client(srv, "/news"); - let total = 2; - chat.on("connect", () => { - --total || done(); - }); - news.on("connect", () => { - --total || done(); - }); - }); - }); - - it('should be able to equivalently start with "" or "/" on server', (done) => { - const srv = createServer(); - const sio = new Server(srv); - let total = 2; - sio.of("").on("connection", () => { - --total || done(); - }); - sio.of("abc").on("connection", () => { - --total || done(); - }); - const c1 = client(srv, "/"); - const c2 = client(srv, "/abc"); - }); - - it('should be equivalent for "" and "/" on client', (done) => { - const srv = createServer(); - const sio = new Server(srv); - sio.of("/").on("connection", () => { - done(); - }); - const c1 = client(srv, ""); - }); - - it("should work with `of` and many sockets", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const chat = client(srv, "/chat"); - const news = client(srv, "/news"); - let total = 2; - sio.of("/news").on("connection", (socket) => { - expect(socket).to.be.a(Socket); - --total || done(); - }); - sio.of("/news").on("connection", (socket) => { - expect(socket).to.be.a(Socket); - --total || done(); - }); - }); - }); - - it("should work with `of` second param", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const chat = client(srv, "/chat"); - const news = client(srv, "/news"); - let total = 2; - sio.of("/news", (socket) => { - expect(socket).to.be.a(Socket); - --total || done(); - }); - sio.of("/news", (socket) => { - expect(socket).to.be.a(Socket); - --total || done(); - }); - }); - }); - - it("should disconnect upon transport disconnection", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const chat = client(srv, "/chat"); - const news = client(srv, "/news"); - let total = 2; - let totald = 2; - let s; - sio.of("/news", (socket) => { - socket.on("disconnect", (reason) => { - --totald || done(); - }); - --total || close(); - }); - sio.of("/chat", (socket) => { - s = socket; - socket.on("disconnect", (reason) => { - --totald || done(); - }); - --total || close(); - }); - function close() { - s.disconnect(true); - } - }); - }); - - it("should fire a `disconnecting` event just before leaving all rooms", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - - sio.on("connection", (s) => { - s.join("a"); - // FIXME not sure why process.nextTick() is needed here - process.nextTick(() => s.disconnect()); - - let total = 2; - s.on("disconnecting", (reason) => { - expect(s.rooms).to.contain(s.id, "a"); - total--; - }); - - s.on("disconnect", (reason) => { - expect(s.rooms.size).to.eql(0); - --total || done(); - }); - }); - }); - }); - - it("should return error connecting to non-existent namespace", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv, "/doesnotexist"); - socket.on("connect_error", (err) => { - expect(err.message).to.be("Invalid namespace"); - done(); - }); - }); - }); - - it("should not reuse same-namespace connections", (done) => { - const srv = createServer(); - const sio = new Server(srv); - let connections = 0; - - srv.listen(() => { - const clientSocket1 = client(srv); - const clientSocket2 = client(srv); - sio.on("connection", () => { - connections++; - if (connections === 2) { - done(); - } - }); - }); - }); - - it("should find all clients in a namespace", (done) => { - const srv = createServer(); - const sio = new Server(srv); - const chatSids: string[] = []; - let otherSid: SocketId | null = null; - srv.listen(() => { - const c1 = client(srv, "/chat"); - const c2 = client(srv, "/chat", { forceNew: true }); - const c3 = client(srv, "/other", { forceNew: true }); - let total = 3; - sio.of("/chat").on("connection", (socket) => { - chatSids.push(socket.id); - --total || getSockets(); - }); - sio.of("/other").on("connection", (socket) => { - otherSid = socket.id; - --total || getSockets(); - }); - }); - async function getSockets() { - const sids = await sio.of("/chat").allSockets(); - - expect(sids).to.contain(chatSids[0], chatSids[1]); - expect(sids).to.not.contain(otherSid); - done(); - } - }); - - it("should find all clients in a namespace room", (done) => { - const srv = createServer(); - const sio = new Server(srv); - let chatFooSid: SocketId | null = null; - let chatBarSid: SocketId | null = null; - let otherSid: SocketId | null = null; - srv.listen(() => { - const c1 = client(srv, "/chat"); - const c2 = client(srv, "/chat", { forceNew: true }); - const c3 = client(srv, "/other", { forceNew: true }); - let chatIndex = 0; - let total = 3; - sio.of("/chat").on("connection", (socket) => { - if (chatIndex++) { - socket.join("foo"); - chatFooSid = socket.id; - --total || getSockets(); - } else { - socket.join("bar"); - chatBarSid = socket.id; - --total || getSockets(); - } - }); - sio.of("/other").on("connection", (socket) => { - socket.join("foo"); - otherSid = socket.id; - --total || getSockets(); - }); - }); - async function getSockets() { - const sids = await sio.of("/chat").in("foo").allSockets(); - - expect(sids).to.contain(chatFooSid); - expect(sids).to.not.contain(chatBarSid); - expect(sids).to.not.contain(otherSid); - done(); - } - }); - - it("should find all clients across namespace rooms", (done) => { - const srv = createServer(); - const sio = new Server(srv); - let chatFooSid: SocketId | null = null; - let chatBarSid: SocketId | null = null; - let otherSid: SocketId | null = null; - srv.listen(() => { - const c1 = client(srv, "/chat"); - const c2 = client(srv, "/chat", { forceNew: true }); - const c3 = client(srv, "/other", { forceNew: true }); - let chatIndex = 0; - let total = 3; - sio.of("/chat").on("connection", (socket) => { - if (chatIndex++) { - socket.join("foo"); - chatFooSid = socket.id; - --total || getSockets(); - } else { - socket.join("bar"); - chatBarSid = socket.id; - --total || getSockets(); - } - }); - sio.of("/other").on("connection", (socket) => { - socket.join("foo"); - otherSid = socket.id; - --total || getSockets(); - }); - }); - async function getSockets() { - const sids = await sio.of("/chat").allSockets(); - expect(sids).to.contain(chatFooSid, chatBarSid); - expect(sids).to.not.contain(otherSid); - done(); - } - }); - - it("should not emit volatile event after regular event", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - let counter = 0; - srv.listen(() => { - sio.of("/chat").on("connection", (s) => { - // Wait to make sure there are no packets being sent for opening the connection - setTimeout(() => { - sio.of("/chat").emit("ev", "data"); - sio.of("/chat").volatile.emit("ev", "data"); - }, 50); - }); - - const socket = client(srv, "/chat"); - socket.on("ev", () => { - counter++; - }); - }); - - setTimeout(() => { - expect(counter).to.be(1); - done(); - }, 500); - }); - - it("should emit volatile event", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - let counter = 0; - srv.listen(() => { - sio.of("/chat").on("connection", (s) => { - // Wait to make sure there are no packets being sent for opening the connection - setTimeout(() => { - sio.of("/chat").volatile.emit("ev", "data"); - }, 100); - }); - - const socket = client(srv, "/chat"); - socket.on("ev", () => { - counter++; - }); - }); - - setTimeout(() => { - expect(counter).to.be(1); - done(); - }, 500); - }); - - it("should enable compression by default", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv, "/chat"); - sio.of("/chat").on("connection", (s) => { - s.conn.once("packetCreate", (packet) => { - expect(packet.options.compress).to.be(true); - done(); - }); - sio.of("/chat").emit("woot", "hi"); - }); - }); - }); - - it("should disable compression", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv, "/chat"); - sio.of("/chat").on("connection", (s) => { - s.conn.once("packetCreate", (packet) => { - expect(packet.options.compress).to.be(false); - done(); - }); - sio.of("/chat").compress(false).emit("woot", "hi"); - }); - }); - }); - - it("should throw on reserved event", () => { - const sio = new Server(); - - expect(() => sio.emit("connect")).to.throwException( - /"connect" is a reserved event name/ - ); - }); - - it("should close a client without namespace", (done) => { - const srv = createServer(); - const sio = new Server(srv, { - connectTimeout: 10, - }); - - srv.listen(() => { - const socket = client(srv); - - // @ts-ignore - socket.io.engine.write = () => {}; // prevent the client from sending a CONNECT packet - - socket.on("disconnect", () => { - socket.close(); - sio.close(); - done(); - }); - }); - }); - - it("should exclude a specific socket when emitting", (done) => { - const srv = createServer(); - const io = new Server(srv); - - srv.listen(() => { - const socket1 = client(srv, "/"); - const socket2 = client(srv, "/"); - - socket2.on("a", () => { - done(new Error("should not happen")); - }); - socket1.on("a", () => { - done(); - }); - - socket2.on("connect", () => { - io.except(socket2.id).emit("a"); - }); - }); - }); - - it("should exclude a specific socket when emitting (in a namespace)", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - const nsp = sio.of("/nsp"); - - srv.listen(() => { - const socket1 = client(srv, "/nsp"); - const socket2 = client(srv, "/nsp"); - - socket2.on("a", () => { - done(new Error("not")); - }); - socket1.on("a", () => { - done(); - }); - - socket2.on("connect", () => { - nsp.except(socket2.id).emit("a"); - }); - }); - }); - - it("should exclude a specific room when emitting", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - const nsp = sio.of("/nsp"); - - srv.listen(() => { - const socket1 = client(srv, "/nsp"); - const socket2 = client(srv, "/nsp"); - - socket1.on("a", () => { - done(); - }); - socket2.on("a", () => { - done(new Error("not")); - }); - - nsp.on("connection", (socket) => { - socket.on("broadcast", () => { - socket.join("room1"); - nsp.except("room1").emit("a"); - }); - }); - - socket2.emit("broadcast"); - }); - }); - - it("should emit an 'new_namespace' event", (done) => { - const sio = new Server(); - - sio.on("new_namespace", (namespace) => { - expect(namespace.name).to.eql("/nsp"); - done(); - }); - - sio.of("/nsp"); - }); - - describe("dynamic namespaces", () => { - it("should allow connections to dynamic namespaces with a regex", (done) => { - const srv = createServer(); - const sio = new Server(srv); - let count = 0; - srv.listen(() => { - const socket = client(srv, "/dynamic-101"); - let dynamicNsp = sio - .of(/^\/dynamic-\d+$/) - .on("connect", (socket) => { - expect(socket.nsp.name).to.be("/dynamic-101"); - dynamicNsp.emit("hello", 1, "2", { 3: "4" }); - if (++count === 4) done(); - }) - .use((socket, next) => { - next(); - if (++count === 4) done(); - }); - socket.on("connect_error", (err) => { - expect().fail(); - }); - socket.on("connect", () => { - if (++count === 4) done(); - }); - socket.on("hello", (a, b, c) => { - expect(a).to.eql(1); - expect(b).to.eql("2"); - expect(c).to.eql({ 3: "4" }); - if (++count === 4) done(); - }); - }); - }); - - it("should allow connections to dynamic namespaces with a function", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv, "/dynamic-101"); - sio.of((name, query, next) => next(null, "/dynamic-101" === name)); - socket.on("connect", done); - }); - }); - - it("should disallow connections when no dynamic namespace matches", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv, "/abc"); - sio.of(/^\/dynamic-\d+$/); - sio.of((name, query, next) => next(null, "/dynamic-101" === name)); - socket.on("connect_error", (err) => { - expect(err.message).to.be("Invalid namespace"); - done(); - }); - }); - }); - - it("should emit an 'new_namespace' event for a dynamic namespace", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - sio.of(/^\/dynamic-\d+$/); - - sio.on("new_namespace", (namespace) => { - expect(namespace.name).to.be("/dynamic-101"); - - socket.disconnect(); - srv.close(); - done(); - }); - - const socket = client(srv, "/dynamic-101"); - }); - }); - - it("should handle race conditions with dynamic namespaces (#4136)", (done) => { - const srv = createServer(); - const sio = new Server(srv); - const counters = { - connected: 0, - created: 0, - events: 0, - }; - const buffer: callback[] = []; - sio.on("new_namespace", (namespace) => { - counters.created++; - }); - srv.listen(() => { - const handler = () => { - if (++counters.events === 2) { - expect(counters.created).to.equal(1); - done(); - } - }; - - sio - .of((name, query, next) => { - buffer.push(next); - if (buffer.length === 2) { - buffer.forEach((next) => next(null, true)); - } - }) - .on("connection", (socket) => { - if (++counters.connected === 2) { - sio.of("/dynamic-101").emit("message"); - } - }); - - let one = client(srv, "/dynamic-101"); - let two = client(srv, "/dynamic-101"); - one.on("message", handler); - two.on("message", handler); - }); - }); - }); - }); - - describe("socket", () => { - it("should not fire events more than once after manually reconnecting", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const clientSocket = client(srv, { reconnection: false }); - clientSocket.on("connect", function init() { - clientSocket.off("connect", init); - clientSocket.io.engine.close(); - - process.nextTick(() => { - clientSocket.connect(); - }); - clientSocket.on("connect", () => { - done(); - }); - }); - }); - }); - - it("should not fire reconnect_failed event more than once when server closed", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const clientSocket = client(srv, { - reconnectionAttempts: 3, - reconnectionDelay: 100, - }); - clientSocket.on("connect", () => { - sio.close(); - }); - - clientSocket.io.on("reconnect_failed", () => { - done(); - }); - }); - }); - - it("should receive events", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - s.on("random", (a, b, c) => { - expect(a).to.be(1); - expect(b).to.be("2"); - expect(c).to.eql([3]); - done(); - }); - socket.emit("random", 1, "2", [3]); - }); - }); - }); - - it("should receive message events through `send`", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - s.on("message", (a) => { - expect(a).to.be(1337); - done(); - }); - socket.send(1337); - }); - }); - }); - - it("should error with null messages", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - s.on("message", (a) => { - expect(a).to.be(null); - done(); - }); - socket.send(null); - }); - }); - }); - - it("should handle transport null messages", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv, { reconnection: false }); - sio.on("connection", (s) => { - s.on("error", (err) => { - expect(err).to.be.an(Error); - s.on("disconnect", (reason) => { - expect(reason).to.be("forced close"); - done(); - }); - }); - (s as any).client.ondata(null); - }); - }); - }); - - it("should emit events", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - socket.on("woot", (a) => { - expect(a).to.be("tobi"); - done(); - }); - sio.on("connection", (s) => { - s.emit("woot", "tobi"); - }); - }); - }); - - it("should emit events with utf8 multibyte character", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - let i = 0; - socket.on("hoot", (a) => { - expect(a).to.be("utf8 — string"); - i++; - - if (3 == i) { - done(); - } - }); - sio.on("connection", (s) => { - s.emit("hoot", "utf8 — string"); - s.emit("hoot", "utf8 — string"); - s.emit("hoot", "utf8 — string"); - }); - }); - }); - - it("should emit events with binary data", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - let imageData; - socket.on("doge", (a) => { - expect(Buffer.isBuffer(a)).to.be(true); - expect(imageData.length).to.equal(a.length); - expect(imageData[0]).to.equal(a[0]); - expect(imageData[imageData.length - 1]).to.equal(a[a.length - 1]); - done(); - }); - sio.on("connection", (s) => { - fs.readFile(join(__dirname, "support", "doge.jpg"), (err, data) => { - if (err) return done(err); - imageData = data; - s.emit("doge", data); - }); - }); - }); - }); - - it("should emit events with several types of data (including binary)", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - socket.on("multiple", (a, b, c, d, e, f) => { - expect(a).to.be(1); - expect(Buffer.isBuffer(b)).to.be(true); - expect(c).to.be("3"); - expect(d).to.eql([4]); - expect(Buffer.isBuffer(e)).to.be(true); - expect(Buffer.isBuffer(f[0])).to.be(true); - expect(f[1]).to.be("swag"); - expect(Buffer.isBuffer(f[2])).to.be(true); - done(); - }); - sio.on("connection", (s) => { - fs.readFile(join(__dirname, "support", "doge.jpg"), (err, data) => { - if (err) return done(err); - const buf = Buffer.from("asdfasdf", "utf8"); - s.emit("multiple", 1, data, "3", [4], buf, [data, "swag", buf]); - }); - }); - }); - }); - - it("should receive events with binary data", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - s.on("buff", (a) => { - expect(Buffer.isBuffer(a)).to.be(true); - done(); - }); - const buf = Buffer.from("abcdefg", "utf8"); - socket.emit("buff", buf); - }); - }); - }); - - it("should receive events with several types of data (including binary)", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - s.on("multiple", (a, b, c, d, e, f) => { - expect(a).to.be(1); - expect(Buffer.isBuffer(b)).to.be(true); - expect(c).to.be("3"); - expect(d).to.eql([4]); - expect(Buffer.isBuffer(e)).to.be(true); - expect(Buffer.isBuffer(f[0])).to.be(true); - expect(f[1]).to.be("swag"); - expect(Buffer.isBuffer(f[2])).to.be(true); - done(); - }); - fs.readFile(join(__dirname, "support", "doge.jpg"), (err, data) => { - if (err) return done(err); - const buf = Buffer.from("asdfasdf", "utf8"); - socket.emit("multiple", 1, data, "3", [4], buf, [ - data, - "swag", - buf, - ]); - }); - }); - }); - }); - - it("should not emit volatile event after regular event (polling)", (done) => { - const srv = createServer(); - const sio = new Server(srv, { transports: ["polling"] }); - - let counter = 0; - srv.listen(() => { - sio.on("connection", (s) => { - s.emit("ev", "data"); - s.volatile.emit("ev", "data"); - }); - - const socket = client(srv, { transports: ["polling"] }); - socket.on("ev", () => { - counter++; - }); - }); - - setTimeout(() => { - expect(counter).to.be(1); - done(); - }, 200); - }); - - it("should not emit volatile event after regular event (ws)", (done) => { - const srv = createServer(); - const sio = new Server(srv, { transports: ["websocket"] }); - - let counter = 0; - srv.listen(() => { - sio.on("connection", (s) => { - s.emit("ev", "data"); - s.volatile.emit("ev", "data"); - }); - - const socket = client(srv, { transports: ["websocket"] }); - socket.on("ev", () => { - counter++; - }); - }); - - setTimeout(() => { - expect(counter).to.be(1); - done(); - }, 200); - }); - - it("should emit volatile event (polling)", (done) => { - const srv = createServer(); - const sio = new Server(srv, { transports: ["polling"] }); - - let counter = 0; - srv.listen(() => { - sio.on("connection", (s) => { - // Wait to make sure there are no packets being sent for opening the connection - setTimeout(() => { - s.volatile.emit("ev", "data"); - }, 100); - }); - - const socket = client(srv, { transports: ["polling"] }); - socket.on("ev", () => { - counter++; - }); - }); - - setTimeout(() => { - expect(counter).to.be(1); - done(); - }, 500); - }); - - it("should emit volatile event (ws)", (done) => { - const srv = createServer(); - const sio = new Server(srv, { transports: ["websocket"] }); - - let counter = 0; - srv.listen(() => { - sio.on("connection", (s) => { - // Wait to make sure there are no packets being sent for opening the connection - setTimeout(() => { - s.volatile.emit("ev", "data"); - }, 20); - }); - - const socket = client(srv, { transports: ["websocket"] }); - socket.on("ev", () => { - counter++; - }); - }); - - setTimeout(() => { - expect(counter).to.be(1); - done(); - }, 200); - }); - - it("should emit only one consecutive volatile event (polling)", (done) => { - const srv = createServer(); - const sio = new Server(srv, { transports: ["polling"] }); - - let counter = 0; - srv.listen(() => { - sio.on("connection", (s) => { - // Wait to make sure there are no packets being sent for opening the connection - setTimeout(() => { - s.volatile.emit("ev", "data"); - s.volatile.emit("ev", "data"); - }, 100); - }); - - const socket = client(srv, { transports: ["polling"] }); - socket.on("ev", () => { - counter++; - }); - }); - - setTimeout(() => { - expect(counter).to.be(1); - done(); - }, 500); - }); - - it("should emit only one consecutive volatile event (ws)", (done) => { - const srv = createServer(); - const sio = new Server(srv, { transports: ["websocket"] }); - - let counter = 0; - srv.listen(() => { - sio.on("connection", (s) => { - // Wait to make sure there are no packets being sent for opening the connection - setTimeout(() => { - s.volatile.emit("ev", "data"); - s.volatile.emit("ev", "data"); - }, 20); - }); - - const socket = client(srv, { transports: ["websocket"] }); - socket.on("ev", () => { - counter++; - }); - }); - - setTimeout(() => { - expect(counter).to.be(1); - done(); - }, 200); - }); - - it("should emit only one consecutive volatile event with binary (ws)", (done) => { - const srv = createServer(); - const sio = new Server(srv, { transports: ["websocket"] }); - - let counter = 0; - srv.listen(() => { - sio.on("connection", (s) => { - // Wait to make sure there are no packets being sent for opening the connection - setTimeout(() => { - s.volatile.emit("ev", Buffer.from([1, 2, 3])); - s.volatile.emit("ev", Buffer.from([4, 5, 6])); - }, 20); - }); - - const socket = client(srv, { transports: ["websocket"] }); - socket.on("ev", () => { - counter++; - }); - }); - - setTimeout(() => { - expect(counter).to.be(1); - done(); - }, 200); - }); - - it("should broadcast only one consecutive volatile event with binary (ws)", (done) => { - const srv = createServer(); - const sio = new Server(srv, { transports: ["websocket"] }); - - let counter = 0; - srv.listen(() => { - sio.on("connection", (s) => { - // Wait to make sure there are no packets being sent for opening the connection - setTimeout(() => { - sio.volatile.emit("ev", Buffer.from([1, 2, 3])); - sio.volatile.emit("ev", Buffer.from([4, 5, 6])); - }, 20); - }); - - const socket = client(srv, { transports: ["websocket"] }); - socket.on("ev", () => { - counter++; - }); - }); - - setTimeout(() => { - expect(counter).to.be(1); - done(); - }, 200); - }); - - it("should emit regular events after trying a failed volatile event (polling)", (done) => { - const srv = createServer(); - const sio = new Server(srv, { transports: ["polling"] }); - - let counter = 0; - srv.listen(() => { - sio.on("connection", (s) => { - // Wait to make sure there are no packets being sent for opening the connection - setTimeout(() => { - s.emit("ev", "data"); - s.volatile.emit("ev", "data"); - s.emit("ev", "data"); - }, 20); - }); - - const socket = client(srv, { transports: ["polling"] }); - socket.on("ev", () => { - counter++; - }); - }); - - setTimeout(() => { - expect(counter).to.be(2); - done(); - }, 200); - }); - - it("should emit regular events after trying a failed volatile event (ws)", (done) => { - const srv = createServer(); - const sio = new Server(srv, { transports: ["websocket"] }); - - let counter = 0; - srv.listen(() => { - sio.on("connection", (s) => { - // Wait to make sure there are no packets being sent for opening the connection - setTimeout(() => { - s.emit("ev", "data"); - s.volatile.emit("ev", "data"); - s.emit("ev", "data"); - }, 20); - }); - - const socket = client(srv, { transports: ["websocket"] }); - socket.on("ev", () => { - counter++; - }); - }); - - setTimeout(() => { - expect(counter).to.be(2); - done(); - }, 200); - }); - - it("should emit message events through `send`", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - socket.on("message", (a) => { - expect(a).to.be("a"); - done(); - }); - sio.on("connection", (s) => { - s.send("a"); - }); - }); - }); - - it("should receive event with callbacks", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - s.on("woot", (fn) => { - fn(1, 2); - }); - socket.emit("woot", (a, b) => { - expect(a).to.be(1); - expect(b).to.be(2); - done(); - }); - }); - }); - }); - - it("should receive all events emitted from namespaced client immediately and in order", (done) => { - const srv = createServer(); - const sio = new Server(srv); - let total = 0; - srv.listen(() => { - sio.of("/chat", (s) => { - s.on("hi", (letter) => { - total++; - if (total == 2 && letter == "b") { - done(); - } else if (total == 1 && letter != "a") { - throw new Error("events out of order"); - } - }); - }); - - const chat = client(srv, "/chat"); - chat.emit("hi", "a"); - setTimeout(() => { - chat.emit("hi", "b"); - }, 50); - }); - }); - - it("should emit events with callbacks", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - socket.on("hi", (fn) => { - fn(); - }); - s.emit("hi", () => { - done(); - }); - }); - }); - }); - - it("should receive events with args and callback", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - s.on("woot", (a, b, fn) => { - expect(a).to.be(1); - expect(b).to.be(2); - fn(); - }); - socket.emit("woot", 1, 2, () => { - done(); - }); - }); - }); - }); - - it("should emit events with args and callback", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - socket.on("hi", (a, b, fn) => { - expect(a).to.be(1); - expect(b).to.be(2); - fn(); - }); - s.emit("hi", 1, 2, () => { - done(); - }); - }); - }); - }); - - it("should receive events with binary args and callbacks", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - s.on("woot", (buf, fn) => { - expect(Buffer.isBuffer(buf)).to.be(true); - fn(1, 2); - }); - socket.emit("woot", Buffer.alloc(3), (a, b) => { - expect(a).to.be(1); - expect(b).to.be(2); - done(); - }); - }); - }); - }); - - it("should emit events with binary args and callback", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - socket.on("hi", (a, fn) => { - expect(Buffer.isBuffer(a)).to.be(true); - fn(); - }); - s.emit("hi", Buffer.alloc(4), () => { - done(); - }); - }); - }); - }); - - it("should emit events and receive binary data in a callback", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - socket.on("hi", (fn) => { - fn(Buffer.alloc(1)); - }); - s.emit("hi", (a) => { - expect(Buffer.isBuffer(a)).to.be(true); - done(); - }); - }); - }); - }); - - it("should receive events and pass binary data in a callback", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - s.on("woot", (fn) => { - fn(Buffer.alloc(2)); - }); - socket.emit("woot", (a) => { - expect(Buffer.isBuffer(a)).to.be(true); - done(); - }); - }); - }); - }); - - it("should have access to the client", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - expect(s.client).to.be.an("object"); - done(); - }); - }); - }); - - it("should have access to the connection", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - expect(s.client.conn).to.be.an("object"); - expect(s.conn).to.be.an("object"); - done(); - }); - }); - }); - - it("should have access to the request", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - expect(s.client.request.headers).to.be.an("object"); - expect(s.request.headers).to.be.an("object"); - done(); - }); - }); - }); - - it("should see query parameters in the request", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv, { query: { key1: 1, key2: 2 } }); - sio.on("connection", (s) => { - const parsed = require("url").parse(s.request.url); - const query = require("querystring").parse(parsed.query); - expect(query.key1).to.be("1"); - expect(query.key2).to.be("2"); - done(); - }); - }); - }); - - it("should see query parameters sent from secondary namespace connections in handshake object", (done) => { - const srv = createServer(); - const sio = new Server(srv); - const client1 = client(srv); - const client2 = client(srv, "/connection2", { - auth: { key1: "aa", key2: "&=bb" }, - }); - sio.on("connection", (s) => {}); - sio.of("/connection2").on("connection", (s) => { - expect(s.handshake.query.key1).to.be(undefined); - expect(s.handshake.query.EIO).to.be("4"); - expect(s.handshake.auth.key1).to.be("aa"); - expect(s.handshake.auth.key2).to.be("&=bb"); - done(); - }); - }); - - it("should handle very large json", function (done) { - this.timeout(30000); - const srv = createServer(); - const sio = new Server(srv, { perMessageDeflate: false }); - let received = 0; - srv.listen(() => { - const socket = client(srv); - socket.on("big", (a) => { - expect(Buffer.isBuffer(a.json)).to.be(false); - if (++received == 3) done(); - else socket.emit("big", a); - }); - sio.on("connection", (s) => { - fs.readFile( - join(__dirname, "fixtures", "big.json"), - (err, data: any) => { - if (err) return done(err); - data = JSON.parse(data); - s.emit("big", { hello: "friend", json: data }); - } - ); - s.on("big", (a) => { - s.emit("big", a); - }); - }); - }); - }); - - it("should handle very large binary data", function (done) { - this.timeout(30000); - const srv = createServer(); - const sio = new Server(srv, { perMessageDeflate: false }); - let received = 0; - srv.listen(() => { - const socket = client(srv); - socket.on("big", (a) => { - expect(Buffer.isBuffer(a.image)).to.be(true); - if (++received == 3) done(); - else socket.emit("big", a); - }); - sio.on("connection", (s) => { - fs.readFile(join(__dirname, "fixtures", "big.jpg"), (err, data) => { - if (err) return done(err); - s.emit("big", { hello: "friend", image: data }); - }); - s.on("big", (a) => { - expect(Buffer.isBuffer(a.image)).to.be(true); - s.emit("big", a); - }); - }); - }); - }); - - it("should be able to emit after server close and restart", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - sio.on("connection", (socket) => { - socket.on("ev", (data) => { - expect(data).to.be("payload"); - done(); - }); - }); - - srv.listen(() => { - const { port } = srv.address() as AddressInfo; - const clientSocket = client(srv, { - reconnectionAttempts: 10, - reconnectionDelay: 100, - }); - clientSocket.once("connect", () => { - sio.close(() => { - clientSocket.io.on("reconnect", () => { - clientSocket.emit("ev", "payload"); - }); - sio.listen(port); - }); - }); - }); - }); - - it("should enable compression by default", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv, "/chat"); - sio.of("/chat").on("connection", (s) => { - s.conn.once("packetCreate", (packet) => { - expect(packet.options.compress).to.be(true); - done(); - }); - sio.of("/chat").emit("woot", "hi"); - }); - }); - }); - - it("should disable compression", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv, "/chat"); - sio.of("/chat").on("connection", (s) => { - s.conn.once("packetCreate", (packet) => { - expect(packet.options.compress).to.be(false); - done(); - }); - sio.of("/chat").compress(false).emit("woot", "hi"); - }); - }); - }); - - it("should error with raw binary and warn", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv, { reconnection: false }); - sio.on("connection", (s) => { - s.conn.on("upgrade", () => { - console.log( - "\u001b[96mNote: warning expected and normal in test.\u001b[39m" - ); - // @ts-ignore - socket.io.engine.write("5woooot"); - setTimeout(() => { - done(); - }, 100); - }); - }); - }); - }); - - it("should not crash when receiving an error packet without handler", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv, { reconnection: false }); - sio.on("connection", (s) => { - s.conn.on("upgrade", () => { - console.log( - "\u001b[96mNote: warning expected and normal in test.\u001b[39m" - ); - // @ts-ignore - socket.io.engine.write('44["handle me please"]'); - setTimeout(() => { - done(); - }, 100); - }); - }); - }); - }); - - it("should not crash with raw binary", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv, { reconnection: false }); - sio.on("connection", (s) => { - s.once("error", (err) => { - expect(err.message).to.match(/Illegal attachments/); - done(); - }); - s.conn.on("upgrade", () => { - // @ts-ignore - socket.io.engine.write("5woooot"); - }); - }); - }); - }); - - it("should handle empty binary packet", (done) => { - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv, { reconnection: false }); - sio.on("connection", (s) => { - s.once("error", (err) => { - expect(err.message).to.match(/Illegal attachments/); - done(); - }); - s.conn.on("upgrade", () => { - // @ts-ignore - socket.io.engine.write("5"); - }); - }); - }); - }); - - it("should not crash when messing with Object prototype (and other globals)", (done) => { - // @ts-ignore - Object.prototype.foo = "bar"; - // @ts-ignore - global.File = ""; - // @ts-ignore - global.Blob = []; - const srv = createServer(); - const sio = new Server(srv); - srv.listen(() => { - const socket = client(srv); - - sio.on("connection", (s) => { - s.disconnect(true); - sio.close(); - setTimeout(() => { - done(); - }, 100); - }); - }); - }); - - it("should throw on reserved event", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - expect(() => s.emit("connect_error")).to.throwException( - /"connect_error" is a reserved event name/ - ); - socket.close(); - done(); - }); - }); - }); - - it("should ignore a packet received after disconnection", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(() => { - const clientSocket = client(srv); - - const success = () => { - clientSocket.close(); - sio.close(); - done(); - }; - - sio.on("connection", (socket) => { - socket.on("test", () => { - done(new Error("should not happen")); - }); - socket.on("disconnect", success); - }); - - clientSocket.on("connect", () => { - clientSocket.emit("test", Buffer.alloc(10)); - clientSocket.disconnect(); - }); - }); - }); - - it("should leave all rooms joined after a middleware failure", (done) => { - const io = new Server(0); - const client = createClient(io, "/"); - - io.use((socket, next) => { - socket.join("room1"); - next(new Error("nope")); - }); - - client.on("connect_error", () => { - expect(io.of("/").adapter.rooms.size).to.eql(0); - - io.close(); - done(); - }); - }); - - it("should not join rooms after disconnection", (done) => { - const io = new Server(0); - const client = createClient(io, "/"); - - io.on("connection", (socket) => { - socket.disconnect(); - socket.join("room1"); - }); - - client.on("disconnect", () => { - expect(io.of("/").adapter.rooms.size).to.eql(0); - - io.close(); - done(); - }); - }); - - describe("onAny", () => { - it("should call listener", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(() => { - const socket = client(srv, { multiplex: false }); - - socket.emit("my-event", "123"); - - sio.on("connection", (socket: Socket) => { - socket.onAny((event, arg1) => { - expect(event).to.be("my-event"); - expect(arg1).to.be("123"); - done(); - }); - }); - }); - }); - - it("should prepend listener", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(() => { - const socket = client(srv, { multiplex: false }); - - socket.emit("my-event", "123"); - - sio.on("connection", (socket: Socket) => { - let count = 0; - - socket.onAny((event, arg1) => { - expect(count).to.be(2); - done(); - }); - - socket.prependAny(() => { - expect(count++).to.be(1); - }); - - socket.prependAny(() => { - expect(count++).to.be(0); - }); - }); - }); - }); - - it("should remove listener", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(() => { - const socket = client(srv, { multiplex: false }); - - socket.emit("my-event", "123"); - - sio.on("connection", (socket: Socket) => { - const fail = () => done(new Error("fail")); - - socket.onAny(fail); - socket.offAny(fail); - expect(socket.listenersAny.length).to.be(0); - - socket.onAny(() => { - done(); - }); - }); - }); - }); - }); - - describe("onAnyOutgoing", () => { - it("should call listener", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(() => { - const clientSocket = client(srv, { multiplex: false }); - - sio.on("connection", (socket) => { - socket.onAnyOutgoing((event, arg1) => { - expect(event).to.be("my-event"); - expect(arg1).to.be("123"); - - success(sio, clientSocket, done); - }); - - socket.emit("my-event", "123"); - }); - }); - }); - - it("should call listener when broadcasting", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(() => { - const clientSocket = client(srv, { multiplex: false }); - - sio.on("connection", (socket) => { - socket.onAnyOutgoing((event, arg1) => { - expect(event).to.be("my-event"); - expect(arg1).to.be("123"); - - success(sio, clientSocket, done); - }); - - sio.emit("my-event", "123"); - }); - }); - }); - - it("should prepend listener", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(async () => { - const clientSocket = client(srv, { multiplex: false }); - - const socket = (await waitFor(sio, "connection")) as Socket; - - let count = 0; - - socket.onAnyOutgoing((event, arg1) => { - expect(count).to.be(2); - - success(sio, clientSocket, done); - }); - - socket.prependAnyOutgoing(() => { - expect(count++).to.be(1); - }); - - socket.prependAnyOutgoing(() => { - expect(count++).to.be(0); - }); - - socket.emit("my-event", "123"); - }); - }); - - it("should remove listener", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(() => { - const clientSocket = client(srv, { multiplex: false }); - - sio.on("connection", (socket) => { - const fail = () => done(new Error("fail")); - - socket.onAnyOutgoing(fail); - socket.offAnyOutgoing(fail); - expect(socket.listenersAnyOutgoing.length).to.be(0); - - socket.onAnyOutgoing(() => { - success(sio, clientSocket, done); - }); - - socket.emit("my-event", "123"); - }); - }); - }); - }); - }); - - describe("messaging many", () => { - it("emits to a namespace", (done) => { - const srv = createServer(); - const sio = new Server(srv); - let total = 2; - - srv.listen(() => { - const socket1 = client(srv, { multiplex: false }); - const socket2 = client(srv, { multiplex: false }); - const socket3 = client(srv, "/test"); - socket1.on("a", (a) => { - expect(a).to.be("b"); - --total || done(); - }); - socket2.on("a", (a) => { - expect(a).to.be("b"); - --total || done(); - }); - socket3.on("a", () => { - done(new Error("not")); - }); - - let sockets = 3; - sio.on("connection", (socket) => { - --sockets || emit(); - }); - sio.of("/test", (socket) => { - --sockets || emit(); - }); - - function emit() { - sio.emit("a", "b"); - } - }); - }); - - it("emits binary data to a namespace", (done) => { - const srv = createServer(); - const sio = new Server(srv); - let total = 2; - - srv.listen(() => { - const socket1 = client(srv, { multiplex: false }); - const socket2 = client(srv, { multiplex: false }); - const socket3 = client(srv, "/test"); - socket1.on("bin", (a) => { - expect(Buffer.isBuffer(a)).to.be(true); - --total || done(); - }); - socket2.on("bin", (a) => { - expect(Buffer.isBuffer(a)).to.be(true); - --total || done(); - }); - socket3.on("bin", () => { - done(new Error("not")); - }); - - let sockets = 3; - sio.on("connection", (socket) => { - --sockets || emit(); - }); - sio.of("/test", (socket) => { - --sockets || emit(); - }); - - function emit() { - sio.emit("bin", Buffer.alloc(10)); - } - }); - }); - - it("emits to the rest", (done) => { - const srv = createServer(); - const sio = new Server(srv); - const total = 2; - - srv.listen(() => { - const socket1 = client(srv, { multiplex: false }); - const socket2 = client(srv, { multiplex: false }); - const socket3 = client(srv, "/test"); - socket1.on("a", (a) => { - expect(a).to.be("b"); - socket1.emit("finish"); - }); - socket2.emit("broadcast"); - socket2.on("a", () => { - done(new Error("done")); - }); - socket3.on("a", () => { - done(new Error("not")); - }); - - const sockets = 2; - sio.on("connection", (socket) => { - socket.on("broadcast", () => { - socket.broadcast.emit("a", "b"); - }); - socket.on("finish", () => { - done(); - }); - }); - }); - }); - - it("emits to rooms", (done) => { - const srv = createServer(); - const sio = new Server(srv); - const total = 2; - - srv.listen(() => { - const socket1 = client(srv, { multiplex: false }); - const socket2 = client(srv, { multiplex: false }); - - socket2.on("a", () => { - done(new Error("not")); - }); - socket1.on("a", () => { - done(); - }); - socket1.emit("join", "woot"); - socket1.emit("emit", "woot"); - - sio.on("connection", (socket) => { - socket.on("join", (room, fn) => { - socket.join(room); - fn && fn(); - }); - - socket.on("emit", (room) => { - sio.in(room).emit("a"); - }); - }); - }); - }); - - it("emits to rooms avoiding dupes", (done) => { - const srv = createServer(); - const sio = new Server(srv); - let total = 2; - - srv.listen(() => { - const socket1 = client(srv, { multiplex: false }); - const socket2 = client(srv, { multiplex: false }); - - socket2.on("a", () => { - done(new Error("not")); - }); - socket1.on("a", () => { - --total || done(); - }); - socket2.on("b", () => { - --total || done(); - }); - - socket1.emit("join", "woot"); - socket1.emit("join", "test"); - socket2.emit("join", "third", () => { - socket2.emit("emit"); - }); - - sio.on("connection", (socket) => { - socket.on("join", (room, fn) => { - socket.join(room); - fn && fn(); - }); - - socket.on("emit", (room) => { - sio.in("woot").in("test").emit("a"); - sio.in("third").emit("b"); - }); - }); - }); - }); - - it("broadcasts to rooms", (done) => { - const srv = createServer(); - const sio = new Server(srv); - let total = 2; - - srv.listen(() => { - const socket1 = client(srv, { multiplex: false }); - const socket2 = client(srv, { multiplex: false }); - const socket3 = client(srv, { multiplex: false }); - - socket1.emit("join", "woot"); - socket2.emit("join", "test"); - socket3.emit("join", "test", () => { - socket3.emit("broadcast"); - }); - - socket1.on("a", () => { - done(new Error("not")); - }); - socket2.on("a", () => { - --total || done(); - }); - socket3.on("a", () => { - done(new Error("not")); - }); - socket3.on("b", () => { - --total || done(); - }); - - sio.on("connection", (socket) => { - socket.on("join", (room, fn) => { - socket.join(room); - fn && fn(); - }); - - socket.on("broadcast", () => { - socket.broadcast.to("test").emit("a"); - socket.emit("b"); - }); - }); - }); - }); - - it("broadcasts binary data to rooms", (done) => { - const srv = createServer(); - const sio = new Server(srv); - let total = 2; - - srv.listen(() => { - const socket1 = client(srv, { multiplex: false }); - const socket2 = client(srv, { multiplex: false }); - const socket3 = client(srv, { multiplex: false }); - - socket1.emit("join", "woot"); - socket2.emit("join", "test"); - socket3.emit("join", "test", () => { - socket3.emit("broadcast"); - }); - - socket1.on("bin", (data) => { - throw new Error("got bin in socket1"); - }); - socket2.on("bin", (data) => { - expect(Buffer.isBuffer(data)).to.be(true); - --total || done(); - }); - socket2.on("bin2", (data) => { - throw new Error("socket2 got bin2"); - }); - socket3.on("bin", (data) => { - throw new Error("socket3 got bin"); - }); - socket3.on("bin2", (data) => { - expect(Buffer.isBuffer(data)).to.be(true); - --total || done(); - }); - - sio.on("connection", (socket) => { - socket.on("join", (room, fn) => { - socket.join(room); - fn && fn(); - }); - socket.on("broadcast", () => { - socket.broadcast.to("test").emit("bin", Buffer.alloc(5)); - socket.emit("bin2", Buffer.alloc(5)); - }); - }); - }); - }); - - it("keeps track of rooms", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - s.join("a"); - expect(s.rooms).to.contain(s.id, "a"); - s.join("b"); - expect(s.rooms).to.contain(s.id, "a", "b"); - s.join("c"); - expect(s.rooms).to.contain(s.id, "a", "b", "c"); - s.leave("b"); - expect(s.rooms).to.contain(s.id, "a", "c"); - (s as any).leaveAll(); - expect(s.rooms.size).to.eql(0); - done(); - }); - }); - }); - - it("deletes empty rooms", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - s.join("a"); - expect(s.nsp.adapter.rooms).to.contain("a"); - s.leave("a"); - expect(s.nsp.adapter.rooms).to.not.contain("a"); - done(); - }); - }); - }); - - it("should properly cleanup left rooms", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - s.join("a"); - expect(s.rooms).to.contain(s.id, "a"); - s.join("b"); - expect(s.rooms).to.contain(s.id, "a", "b"); - s.leave("unknown"); - expect(s.rooms).to.contain(s.id, "a", "b"); - (s as any).leaveAll(); - expect(s.rooms.size).to.eql(0); - done(); - }); - }); - }); - - it("allows to join several rooms at once", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (s) => { - s.join(["a", "b", "c"]); - expect(s.rooms).to.contain(s.id, "a", "b", "c"); - done(); - }); - }); - }); - - it("should exclude specific sockets when broadcasting", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(() => { - const socket1 = client(srv, { multiplex: false }); - const socket2 = client(srv, { multiplex: false }); - const socket3 = client(srv, { multiplex: false }); - - socket2.on("a", () => { - done(new Error("not")); - }); - socket3.on("a", () => { - done(new Error("not")); - }); - socket1.on("a", () => { - done(); - }); - - sio.on("connection", (socket) => { - socket.on("exclude", (id) => { - socket.broadcast.except(id).emit("a"); - }); - }); - - socket2.on("connect", () => { - socket3.emit("exclude", socket2.id); - }); - }); - }); - - it("should exclude a specific room when broadcasting", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(() => { - const socket1 = client(srv, { multiplex: false }); - const socket2 = client(srv, { multiplex: false }); - const socket3 = client(srv, { multiplex: false }); - - socket2.on("a", () => { - done(new Error("not")); - }); - socket3.on("a", () => { - done(new Error("not")); - }); - socket1.on("a", () => { - done(); - }); - - sio.on("connection", (socket) => { - socket.on("join", (room, cb) => { - socket.join(room); - cb(); - }); - socket.on("broadcast", () => { - socket.broadcast.except("room1").emit("a"); - }); - }); - - socket2.emit("join", "room1", () => { - socket3.emit("broadcast"); - }); - }); - }); - - it("should return an immutable broadcast operator", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(() => { - const clientSocket = client(srv, { multiplex: false }); - - sio.on("connection", (socket: Socket) => { - const operator = socket.local - .compress(false) - .to(["room1", "room2"]) - .except("room3"); - operator.compress(true).emit("hello"); - operator.volatile.emit("hello"); - operator.to("room4").emit("hello"); - operator.except("room5").emit("hello"); - socket.emit("hello"); - socket.to("room6").emit("hello"); - // @ts-ignore - expect(operator.rooms).to.contain("room1", "room2"); - // @ts-ignore - expect(operator.rooms).to.not.contain("room4", "room5", "room6"); - // @ts-ignore - expect(operator.exceptRooms).to.contain("room3"); - // @ts-ignore - expect(operator.flags).to.eql({ local: true, compress: false }); - - clientSocket.close(); - sio.close(); - done(); - }); - }); - }); - - it("should broadcast and expect multiple acknowledgements", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(async () => { - const socket1 = client(srv, { multiplex: false }); - const socket2 = client(srv, { multiplex: false }); - const socket3 = client(srv, { multiplex: false }); - - await Promise.all([ - waitFor(socket1, "connect"), - waitFor(socket2, "connect"), - waitFor(socket3, "connect"), - ]); - - socket1.on("some event", (cb) => { - cb(1); - }); - - socket2.on("some event", (cb) => { - cb(2); - }); - - socket3.on("some event", (cb) => { - cb(3); - }); - - sio.timeout(2000).emit("some event", (err, responses) => { - expect(err).to.be(null); - expect(responses).to.have.length(3); - expect(responses).to.contain(1, 2, 3); - - done(); - }); - }); - }); - - it("should fail when a client does not acknowledge the event in the given delay", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(async () => { - const socket1 = client(srv, { multiplex: false }); - const socket2 = client(srv, { multiplex: false }); - const socket3 = client(srv, { multiplex: false }); - - await Promise.all([ - waitFor(socket1, "connect"), - waitFor(socket2, "connect"), - waitFor(socket3, "connect"), - ]); - - socket1.on("some event", (cb) => { - cb(1); - }); - - socket2.on("some event", (cb) => { - cb(2); - }); - - socket3.on("some event", (cb) => { - // timeout - }); - - sio.timeout(200).emit("some event", (err, responses) => { - expect(err).to.be.an(Error); - expect(responses).to.have.length(2); - expect(responses).to.contain(1, 2); - - done(); - }); - }); - }); - - it("should broadcast and return if the packet is sent to 0 client", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(async () => { - const socket1 = client(srv, { multiplex: false }); - const socket2 = client(srv, { multiplex: false }); - const socket3 = client(srv, { multiplex: false }); - - await Promise.all([ - waitFor(socket1, "connect"), - waitFor(socket2, "connect"), - waitFor(socket3, "connect"), - ]); - - socket1.on("some event", () => { - done(new Error("should not happen")); - }); - - socket2.on("some event", () => { - done(new Error("should not happen")); - }); - - socket3.on("some event", () => { - done(new Error("should not happen")); - }); - - sio - .to("room123") - .timeout(200) - .emit("some event", (err, responses) => { - expect(err).to.be(null); - expect(responses).to.have.length(0); - - done(); - }); - }); - }); - }); - - describe("middleware", () => { - it("should call functions", (done) => { - const srv = createServer(); - const sio = new Server(srv); - let run = 0; - sio.use((socket, next) => { - expect(socket).to.be.a(Socket); - run++; - next(); - }); - sio.use((socket, next) => { - expect(socket).to.be.a(Socket); - run++; - next(); - }); - srv.listen(() => { - const socket = client(srv); - socket.on("connect", () => { - expect(run).to.be(2); - done(); - }); - }); - }); - - it("should pass errors", (done) => { - const srv = createServer(); - const sio = new Server(srv); - const run = 0; - sio.use((socket, next) => { - next(new Error("Authentication error")); - }); - sio.use((socket, next) => { - done(new Error("nope")); - }); - srv.listen(() => { - const socket = client(srv); - socket.on("connect", () => { - done(new Error("nope")); - }); - socket.on("connect_error", (err) => { - expect(err.message).to.be("Authentication error"); - done(); - }); - }); - }); - - it("should pass an object", (done) => { - const srv = createServer(); - const sio = new Server(srv); - sio.use((socket, next) => { - const err = new Error("Authentication error"); - // @ts-ignore - err.data = { a: "b", c: 3 }; - next(err); - }); - srv.listen(() => { - const socket = client(srv); - socket.on("connect", () => { - done(new Error("nope")); - }); - socket.on("connect_error", (err) => { - expect(err).to.be.an(Error); - expect(err.message).to.eql("Authentication error"); - // @ts-ignore - expect(err.data).to.eql({ a: "b", c: 3 }); - done(); - }); - }); - }); - - it("should only call connection after fns", (done) => { - const srv = createServer(); - const sio = new Server(srv); - sio.use((socket: any, next) => { - socket.name = "guillermo"; - next(); - }); - srv.listen(() => { - const socket = client(srv); - sio.on("connection", (socket) => { - expect((socket as any).name).to.be("guillermo"); - done(); - }); - }); - }); - - it("should only call connection after (lengthy) fns", (done) => { - const srv = createServer(); - const sio = new Server(srv); - let authenticated = false; - - sio.use((socket, next) => { - setTimeout(() => { - authenticated = true; - next(); - }, 300); - }); - srv.listen(() => { - const socket = client(srv); - socket.on("connect", () => { - expect(authenticated).to.be(true); - done(); - }); - }); - }); - - it("should be ignored if socket gets closed", (done) => { - const srv = createServer(); - const sio = new Server(srv); - let socket; - sio.use((s, next) => { - socket.io.engine.close(); - s.client.conn.on("close", () => { - process.nextTick(next); - setTimeout(() => { - done(); - }, 50); - }); - }); - srv.listen(() => { - socket = client(srv); - sio.on("connection", (socket) => { - done(new Error("should not fire")); - }); - }); - }); - - it("should call functions in expected order", (done) => { - const srv = createServer(); - const sio = new Server(srv); - const result: number[] = []; - - sio.use(() => { - done(new Error("should not fire")); - }); - sio.of("/chat").use((socket, next) => { - result.push(1); - setTimeout(next, 50); - }); - sio.of("/chat").use((socket, next) => { - result.push(2); - setTimeout(next, 50); - }); - sio.of("/chat").use((socket, next) => { - result.push(3); - setTimeout(next, 50); - }); - - srv.listen(() => { - const chat = client(srv, "/chat"); - chat.on("connect", () => { - expect(result).to.eql([1, 2, 3]); - done(); - }); - }); - }); - - it("should disable the merge of handshake packets", (done) => { - const srv = createServer(); - const sio = new Server(); - sio.use((socket, next) => { - next(); - }); - sio.listen(srv); - const socket = client(srv); - socket.on("connect", () => { - done(); - }); - }); - - it("should work with a custom namespace", (done) => { - const srv = createServer(); - const sio = new Server(); - sio.listen(srv); - sio.of("/chat").use((socket, next) => { - next(); - }); - - let count = 0; - client(srv, "/").on("connect", () => { - if (++count === 2) done(); - }); - client(srv, "/chat").on("connect", () => { - if (++count === 2) done(); - }); - }); - - it("should only set `connected` to true after the middleware execution", (done) => { - const httpServer = createServer(); - const io = new Server(httpServer); - - const clientSocket = client(httpServer, "/"); - - io.use((socket, next) => { - expect(socket.connected).to.be(false); - expect(socket.disconnected).to.be(true); - next(); - }); - - io.on("connection", (socket) => { - expect(socket.connected).to.be(true); - expect(socket.disconnected).to.be(false); - success(io, clientSocket, done); - }); - }); - }); - - describe("socket middleware", () => { - it("should call functions", (done) => { - const srv = createServer(); - const sio = new Server(srv); - let run = 0; - - srv.listen(() => { - const socket = client(srv, { multiplex: false }); - - socket.emit("join", "woot"); - - sio.on("connection", (socket) => { - socket.use((event, next) => { - expect(event).to.eql(["join", "woot"]); - event.unshift("wrap"); - run++; - next(); - }); - socket.use((event, next) => { - expect(event).to.eql(["wrap", "join", "woot"]); - run++; - next(); - }); - socket.on("wrap", (data1, data2) => { - expect(data1).to.be("join"); - expect(data2).to.be("woot"); - expect(run).to.be(2); - done(); - }); - }); - }); - }); - - it("should pass errors", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(() => { - const socket = client(srv, { multiplex: false }); - - socket.emit("join", "woot"); - - const success = () => { - socket.close(); - sio.close(); - done(); - }; - - sio.on("connection", (socket) => { - socket.use((event, next) => { - next(new Error("Authentication error")); - }); - socket.use((event, next) => { - done(new Error("should not happen")); - }); - socket.on("join", () => { - done(new Error("should not happen")); - }); - socket.on("error", (err) => { - expect(err).to.be.an(Error); - expect(err.message).to.eql("Authentication error"); - success(); - }); - }); - }); - }); - }); - - describe("v2 compatibility", () => { - it("should connect if `allowEIO3` is true", (done) => { - const srv = createServer(); - const sio = new Server(srv, { - allowEIO3: true, - }); - - srv.listen(async () => { - const port = (srv.address() as AddressInfo).port; - const clientSocket = io_v2.connect(`http://localhost:${port}`, { - multiplex: false, - }); - - const [socket]: Array = await Promise.all([ - waitFor(sio, "connection"), - waitFor(clientSocket, "connect"), - ]); - - expect(socket.id).to.eql(clientSocket.id); - success(sio, clientSocket, done); - }); - }); - - it("should be able to connect to a namespace with a query", (done) => { - const srv = createServer(); - const sio = new Server(srv, { - allowEIO3: true, - }); - - srv.listen(async () => { - const port = (srv.address() as AddressInfo).port; - const clientSocket = io_v2.connect( - `http://localhost:${port}/the-namespace`, - { - multiplex: false, - } - ); - clientSocket.query = { test: "123" }; - - const [socket]: Array = await Promise.all([ - waitFor(sio.of("/the-namespace"), "connection"), - waitFor(clientSocket, "connect"), - ]); - - expect(socket.handshake.auth).to.eql({ test: "123" }); - success(sio, clientSocket, done); - }); - }); - - it("should not connect if `allowEIO3` is false (default)", (done) => { - const srv = createServer(); - const sio = new Server(srv); - - srv.listen(() => { - const port = (srv.address() as AddressInfo).port; - const clientSocket = io_v2.connect(`http://localhost:${port}`, { - multiplex: false, - }); - - clientSocket.on("connect", () => { - done(new Error("should not happen")); - }); - - clientSocket.on("connect_error", () => { - success(sio, clientSocket, done); - }); - }); - }); - }); - - require("./socket-timeout"); -}); diff --git a/test/socket.ts b/test/socket.ts new file mode 100644 index 0000000000..861d5a76ee --- /dev/null +++ b/test/socket.ts @@ -0,0 +1,1043 @@ +import fs = require("fs"); +import { join } from "path"; +import { createClient, getPort, success, successFn } from "./support/util"; +import { Server } from ".."; +import expect from "expect.js"; + +describe("socket", () => { + it("should not fire events more than once after manually reconnecting", (done) => { + const io = new Server(0); + const clientSocket = createClient(io, "/", { reconnection: false }); + + clientSocket.on("connect", function init() { + clientSocket.off("connect", init); + clientSocket.io.engine.close(); + + process.nextTick(() => { + clientSocket.connect(); + }); + clientSocket.on("connect", successFn(done, io, clientSocket)); + }); + }); + + it("should not fire reconnect_failed event more than once when server closed", (done) => { + const io = new Server(0); + const clientSocket = createClient(io, "/", { + reconnectionAttempts: 3, + reconnectionDelay: 100, + }); + + clientSocket.on("connect", () => { + io.close(); + }); + + clientSocket.io.on("reconnect_failed", () => { + success(done, io, clientSocket); + }); + }); + + it("should receive events", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + s.on("random", (a, b, c) => { + expect(a).to.be(1); + expect(b).to.be("2"); + expect(c).to.eql([3]); + + success(done, io, socket); + }); + socket.emit("random", 1, "2", [3]); + }); + }); + + it("should receive message events through `send`", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + s.on("message", (a) => { + expect(a).to.be(1337); + + success(done, io, socket); + }); + socket.send(1337); + }); + }); + + it("should error with null messages", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + s.on("message", (a) => { + expect(a).to.be(null); + success(done, io, socket); + }); + socket.send(null); + }); + }); + + it("should handle transport null messages", (done) => { + const io = new Server(0); + const socket = createClient(io, "/", { reconnection: false }); + + io.on("connection", (s) => { + s.on("error", (err) => { + expect(err).to.be.an(Error); + s.on("disconnect", (reason) => { + expect(reason).to.be("forced close"); + + success(done, io, socket); + }); + }); + (s as any).client.ondata(null); + }); + }); + + it("should emit events", (done) => { + const io = new Server(0); + const socket = createClient(io); + + socket.on("woot", (a) => { + expect(a).to.be("tobi"); + success(done, io, socket); + }); + io.on("connection", (s) => { + s.emit("woot", "tobi"); + }); + }); + + it("should emit events with utf8 multibyte character", (done) => { + const io = new Server(0); + const socket = createClient(io); + let i = 0; + + socket.on("hoot", (a) => { + expect(a).to.be("utf8 — string"); + i++; + + if (3 == i) { + success(done, io, socket); + } + }); + io.on("connection", (s) => { + s.emit("hoot", "utf8 — string"); + s.emit("hoot", "utf8 — string"); + s.emit("hoot", "utf8 — string"); + }); + }); + + it("should emit events with binary data", (done) => { + const io = new Server(0); + const socket = createClient(io); + + let imageData; + socket.on("doge", (a) => { + expect(Buffer.isBuffer(a)).to.be(true); + expect(imageData.length).to.equal(a.length); + expect(imageData[0]).to.equal(a[0]); + expect(imageData[imageData.length - 1]).to.equal(a[a.length - 1]); + + success(done, io, socket); + }); + io.on("connection", (s) => { + fs.readFile(join(__dirname, "support", "doge.jpg"), (err, data) => { + if (err) return done(err); + imageData = data; + s.emit("doge", data); + }); + }); + }); + + it("should emit events with several types of data (including binary)", (done) => { + const io = new Server(0); + const socket = createClient(io); + + socket.on("multiple", (a, b, c, d, e, f) => { + expect(a).to.be(1); + expect(Buffer.isBuffer(b)).to.be(true); + expect(c).to.be("3"); + expect(d).to.eql([4]); + expect(Buffer.isBuffer(e)).to.be(true); + expect(Buffer.isBuffer(f[0])).to.be(true); + expect(f[1]).to.be("swag"); + expect(Buffer.isBuffer(f[2])).to.be(true); + + success(done, io, socket); + }); + io.on("connection", (s) => { + fs.readFile(join(__dirname, "support", "doge.jpg"), (err, data) => { + if (err) return done(err); + const buf = Buffer.from("asdfasdf", "utf8"); + s.emit("multiple", 1, data, "3", [4], buf, [data, "swag", buf]); + }); + }); + }); + + it("should receive events with binary data", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + s.on("buff", (a) => { + expect(Buffer.isBuffer(a)).to.be(true); + + success(done, io, socket); + }); + const buf = Buffer.from("abcdefg", "utf8"); + socket.emit("buff", buf); + }); + }); + + it("should receive events with several types of data (including binary)", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + s.on("multiple", (a, b, c, d, e, f) => { + expect(a).to.be(1); + expect(Buffer.isBuffer(b)).to.be(true); + expect(c).to.be("3"); + expect(d).to.eql([4]); + expect(Buffer.isBuffer(e)).to.be(true); + expect(Buffer.isBuffer(f[0])).to.be(true); + expect(f[1]).to.be("swag"); + expect(Buffer.isBuffer(f[2])).to.be(true); + + success(done, io, socket); + }); + fs.readFile(join(__dirname, "support", "doge.jpg"), (err, data) => { + if (err) return done(err); + const buf = Buffer.from("asdfasdf", "utf8"); + socket.emit("multiple", 1, data, "3", [4], buf, [data, "swag", buf]); + }); + }); + }); + + it("should not emit volatile event after regular event (polling)", (done) => { + const io = new Server(0, { transports: ["polling"] }); + + let counter = 0; + io.on("connection", (s) => { + s.emit("ev", "data"); + s.volatile.emit("ev", "data"); + }); + + const socket = createClient(io, "/", { transports: ["polling"] }); + socket.on("ev", () => { + counter++; + }); + + setTimeout(() => { + expect(counter).to.be(1); + success(done, io, socket); + }, 200); + }); + + it("should not emit volatile event after regular event (ws)", (done) => { + const io = new Server(0, { transports: ["websocket"] }); + + let counter = 0; + io.on("connection", (s) => { + s.emit("ev", "data"); + s.volatile.emit("ev", "data"); + }); + + const socket = createClient(io, "/", { transports: ["websocket"] }); + socket.on("ev", () => { + counter++; + }); + + setTimeout(() => { + expect(counter).to.be(1); + success(done, io, socket); + }, 200); + }); + + it("should emit volatile event (polling)", (done) => { + const io = new Server(0, { transports: ["polling"] }); + + let counter = 0; + io.on("connection", (s) => { + // Wait to make sure there are no packets being sent for opening the connection + setTimeout(() => { + s.volatile.emit("ev", "data"); + }, 100); + }); + + const socket = createClient(io, "/", { transports: ["polling"] }); + socket.on("ev", () => { + counter++; + }); + + setTimeout(() => { + expect(counter).to.be(1); + success(done, io, socket); + }, 500); + }); + + it("should emit volatile event (ws)", (done) => { + const io = new Server(0, { transports: ["websocket"] }); + + let counter = 0; + io.on("connection", (s) => { + // Wait to make sure there are no packets being sent for opening the connection + setTimeout(() => { + s.volatile.emit("ev", "data"); + }, 20); + }); + + const socket = createClient(io, "/", { transports: ["websocket"] }); + socket.on("ev", () => { + counter++; + }); + + setTimeout(() => { + expect(counter).to.be(1); + success(done, io, socket); + }, 200); + }); + + it("should emit only one consecutive volatile event (polling)", (done) => { + const io = new Server(0, { transports: ["polling"] }); + + let counter = 0; + io.on("connection", (s) => { + // Wait to make sure there are no packets being sent for opening the connection + setTimeout(() => { + s.volatile.emit("ev", "data"); + s.volatile.emit("ev", "data"); + }, 100); + }); + + const socket = createClient(io, "/", { transports: ["polling"] }); + socket.on("ev", () => { + counter++; + }); + + setTimeout(() => { + expect(counter).to.be(1); + success(done, io, socket); + }, 500); + }); + + it("should emit only one consecutive volatile event (ws)", (done) => { + const io = new Server(0, { transports: ["websocket"] }); + + let counter = 0; + io.on("connection", (s) => { + // Wait to make sure there are no packets being sent for opening the connection + setTimeout(() => { + s.volatile.emit("ev", "data"); + s.volatile.emit("ev", "data"); + }, 20); + }); + + const socket = createClient(io, "/", { transports: ["websocket"] }); + socket.on("ev", () => { + counter++; + }); + + setTimeout(() => { + expect(counter).to.be(1); + success(done, io, socket); + }, 200); + }); + + it("should emit only one consecutive volatile event with binary (ws)", (done) => { + const io = new Server(0, { transports: ["websocket"] }); + + let counter = 0; + io.on("connection", (s) => { + // Wait to make sure there are no packets being sent for opening the connection + setTimeout(() => { + s.volatile.emit("ev", Buffer.from([1, 2, 3])); + s.volatile.emit("ev", Buffer.from([4, 5, 6])); + }, 20); + }); + + const socket = createClient(io, "/", { transports: ["websocket"] }); + socket.on("ev", () => { + counter++; + }); + + setTimeout(() => { + expect(counter).to.be(1); + success(done, io, socket); + }, 200); + }); + + it("should broadcast only one consecutive volatile event with binary (ws)", (done) => { + const io = new Server(0, { transports: ["websocket"] }); + + let counter = 0; + io.on("connection", (s) => { + // Wait to make sure there are no packets being sent for opening the connection + setTimeout(() => { + io.volatile.emit("ev", Buffer.from([1, 2, 3])); + io.volatile.emit("ev", Buffer.from([4, 5, 6])); + }, 20); + }); + + const socket = createClient(io, "/", { transports: ["websocket"] }); + socket.on("ev", () => { + counter++; + }); + + setTimeout(() => { + expect(counter).to.be(1); + success(done, io, socket); + }, 200); + }); + + it("should emit regular events after trying a failed volatile event (polling)", (done) => { + const io = new Server(0, { transports: ["polling"] }); + + let counter = 0; + io.on("connection", (s) => { + // Wait to make sure there are no packets being sent for opening the connection + setTimeout(() => { + s.emit("ev", "data"); + s.volatile.emit("ev", "data"); + s.emit("ev", "data"); + }, 20); + }); + + const socket = createClient(io, "/", { transports: ["polling"] }); + socket.on("ev", () => { + counter++; + }); + + setTimeout(() => { + expect(counter).to.be(2); + success(done, io, socket); + }, 200); + }); + + it("should emit regular events after trying a failed volatile event (ws)", (done) => { + const io = new Server(0, { transports: ["websocket"] }); + + let counter = 0; + io.on("connection", (s) => { + // Wait to make sure there are no packets being sent for opening the connection + setTimeout(() => { + s.emit("ev", "data"); + s.volatile.emit("ev", "data"); + s.emit("ev", "data"); + }, 20); + }); + + const socket = createClient(io, "/", { transports: ["websocket"] }); + socket.on("ev", () => { + counter++; + }); + + setTimeout(() => { + expect(counter).to.be(2); + success(done, io, socket); + }, 200); + }); + + it("should emit message events through `send`", (done) => { + const io = new Server(0); + const socket = createClient(io); + + socket.on("message", (a) => { + expect(a).to.be("a"); + success(done, io, socket); + }); + io.on("connection", (s) => { + s.send("a"); + }); + }); + + it("should receive event with callbacks", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + s.on("woot", (fn) => { + fn(1, 2); + }); + socket.emit("woot", (a, b) => { + expect(a).to.be(1); + expect(b).to.be(2); + success(done, io, socket); + }); + }); + }); + + it("should receive all events emitted from namespaced client immediately and in order", (done) => { + const io = new Server(0); + let total = 0; + + io.of("/chat", (s) => { + s.on("hi", (letter) => { + total++; + if (total == 2 && letter == "b") { + success(done, io, chat); + } else if (total == 1 && letter != "a") { + throw new Error("events out of order"); + } + }); + }); + + const chat = createClient(io, "/chat"); + chat.emit("hi", "a"); + setTimeout(() => { + chat.emit("hi", "b"); + }, 50); + }); + + it("should emit events with callbacks", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + socket.on("hi", (fn) => { + fn(); + }); + s.emit("hi", () => { + success(done, io, socket); + }); + }); + }); + + it("should receive events with args and callback", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + s.on("woot", (a, b, fn) => { + expect(a).to.be(1); + expect(b).to.be(2); + fn(); + }); + socket.emit("woot", 1, 2, () => { + success(done, io, socket); + }); + }); + }); + + it("should emit events with args and callback", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + socket.on("hi", (a, b, fn) => { + expect(a).to.be(1); + expect(b).to.be(2); + fn(); + }); + s.emit("hi", 1, 2, () => { + success(done, io, socket); + }); + }); + }); + + it("should receive events with binary args and callbacks", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + s.on("woot", (buf, fn) => { + expect(Buffer.isBuffer(buf)).to.be(true); + fn(1, 2); + }); + socket.emit("woot", Buffer.alloc(3), (a, b) => { + expect(a).to.be(1); + expect(b).to.be(2); + success(done, io, socket); + }); + }); + }); + + it("should emit events with binary args and callback", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + socket.on("hi", (a, fn) => { + expect(Buffer.isBuffer(a)).to.be(true); + fn(); + }); + s.emit("hi", Buffer.alloc(4), () => { + success(done, io, socket); + }); + }); + }); + + it("should emit events and receive binary data in a callback", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + socket.on("hi", (fn) => { + fn(Buffer.alloc(1)); + }); + s.emit("hi", (a) => { + expect(Buffer.isBuffer(a)).to.be(true); + success(done, io, socket); + }); + }); + }); + + it("should receive events and pass binary data in a callback", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + s.on("woot", (fn) => { + fn(Buffer.alloc(2)); + }); + socket.emit("woot", (a) => { + expect(Buffer.isBuffer(a)).to.be(true); + success(done, io, socket); + }); + }); + }); + + it("should have access to the client", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + expect(s.client).to.be.an("object"); + success(done, io, socket); + }); + }); + + it("should have access to the connection", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + expect(s.client.conn).to.be.an("object"); + expect(s.conn).to.be.an("object"); + success(done, io, socket); + }); + }); + + it("should have access to the request", (done) => { + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", (s) => { + expect(s.client.request.headers).to.be.an("object"); + expect(s.request.headers).to.be.an("object"); + success(done, io, socket); + }); + }); + + it("should see query parameters in the request", (done) => { + const io = new Server(0); + const socket = createClient(io, "/", { query: { key1: 1, key2: 2 } }); + + io.on("connection", (s) => { + const parsed = require("url").parse(s.request.url); + const query = require("querystring").parse(parsed.query); + expect(query.key1).to.be("1"); + expect(query.key2).to.be("2"); + success(done, io, socket); + }); + }); + + it("should see query parameters sent from secondary namespace connections in handshake object", (done) => { + const io = new Server(0); + const client1 = createClient(io); + const client2 = createClient(io, "/connection2", { + auth: { key1: "aa", key2: "&=bb" }, + }); + io.on("connection", (s) => {}); + io.of("/connection2").on("connection", (s) => { + expect(s.handshake.query.key1).to.be(undefined); + expect(s.handshake.query.EIO).to.be("4"); + expect(s.handshake.auth.key1).to.be("aa"); + expect(s.handshake.auth.key2).to.be("&=bb"); + success(done, io, client1, client2); + }); + }); + + it("should handle very large json", function (done) { + this.timeout(30000); + const io = new Server(0, { perMessageDeflate: false }); + let received = 0; + + const socket = createClient(io); + socket.on("big", (a) => { + expect(Buffer.isBuffer(a.json)).to.be(false); + if (++received == 3) success(done, io, socket); + else socket.emit("big", a); + }); + io.on("connection", (s) => { + fs.readFile(join(__dirname, "fixtures", "big.json"), (err, data: any) => { + if (err) return done(err); + data = JSON.parse(data); + s.emit("big", { hello: "friend", json: data }); + }); + s.on("big", (a) => { + s.emit("big", a); + }); + }); + }); + + it("should handle very large binary data", function (done) { + this.timeout(30000); + const io = new Server(0, { perMessageDeflate: false }); + let received = 0; + + const socket = createClient(io); + socket.on("big", (a) => { + expect(Buffer.isBuffer(a.image)).to.be(true); + if (++received == 3) success(done, io, socket); + else socket.emit("big", a); + }); + io.on("connection", (s) => { + fs.readFile(join(__dirname, "fixtures", "big.jpg"), (err, data) => { + if (err) return done(err); + s.emit("big", { hello: "friend", image: data }); + }); + s.on("big", (a) => { + expect(Buffer.isBuffer(a.image)).to.be(true); + s.emit("big", a); + }); + }); + }); + + it("should be able to emit after server close and restart", (done) => { + const io = new Server(0); + + io.on("connection", (socket) => { + socket.on("ev", (data) => { + expect(data).to.be("payload"); + success(done, io, clientSocket); + }); + }); + + const port = getPort(io); + const clientSocket = createClient(io, "/", { + reconnectionAttempts: 10, + reconnectionDelay: 100, + }); + clientSocket.once("connect", () => { + io.close(() => { + clientSocket.io.on("reconnect", () => { + clientSocket.emit("ev", "payload"); + }); + io.listen(port); + }); + }); + }); + + it("should enable compresion by default", (done) => { + const io = new Server(0); + const socket = createClient(io, "/chat"); + + io.of("/chat").on("connection", (s) => { + s.conn.once("packetCreate", (packet) => { + expect(packet.options.compress).to.be(true); + success(done, io, socket); + }); + io.of("/chat").emit("woot", "hi"); + }); + }); + + it("should disable compresion", (done) => { + const io = new Server(0); + const socket = createClient(io, "/chat"); + + io.of("/chat").on("connection", (s) => { + s.conn.once("packetCreate", (packet) => { + expect(packet.options.compress).to.be(false); + success(done, io, socket); + }); + io.of("/chat").compress(false).emit("woot", "hi"); + }); + }); + + it("should error with raw binary and warn", (done) => { + const io = new Server(0); + const socket = createClient(io, "/", { reconnection: false }); + + io.on("connection", (s) => { + s.conn.on("upgrade", () => { + console.log( + "\u001b[96mNote: warning expected and normal in test.\u001b[39m" + ); + // @ts-ignore + socket.io.engine.write("5woooot"); + setTimeout(() => { + success(done, io, socket); + }, 100); + }); + }); + }); + + it("should not crash when receiving an error packet without handler", (done) => { + const io = new Server(0); + const socket = createClient(io, "/", { reconnection: false }); + + io.on("connection", (s) => { + s.conn.on("upgrade", () => { + console.log( + "\u001b[96mNote: warning expected and normal in test.\u001b[39m" + ); + // @ts-ignore + socket.io.engine.write('44["handle me please"]'); + setTimeout(() => { + success(done, io, socket); + }, 100); + }); + }); + }); + + it("should not crash with raw binary", (done) => { + const io = new Server(0); + const socket = createClient(io, "/", { reconnection: false }); + + io.on("connection", (s) => { + s.once("error", (err) => { + expect(err.message).to.match(/Illegal attachments/); + success(done, io, socket); + }); + s.conn.on("upgrade", () => { + // @ts-ignore + socket.io.engine.write("5woooot"); + }); + }); + }); + + it("should handle empty binary packet", (done) => { + const io = new Server(0); + const socket = createClient(io, "/", { reconnection: false }); + + io.on("connection", (s) => { + s.once("error", (err) => { + expect(err.message).to.match(/Illegal attachments/); + success(done, io, socket); + }); + s.conn.on("upgrade", () => { + // @ts-ignore + socket.io.engine.write("5"); + }); + }); + }); + + it("should not crash when messing with Object prototype (and other globals)", (done) => { + // @ts-ignore + Object.prototype.foo = "bar"; + // @ts-ignore + global.File = ""; + // @ts-ignore + global.Blob = []; + const io = new Server(0); + const socket = createClient(io); + + io.on("connection", successFn(done, io, socket)); + }); + + it("should throw on reserved event", (done) => { + const io = new Server(0); + + const socket = createClient(io); + io.on("connection", (s) => { + expect(() => s.emit("connect_error")).to.throwException( + /"connect_error" is a reserved event name/ + ); + socket.close(); + success(done, io, socket); + }); + }); + + it("should ignore a packet received after disconnection", (done) => { + const io = new Server(0); + const clientSocket = createClient(io); + + io.on("connection", (socket) => { + socket.on("test", () => { + done(new Error("should not happen")); + }); + socket.on("disconnect", successFn(done, io, clientSocket)); + }); + + clientSocket.on("connect", () => { + clientSocket.emit("test", Buffer.alloc(10)); + clientSocket.disconnect(); + }); + }); + + it("should leave all rooms joined after a middleware failure", (done) => { + const io = new Server(0); + const client = createClient(io, "/"); + + io.use((socket, next) => { + socket.join("room1"); + next(new Error("nope")); + }); + + client.on("connect_error", () => { + expect(io.of("/").adapter.rooms.size).to.eql(0); + + io.close(); + success(done, io, client); + }); + }); + + it("should not join rooms after disconnection", (done) => { + const io = new Server(0); + const client = createClient(io, "/"); + + io.on("connection", (socket) => { + socket.disconnect(); + socket.join("room1"); + }); + + client.on("disconnect", () => { + expect(io.of("/").adapter.rooms.size).to.eql(0); + + io.close(); + success(done, io, client); + }); + }); + + describe("onAny", () => { + it("should call listener", (done) => { + const io = new Server(0); + const clientSocket = createClient(io, "/", { multiplex: false }); + + clientSocket.emit("my-event", "123"); + + io.on("connection", (socket) => { + socket.onAny((event, arg1) => { + expect(event).to.be("my-event"); + expect(arg1).to.be("123"); + success(done, io, clientSocket); + }); + }); + }); + + it("should prepend listener", (done) => { + const io = new Server(0); + const clientSocket = createClient(io, "/", { multiplex: false }); + + clientSocket.emit("my-event", "123"); + + io.on("connection", (socket) => { + let count = 0; + + socket.onAny((event, arg1) => { + expect(count).to.be(2); + success(done, io, clientSocket); + }); + + socket.prependAny(() => { + expect(count++).to.be(1); + }); + + socket.prependAny(() => { + expect(count++).to.be(0); + }); + }); + }); + + it("should remove listener", (done) => { + const io = new Server(0); + const clientSocket = createClient(io, "/", { multiplex: false }); + + clientSocket.emit("my-event", "123"); + + io.on("connection", (socket) => { + const fail = () => done(new Error("fail")); + + socket.onAny(fail); + socket.offAny(fail); + expect(socket.listenersAny.length).to.be(0); + + socket.onAny(() => { + success(done, io, clientSocket); + }); + }); + }); + }); + + describe("onAnyOutgoing", () => { + it("should call listener", (done) => { + const io = new Server(0); + const clientSocket = createClient(io, "/", { multiplex: false }); + + io.on("connection", (socket) => { + socket.onAnyOutgoing((event, arg1) => { + expect(event).to.be("my-event"); + expect(arg1).to.be("123"); + + success(done, io, clientSocket); + }); + + socket.emit("my-event", "123"); + }); + }); + + it("should call listener when broadcasting", (done) => { + const io = new Server(0); + const clientSocket = createClient(io, "/", { multiplex: false }); + + io.on("connection", (socket) => { + socket.onAnyOutgoing((event, arg1) => { + expect(event).to.be("my-event"); + expect(arg1).to.be("123"); + + success(done, io, clientSocket); + }); + + io.emit("my-event", "123"); + }); + }); + + it("should prepend listener", (done) => { + const io = new Server(0); + const clientSocket = createClient(io, "/", { multiplex: false }); + + io.on("connection", (socket) => { + let count = 0; + + socket.onAnyOutgoing((event, arg1) => { + expect(count).to.be(2); + + success(done, io, clientSocket); + }); + + socket.prependAnyOutgoing(() => { + expect(count++).to.be(1); + }); + + socket.prependAnyOutgoing(() => { + expect(count++).to.be(0); + }); + + socket.emit("my-event", "123"); + }); + }); + + it("should remove listener", (done) => { + const io = new Server(0); + + const clientSocket = createClient(io, "/", { multiplex: false }); + + io.on("connection", (socket) => { + const fail = () => done(new Error("fail")); + + socket.onAnyOutgoing(fail); + socket.offAnyOutgoing(fail); + expect(socket.listenersAnyOutgoing.length).to.be(0); + + socket.onAnyOutgoing(() => { + success(done, io, clientSocket); + }); + + socket.emit("my-event", "123"); + }); + }); + }); +}); diff --git a/test/support/util.ts b/test/support/util.ts index 87672b8204..f052f51658 100644 --- a/test/support/util.ts +++ b/test/support/util.ts @@ -31,16 +31,50 @@ expect.Assertion.prototype.contain = function (...args) { export function createClient( io: Server, - nsp: string, - opts?: ManagerOptions & SocketOptions + nsp: string = "/", + opts?: Partial ): ClientSocket { // @ts-ignore const port = io.httpServer.address().port; return ioc(`http://localhost:${port}${nsp}`, opts); } -export function success(done: Function, io: Server, client: ClientSocket) { +export function success( + done: Function, + io: Server, + ...clients: ClientSocket[] +) { io.close(); - client.disconnect(); + clients.forEach((client) => client.disconnect()); done(); } + +export function successFn( + done: () => void, + sio: Server, + ...clientSockets: ClientSocket[] +) { + return () => success(done, sio, ...clientSockets); +} + +export function getPort(io: Server): number { + // @ts-ignore + return io.httpServer.address().port; +} + +export function createPartialDone(count: number, done: (err?: Error) => void) { + let i = 0; + return () => { + if (++i === count) { + done(); + } else if (i > count) { + done(new Error(`partialDone() called too many times: ${i} > ${count}`)); + } + }; +} + +export function waitFor(emitter, event) { + return new Promise((resolve) => { + emitter.once(event, resolve); + }); +} diff --git a/test/utility-methods.ts b/test/utility-methods.ts index 05b409f03f..efbc21c0b7 100644 --- a/test/utility-methods.ts +++ b/test/utility-methods.ts @@ -6,26 +6,10 @@ import expect from "expect.js"; import type { AddressInfo } from "net"; import "./support/util"; +import { createPartialDone } from "./support/util"; const SOCKETS_COUNT = 3; -const createPartialDone = ( - count: number, - done: () => void, - callback?: () => void -) => { - let i = 0; - return () => { - i++; - if (i === count) { - done(); - if (callback) { - callback(); - } - } - }; -}; - class DummyAdapter extends Adapter { fetchSockets(opts: BroadcastOptions): Promise { return Promise.resolve([ @@ -49,7 +33,7 @@ class DummyAdapter extends Adapter { } } -describe("socket.io", () => { +describe("utility methods", () => { let io: Server, clientSockets: ClientSocket[], serverSockets: Socket[]; beforeEach((done) => { const srv = createServer(); @@ -59,7 +43,12 @@ describe("socket.io", () => { clientSockets = []; for (let i = 0; i < SOCKETS_COUNT; i++) { - clientSockets.push(ioc(`http://localhost:${port}`)); + clientSockets.push( + ioc(`http://localhost:${port}`, { + // FIXME needed so that clients are properly closed + transports: ["websocket"], + }) + ); } serverSockets = []; @@ -77,100 +66,99 @@ describe("socket.io", () => { clientSockets.forEach((socket) => socket.disconnect()); }); - describe("utility methods", () => { - describe("fetchSockets", () => { - it("returns all socket instances", async () => { - const sockets = await io.fetchSockets(); - expect(sockets.length).to.eql(3); - }); + describe("fetchSockets", () => { + it("returns all socket instances", async () => { + const sockets = await io.fetchSockets(); + expect(sockets.length).to.eql(3); + }); - it("returns all socket instances in the given room", async () => { - serverSockets[0].join(["room1", "room2"]); - serverSockets[1].join("room1"); - serverSockets[2].join("room2"); - const sockets = await io.in("room1").fetchSockets(); - expect(sockets.length).to.eql(2); - }); + it("returns all socket instances in the given room", async () => { + serverSockets[0].join(["room1", "room2"]); + serverSockets[1].join("room1"); + serverSockets[2].join("room2"); + const sockets = await io.in("room1").fetchSockets(); + expect(sockets.length).to.eql(2); + }); - it("works with a custom adapter", async () => { - io.adapter(DummyAdapter); - const sockets = await io.fetchSockets(); - expect(sockets.length).to.eql(1); - const remoteSocket = sockets[0]; - expect(remoteSocket.id).to.eql("42"); - expect(remoteSocket.rooms).to.contain("42", "room1"); - expect(remoteSocket.data).to.eql({ username: "john" }); - }); + it("works with a custom adapter", async () => { + io.adapter(DummyAdapter); + const sockets = await io.fetchSockets(); + expect(sockets.length).to.eql(1); + const remoteSocket = sockets[0]; + expect(remoteSocket.id).to.eql("42"); + expect(remoteSocket.rooms).to.contain("42", "room1"); + expect(remoteSocket.data).to.eql({ username: "john" }); }); + }); - describe("socketsJoin", () => { - it("makes all socket instances join the given room", () => { - io.socketsJoin("room1"); - serverSockets.forEach((socket) => { - expect(socket.rooms).to.contain("room1"); - }); + describe("socketsJoin", () => { + it("makes all socket instances join the given room", () => { + io.socketsJoin("room1"); + serverSockets.forEach((socket) => { + expect(socket.rooms).to.contain("room1"); }); + }); - it("makes all socket instances in a room join the given room", () => { - serverSockets[0].join(["room1", "room2"]); - serverSockets[1].join("room1"); - serverSockets[2].join("room2"); - io.in("room1").socketsJoin("room3"); - expect(serverSockets[0].rooms).to.contain("room3"); - expect(serverSockets[1].rooms).to.contain("room3"); - expect(serverSockets[2].rooms).to.not.contain("room3"); - }); + it("makes all socket instances in a room join the given room", () => { + serverSockets[0].join(["room1", "room2"]); + serverSockets[1].join("room1"); + serverSockets[2].join("room2"); + io.in("room1").socketsJoin("room3"); + expect(serverSockets[0].rooms).to.contain("room3"); + expect(serverSockets[1].rooms).to.contain("room3"); + expect(serverSockets[2].rooms).to.not.contain("room3"); }); + }); - describe("socketsLeave", () => { - it("makes all socket instances leave the given room", () => { - serverSockets[0].join(["room1", "room2"]); - serverSockets[1].join("room1"); - serverSockets[2].join("room2"); - io.socketsLeave("room1"); - expect(serverSockets[0].rooms).to.contain("room2"); - expect(serverSockets[0].rooms).to.not.contain("room1"); - expect(serverSockets[1].rooms).to.not.contain("room1"); - }); + describe("socketsLeave", () => { + it("makes all socket instances leave the given room", () => { + serverSockets[0].join(["room1", "room2"]); + serverSockets[1].join("room1"); + serverSockets[2].join("room2"); + io.socketsLeave("room1"); + expect(serverSockets[0].rooms).to.contain("room2"); + expect(serverSockets[0].rooms).to.not.contain("room1"); + expect(serverSockets[1].rooms).to.not.contain("room1"); + }); - it("makes all socket instances in a room leave the given room", () => { - serverSockets[0].join(["room1", "room2"]); - serverSockets[1].join("room1"); - serverSockets[2].join("room2"); - io.in("room2").socketsLeave("room1"); - expect(serverSockets[0].rooms).to.contain("room2"); - expect(serverSockets[0].rooms).to.not.contain("room1"); - expect(serverSockets[1].rooms).to.contain("room1"); - }); + it("makes all socket instances in a room leave the given room", () => { + serverSockets[0].join(["room1", "room2"]); + serverSockets[1].join("room1"); + serverSockets[2].join("room2"); + io.in("room2").socketsLeave("room1"); + expect(serverSockets[0].rooms).to.contain("room2"); + expect(serverSockets[0].rooms).to.not.contain("room1"); + expect(serverSockets[1].rooms).to.contain("room1"); }); + }); + + describe("disconnectSockets", () => { + it("makes all socket instances disconnect", (done) => { + io.disconnectSockets(true); - describe("disconnectSockets", () => { - it("makes all socket instances disconnect", (done) => { - io.disconnectSockets(true); + const partialDone = createPartialDone(3, done); + + clientSockets[0].on("disconnect", partialDone); + clientSockets[1].on("disconnect", partialDone); + clientSockets[2].on("disconnect", partialDone); + }); - const partialDone = createPartialDone(3, done); + it("makes all socket instances in a room disconnect", (done) => { + serverSockets[0].join(["room1", "room2"]); + serverSockets[1].join("room1"); + serverSockets[2].join("room2"); + io.in("room2").disconnectSockets(true); - clientSockets[0].on("disconnect", partialDone); - clientSockets[1].on("disconnect", partialDone); - clientSockets[2].on("disconnect", partialDone); + const partialDone = createPartialDone(2, () => { + clientSockets[1].off("disconnect"); + done(); }); - it("makes all socket instances in a room disconnect", (done) => { - serverSockets[0].join(["room1", "room2"]); - serverSockets[1].join("room1"); - serverSockets[2].join("room2"); - io.in("room2").disconnectSockets(true); - - const partialDone = createPartialDone(2, done, () => { - clientSockets[1].off("disconnect"); - }); - - clientSockets[0].on("disconnect", partialDone); - clientSockets[1].on("disconnect", () => { - done(new Error("should not happen")); - }); - clientSockets[2].on("disconnect", partialDone); + clientSockets[0].on("disconnect", partialDone); + clientSockets[1].on("disconnect", () => { + done(new Error("should not happen")); }); + clientSockets[2].on("disconnect", partialDone); }); }); }); diff --git a/test/uws.ts b/test/uws.ts index 3bd4b648f8..de185d7c03 100644 --- a/test/uws.ts +++ b/test/uws.ts @@ -1,4 +1,8 @@ -import { App, us_socket_local_port } from "uWebSockets.js"; +import { + App, + us_socket_local_port, + us_listen_socket_close, +} from "uWebSockets.js"; import { Server } from ".."; import { io as ioc, Socket as ClientSocket } from "socket.io-client"; import request from "supertest"; @@ -19,6 +23,7 @@ const shouldNotHappen = (done) => () => done(new Error("should not happen")); describe("socket.io with uWebSocket.js-based engine", () => { let io: Server, + uwsSocket: any, port: number, client: ClientSocket, clientWSOnly: ClientSocket, @@ -33,6 +38,7 @@ describe("socket.io with uWebSocket.js-based engine", () => { io.of("/custom"); app.listen(0, (listenSocket) => { + uwsSocket = listenSocket; port = us_socket_local_port(listenSocket); client = ioc(`http://localhost:${port}`); @@ -54,6 +60,8 @@ describe("socket.io with uWebSocket.js-based engine", () => { afterEach(() => { io.close(); + us_listen_socket_close(uwsSocket); + client.disconnect(); clientWSOnly.disconnect(); clientPollingOnly.disconnect(); diff --git a/test/v2-compatibility.ts b/test/v2-compatibility.ts new file mode 100644 index 0000000000..20d3d4a76f --- /dev/null +++ b/test/v2-compatibility.ts @@ -0,0 +1,64 @@ +import { Server, Socket } from ".."; +import expect from "expect.js"; +import { success, getPort, waitFor } from "./support/util"; +import * as io_v2 from "socket.io-client-v2"; + +describe("v2 compatibility", () => { + it("should connect if `allowEIO3` is true", (done) => { + const io = new Server(0, { + allowEIO3: true, + }); + + const clientSocket = io_v2.connect(`http://localhost:${getPort(io)}`, { + multiplex: false, + }); + + Promise.all([ + waitFor(io, "connection"), + waitFor(clientSocket, "connect"), + ]).then(([socket]) => { + expect((socket as Socket).id).to.eql(clientSocket.id); + + success(done, io, clientSocket); + }); + }); + + it("should be able to connect to a namespace with a query", (done) => { + const io = new Server(0, { + allowEIO3: true, + }); + + const clientSocket = io_v2.connect( + `http://localhost:${getPort(io)}/the-namespace`, + { + multiplex: false, + } + ); + clientSocket.query = { test: "123" }; + + Promise.all([ + waitFor(io.of("/the-namespace"), "connection"), + waitFor(clientSocket, "connect"), + ]).then(([socket]) => { + expect((socket as Socket).handshake.auth).to.eql({ test: "123" }); + + success(done, io, clientSocket); + }); + }); + + it("should not connect if `allowEIO3` is false (default)", (done) => { + const io = new Server(0); + + const clientSocket = io_v2.connect(`http://localhost:${getPort(io)}`, { + multiplex: false, + }); + + clientSocket.on("connect", () => { + done(new Error("should not happen")); + }); + + clientSocket.on("connect_error", () => { + success(done, io, clientSocket); + }); + }); +});